• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
学习?学个屁!
博客园    首页    新随笔    联系   管理    订阅  订阅
自动化测试大赛全省第一学习笔记(功能测试未学习)

环境配置

Linux相关
[[鸟哥Linux基础]]
[[Linux命令行与Shell脚本编程大全]]

>centos无网络,使用virtualBox的NAT连接
解1:
1. su切换到root
2. cd到/etc/sysconfig/network-scripts/
3. vi编辑ifcfg-enp0s3文件
4. HWADDR=00:00:00:00(这个替换为MAC地址) ONBOOT=no改为yes,添加BOOTPROTO=dhcp
5. 重启网络service network restart

解2:
1. vi /etc/resolv.conf
2. 增加一行nameserver 后面是主机地址,这里是添加DNS服务器

配置JDK

centos系统自带OpenJDK,如果需要安装其他版本,可能需要先卸载

[test@localhost ~]$ java -version
openjdk version "1.8.0_262"
OpenJDK Runtime Environment (build 1.8.0_262-b10)
OpenJDK 64-Bit Server VM (build 25.262-b10, mixed mode)

卸载OpenJDK

rpm -qa | grep java//查询相关java套件
//.noarch文件可以不用管
rpm -e --nodeps java文件

rpm安装
一般不需要手动配置环境变量,因为rpm包安装过程中会自动将必要的路径添加到系统的环境变量中

# rpm包的安装命令
rpm -ivh 包全名
选项:
    -i(install)    安装
    -v(verbose)    显示详细信息
    -h(hash)       显示进度
    --nodeps       不检测依赖性

tar.gz安装

# 解压gz压缩包
tar -zxvf 包全名
选项:
	-z: 通过gzip过滤归档文件,用于处理.gz压缩文件
	-x: 提取文件
	-v: 显示详细信息
	-f: 指定归档文件的名称
# 创建文件夹
mkdir -p 
# 复制jdk到上一步创建的文件夹
cp -r 

# 编辑全局变量文件
vim /etc/profile
export JAVA_HOME=jdk所在目录
export JRE_HOME=$JAVA_HOME/jre
export PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin

# 使配置文件生效
source /etc/profile

配置Mysql5.7

Mysql相关
[[MySQL基础]]

tar.gz安装

环境检查

// 检查 是否有 mysql 的进程
ps ajx | grep mysql          
// 检查 是否有 mariabd 的进程
ps ajx | grep mariabd       
//如果发现有进程在运行需要关闭进程
systemctl stop  mysqld  
// 检查是否有安装包
rpm -qa | grep mysql
//若有安装包出现,并且之前没有用过MySQL,那就将这些安装包删除
//批量化删除安装包
rpm -qa | grep mysql  | xargs  yum -y remove  
//检查是否有配置文件
ls /etc/my.cnf
//删除配置文件
rm -rf /etc/my.cnf

安装配置

//解压
tar -zxvf
//创建用户组
groupadd mysql
/*
-r 选项表示创建一个系统用户,系统用户通常没有登录 shell,它们通常用于运行服务
-g mysql 选项指定新用户的主组为mysql,这个组必须已经存在
-s /bin/false 选项指定用户的登录shell 为 /bin/false,这是一个假的shell,意味着这个用户不能通过密码登录系统
mysql 是新用户的用户名
*/
useradd -r -g mysql -s /bin/false mysql
/*
将当前目录及其子目录和文件所有权改为用户mysql和组mysql
-R表示递归更改
*/
chown -R mysql:mysql .

//安装mysql,路径根据实际情况更改
./bin/mysqld --user=mysql --basedir=/opt/mysql --datadir=/opt/mysql/data --initialize
//修改MySQL配置文件
vi /etc/my.cnf
//开启mysql
./support-files/mysql.server start

//配置环境变量
export PATH=$PATH:/opt/mysql/bin
//将mysql进程放入系统进程中
cp support-files/mysql.server /etc/init.d/mysqld
//重新启动mysql服务
service mysqld restart

//使用随机密码登录mysql数据库
mysql -u root -p
//将名为root的用户,其登录地址为localhost的密码修改为123456
alter user 'root'@'localhost' identified by '123456';
//将用户名为root的用户的主机字段设置为%,表示从任何主机连接到MySQL服务器,而不仅仅是从localhost
use mysql;
user SET Host = '%' WHERE User = 'root';
//查看修改后的值
select user,host from user;
//刷新权限
flush privileges;

//确保防火墙允许MySQL的默认端口(3306)通过
firewall-cmd --zone=public --add-port=3306/tcp --permanent 
firewall-cmd --reload

my.cnf文件

[mysqld]
port=3306
basedir=/opt/mysql
datadir=/opt/mysql/data
socket=/opt/mysql/mysql.sock
character-set-server=utf8
symbolic-links=0
bind_address=0.0.0.0

[mysqld_safe]
log-error=/opt/mysql/mariadb/log/mariadb.log
pid-file=/opt/mysql/mariadb/run/mariadb.pid

[client]
socket=/opt/mysql/mysql.sock
default-character-set=utf8

!includedir /etc/my.cnf.d

配置Tomcat与war包的部署

配置tomcat

//进入到bin下
./startup.sh //开启
./shutdown.sh //关闭

//查看系统中的所有开放端口
firewall-cmd --zone=public --list-ports
//打开8080端口
firewall-cmd --zone=public --add-port=8080/tcp --permanent
//重启防火墙
systemctl restart firewalld.service

Python基础

输出

n=100  
print("one:%d"%n) #整数d
n=33.333  
print("two:%f"%n) #浮点数f
n="sss"  
print("three:%s"%n) #字符串s
n = { 1, 2, 3 }  
print("four:%r"%n) #万能r,输出原始表示

#f-string字符串格式化,类似字符串内插  
age=18  
name="root"  
print(f"my name is {name}, my age is {age}")

#str.format方法,格式控制符通常包含在大括号{}中,并可以使用命名参数或位置参数
template = "整数:{}, 浮点数:{:.2f}, 字符串:{}"  
print(template.format(123, 45.6789, "hello"))

print()有一个可选参数end=,可以指定为空字符串''就不会换行,可以设置为其他字符或字符串,以便在输出后添加自定义的分隔符

输入

while (1):  
    try:  
        age = int(input("请输入您的年龄:"))  
        print(f"您的年龄是:{age}岁")  
        break  
    except ValueError:  
        print("对不起,您输入的不是一个有效的年龄。请重新输入一个整数")

类型转换

#转换为float
height = float(input("请输入您的身高(米):") 
print(f"您的身高是:{height}米")

[!hint]

  • Python不区分单引号和双引号,它们都可以表示一个字符串
  • 单引号和双引号可以互相嵌套使用,但不能交叉使用
  • 单行注释#,多行注释三对引号,不区分单双引号
  • Python使用花括号表示语句体,使用语句缩进判断语句体

Python的字符串运算符

str1 + str2 #字符串连接
str * n #重复n次字符串
[] #索引获取字符串中字符,也可以使用冒号获取部分字符
str in a #字符串是否包含给定字符
str not in a #字符串是否不包含给定字符
r/R"str" #让字符串原始输出,不转义

格式化字符串和内建函数自查

分支和循环结构

if语句

a = 1  
b = 2  
if a > b:  
    print("a max!")  
else:  
    print("b max!")

#多重条件判断,不能使用else if,使用elif
results = 99  
if results >= 90:  
    print('优秀')  
elif results >= 70:  
    print('良好')  
elif results >= 60:  
    print('及格')  
else:  
    print('不及格')

三元表达式

x = 10  
y = "positive" if x > 0 else "non-positive"  
print(y)

for语句

#遍历字符串,只有一条语句可以写在一行
for i in "hello world": print(i, end = '')

#遍历数组(列表)
fruits=['banana', 'apple', 'mango']  
for fruit in fruits: print(fruit)

如果需要进行一定次数的循环,则需要借助range()函数

for i in range(5, 10, 2): print(i, end = ' ')

range()函数,第一个参数是开始位置,第二个参数是结束位置,第三个参数是循环步长,后两个参数是可选参数

如果只想循环而不在乎每次迭代的索引或值:

for _ in range(5): print("Hello World")

_只是一个普通的变量名,也可以使用其他变量名来替代,只是按照编程惯例,它表示一个占位符来表示该变量不会被使用

数组(列表)
数组是方括号表示,每一项使用逗号隔开,数组下标从零开始,Python将数组称为列表

#Python列表内置方法
append(x) #列表末尾添加一个元素
extend(x) #将另一个列表的所有元素添加到列表中
insert(i, x) #在指定位置插入元素
remove(x) #移除列表中第一个值为x的元素
pop([i]) #移除并返回列表中指定位置的元素,未指定位置默认移除并返回最后一个元素
clear() #移除列表所有元素
index(x, strat,stop) #返回第一个值为x的元素的索引,后为可选参数,指定范围搜索
count(x) #返回列表中值为x的元素的数量
sort(key=,reverse=) #对列表进行原地排序,都是可选参数,key指定一个函数,该函数会在排序之前应用于每个元素。这允许你基于元素的某个属性或转换后的值进行排序.reverse布尔值,true为降序,默认false为升序
reverse() #反转列表元素顺序
copy() #返回列表的浅拷贝

可以使用下标索引访问列表中的值.也可以使用方括号的形式截取字符

list = ['physics', 'chemistry', 1997, 2000, 1, 2]  
print("list[1:3]: ", list[1:3])#不包含3
#1到末尾
for i in list[1:]:  
    print("list[1:4]: ", i)
#负数是反转读取,-1就是倒数第一个元素

列表对+和*的操作符与字符串类似,+组合列表,*重复列表

print(3 in list) #元素是否在列表中
len(list) #列表长度

字典
字典使用花括号表示(就是键值对),一个key对应一个value,之间使用冒号分隔,不同项之间使用逗号分隔

Python规定一个字典中的key必须独一无二,value可以相同

#Python字典内置方法
clear() #清除字典所有项
copy() #返回字典浅拷贝
fromkeys(seq,value) #创建新字典,以序列seq中元素作为字典建,value是可选参数,为字典所有键对应的初始值
get(key,default) #返回指定键的值,如果键不存在则返回default值,如果未指定default,则返回None
items() #返回包含字典中所有键值对的视图对象
keys() #返回包含字典中所有键的视图对象
values() #返回包含字典中所有值的视图对象
pop(key,default) #移除并返回列表中指定位置的元素,未指定位置默认移除并返回最后一个元素,default是可选参数
popitem() #随机移除字典中的一对键值对,并作为一个元组返回,如果字典为空,抛出KeyError异常
setdefault(key,default) #如果键不存在则插入键并将值设置为default,如果键已经存在则返回其值。default的默认值为None
update() #使用另一个字典的键值对更新该字典

元组
元祖的元素不能被修改,元祖使用圆括号创建,逗号隔开,元组中只包含一个元素时,需要在元素后面添加逗号

元组与字符串类似,下标索引从0开始,可以进行截取,组合等

元组中的元素值是不允许修改的,但可以对元组进行连接组合

tup1 = (12, 34.56) 
tup2 = ('abc', 'xyz') 
#修改元组元素操作是非法的
tup1[0] = 100 
#创建一个新的元组
tup3 = tup1 + tup2 
print(tup3)

[!caution]
del语句在Python中用于删除对象。它可以用于删除变量、列表中的元素、字典中的键值对,甚至是整个对象(比如列表或字典)
del语句用于解除一个或多个对象与它们名称之间的绑定

元组中的元素值不能修改,但可以使用del删除整个元组

del tup3

与字符串一样,元组之间可以使用+号和*号进行运算。这就意味着他们可以组合和复制,运算后会生成一个新的元组

#元组内置函数
cmp(tuple1, tuple2) #比较两个元组元素
len(tuple) #计算元组元素个数
max(tuple) #返回元组中元素最大值
min(tuple) #返回元组中元素最小值
tuple(seq) #将列表转换为元组

函数
def关键字定义,后接函数标识符和圆括号

使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为Python解释器能够用参数名匹配参数值

function(agr=2,str="meet")

#默认参数
def function(name, age = 12):

#不定长参数,带星号的变量名会存放所有未命名的变量参数
def printinfo( arg1, *vartuple ): 
 print("输出: ") 
 print(arg1)
 for var in vartuple: print(var) 

Python使用lambda表达式创建匿名函数,只包含一条语句

#将它赋给变量即可作为函数使用,这怎么那么像C#的委托
sum = lambda arg1, arg2: arg1 + arg2 
# 调用sum函数 
print("相加后的值为: "), sum( 10, 20 )
print("相加后的值为: "), sum( 20, 20 )

Python中的类型属于对象,变量没有类型

a = [1, 2, 3]
a = 'Apple'

[1,2,3]是list类型,'Apple'是String类型,变量a没有类型,它仅是一个对象的引用(指针)

python函数的参数传递:

  • 不可变类型:类似c++的值传递,如整数、字符串、元组,只传递值副本,不传递本身

  • 可变类型:类似c++的引用传递,如列表,字典,传递对象本身

类和方法
class关键字创建类

class A(object):
#创建了A类,默认继承object,不显式声明继承也可以

方法和函数唯一的不同是,方法第一个参数必须存在,一般命名为self,但在调用这个方法时不需要为这个参数传值

一般在创建类时会首先声明初始化方法__init__()

class A():
	def__init__(self, a, b):
	 self.a=int(a)
	 self.b=int(b)

就是构造函数

模块
也就是类库,一个模块只会被导入一次,不管执行多少次import。这样可以防止导入模块被一遍又一遍地执行

#引入模块或模块中的函数
import 模块.函数
#从模块中导入一个指定的部分到当前命名空间中
from 模块 import 函数1,函数2
from 模块 import * #把该模块的所有内容导入到当前的命名空间

模块搜索路径存储在system模块的sys.path变量中。变量里包含当前目录,PYTHONPATH和由安装过程决定的默认目录

Python标准异常

BaseException 所有异常的基类
SystemExit 解释器请求退出
KeyboardInterrupt 用户中断执行(通常是输入^C)
Exception 常规错误的基类
StopIteration 迭代器没有更多的值
GeneratorExit 生成器(generator)发生异常来通知退出
StandardError 所有的内建标准异常的基类
ArithmeticError 所有数值计算错误的基类
FloatingPointError 浮点计算错误
OverflowError 数值运算超出最大限制
ZeroDivisionError 除(或取模)零 (所有数据类型)
AssertionError 断言语句失败
AttributeError 对象没有这个属性
EOFError 没有内建输入,到达EOF 标记
EnvironmentError 操作系统错误的基类
IOError 输入/输出操作失败
OSError 操作系统错误
WindowsError 系统调用失败
ImportError 导入模块/对象失败
LookupError 无效数据查询的基类
IndexError 序列中没有此索引(index)
KeyError 映射中没有这个键
MemoryError 内存溢出错误(对于Python 解释器不是致命的)
NameError 未声明/初始化对象 (没有属性)
UnboundLocalError 访问未初始化的本地变量
ReferenceError 弱引用(Weak reference)试图访问已经垃圾回收了的对象
RuntimeError 一般的运行时错误
NotImplementedError 尚未实现的方法
SyntaxError Python 语法错误
IndentationError 缩进错误
TabError Tab 和空格混用
SystemError 一般的解释器系统错误
TypeError 对类型无效的操作
ValueError 传入无效的参数
UnicodeError Unicode 相关的错误
UnicodeDecodeError Unicode 解码时的错误
UnicodeEncodeError Unicode 编码时错误
UnicodeTranslateError Unicode 转换时错误
Warning 警告的基类
DeprecationWarning 关于被弃用的特征的警告
FutureWarning 关于构造将来语义会有改变的警告
OverflowWarning 旧的关于自动提升为长整型(long)的警告
PendingDeprecationWarning 关于特性将会被废弃的警告
RuntimeWarning 可疑的运行时行为(runtime behavior)的警告
SyntaxWarning 可疑的语法的警告
UserWarning 用户代码生成的警告

除了在except中使用,还可以使用raise语句抛出异常

raise 异常类型(可以附加值,通常是用于描述异常的原因)

单元测试Junit框架

JUnit4注解

  • @Test:注释方法为测试方法,JUnit 运行器将执行标有 @Test 注解的所有方法
    • expected=指定预期的异常。如果测试方法抛出了指定的异常,则测试通过;否则,测试失败
    • timeout=用于指定测试方法的最大运行时间(以毫秒为单位)。如果测试方法在指定时间内没有完成,则测试失败
  • @Before:在每个测试方法之前执行。用于初始化测试环境
  • @After:在每个测试方法之后执行。用于清理测试环境
    ![[Pasted image 20240930160011.png]]
  • @BeforeClass:在所有测试方法之前仅执行一次。用于执行一次性的初始化,如数据库连接
  • @AfterClass:在所有测试方法之后仅执行一次。用于执行一次性的清理,如关闭数据库连接
    ![[Pasted image 20240930160026.png]]
  • @Ignore:忽略某个测试方法或测试类。被忽略的测试不会被 JUnit 运行器执行

参数化测试

允许使用不同的值反复运行同一测试,遵循5个步骤创建参数化测试
1. 使用`@RunWith(Parameterized.class)`注释指定测试运行器
2. 创建一个由`@Parameters`注释的公共的静态方法,它返回一个对象的集合(数组)来作为测试数据集合
3. 创建一个公共的构造函数,它接受和一行测试数据相等同的东西
4. 为每一列测试数据创建一个实例变量
5. 用实例变量作为测试数据的来源来创建你的测试用例
  • @RunWith(Parameterized.class):在一个测试类上使用 @RunWith(Parameterized.class) 注解时,告诉JUnit使用Parameterized运行器来执行这个测试类中的所有测试方法。这个运行器知道如何处理参数化测试,即它会为@Parameters注解方法提供的每组数据创建一个测试实例,并为每个实例运行测试方法
  • @Parameters:这个注解用于定义参数化测试的数据。它修饰一个静态方法,该方法返回一个 Collection<Object[]> 类型的数据集合,其中每个Object[]包含一组参数,这些参数将被用来构造测试类的实例。通常返回一个列表(如 Arrays.asList),其中包含了多个数组,每个数组代表一组测试参数。这些参数将按照它们在列表中的顺序被用来创建测试实例,并分别运行测试方法
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

import java.util.Arrays;
import java.util.Collection;

import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class FourFlowChartTest {
	static FourFlowChart fourFlowChart;
	@BeforeClass
	public static void setUP() {
		fourFlowChart=new FourFlowChart();
	}
	private String usernameString;
	private String passwordString;
	private String expectedString;
	public FourFlowChartTest(String u,String p,String e) {
		this.usernameString=u;
		this.passwordString=p;
		this.expectedString=e;
	}
	@Parameters
	public static Collection<Object[]> datas(){
		return Arrays.asList(new Object[][] {
			{"","","用户名或密码不能为空"},
			{"admin","123","登录成功"},
			{"test","123","请输入正确的用户名"},
			{"admin","1","请输入正确的密码"},
			{"test","1","请输入正确的用户名和密码"},
		});
	}
	@Test
	public void test() {
		String result=fourFlowChart.getResult(usernameString, passwordString);
		assertEquals(expectedString, result);
	}

}

使用BufferedReader和FileReader读取csv文件

package test;
import static org.junit.Assert.assertEquals;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class ReadCSVAddTest {
	private ReadCSVAdd readCSVAdd=new ReadCSVAdd();

	private int a;
	private int b;
	private int expected;
	public ReadCSVAddTest(int a,int b,int expected) {
		this.a=a;
		this.b=b;
		this.expected=expected;
	}
	@Parameters
	public static Collection<Object[]> datas() throws IOException{
		ArrayList<Object[]> datas=new ArrayList<>();
		BufferedReader br=new BufferedReader(new FileReader("F:\\AutoTest\\Eclipse\\code\\code\\review\\junitCode\\test\\data.csv"));
		String line;
	try {
		while((line=br.readLine())!=null)
		{
			String[] values=line.split(",");
			int a=Integer.parseInt(values[0]);
			int b=Integer.parseInt(values[1]);
			int expected=Integer.parseInt(values[2]);
			datas.add(new Object[] {a,b,expected});
		}
	}finally {
		br.close();
	}
		return datas;
	}
	@Test
	public void testAdd() {
		int result=readCSVAdd.getResult(a, b);
		assertEquals("不满足加法需求",result,expected);
	}
}

测试套件

一种可批量运行测试类的方法

import org.junit.runner.RunWith;
import org.junit.runners.Suite;

@RunWith(Suite.class)
@Suite.SuiteClasses({//测试类数组
EmailRegisterTest.class,
GetDaysTest.class
})
public class SuiteTest {//空类作为测试套件的入口

}

Rule注解

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;

public class TestNameExample {
	@Rule
	public TestName testName = new TestName();//在测试方法中获取当前测试方法的名称
	
	@Test
	public void testMethod1() {
		System.out.println("Running test: " + testName.getMethodName());
	}
	
	@Test
	public void testMethod2() {
		System.out.println("Running test: " + testName.getMethodName());
	}
}
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;

public class TemporaryFolderExample {
	@Rule
	public TemporaryFolder temporaryFolder = new TemporaryFolder();//在测试中创建临时文件和目录,并在测试结束后自动删除它们。这对于需要文件系统操作的测试非常有用
	
	@Test
	public void testCreateFile() throws IOException {
		File file = temporaryFolder.newFile("test.txt");
		System.out.println("Created file: " + file.getAbsolutePath());
	}
	
	@Test
	public void testCreateDirectory() throws IOException {
		File directory = temporaryFolder.newFolder("testDir");
		System.out.println("Created directory: " + directory.getAbsolutePath());
	}
}

ExternalResource 是 JUnit 提供的一个基类,用于在测试前后执行资源设置和清理工作
每个测试方法执行前都会打印 "Before test: Setting up resources",执行后都会打印 "After test: Tearing down resources"

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExternalResource;
  
public class ExternalResourceExample {
	@Rule
	public ExternalResource resource = new ExternalResource() {
		@Override
		protected void before() throws Throwable {
			System.out.println("Before test: Setting up resources");
		}
		@Override
		protected void after() {
			System.out.println("After test: Tearing down resources");
		}
	};
	@Test
	public void testMethod1() {
		System.out.println("Running test method 1");
	}
	@Test
	public void testMethod2() {
		System.out.println("Running test method 2");
	}
}

JUnit4断言

  • assertEquals(expected, actual): 断言检查两个值是否相等

  • assertEquals(double expected, double actual, double delta): 断言检查两个双精度浮点数是否在指定的误差范围内相等

  • assertEquals(float expected, float actual, float delta): 断言检查两个浮点数是否在指定的误差范围内相等

  • assertNotNull(Object object): 断言检查对象不为空

  • assertNull(Object object): 断言检查对象为空

  • assertTrue(boolean condition): 断言检查条件是否为 true

  • assertFalse(boolean condition): 断言检查条件是否为 false

  • assertSame(Object expected, Object actual): 断言检查两个对象引用是否指向同一个对象

  • assertNotSame(Object expected, Object actual): 断言检查两个对象引用是否指向不同的对象

  • assertArrayEquals(Object[] expecteds, Object[] actuals): 断言检查两个数组是否相等

  • assertArrayEquals(double[] expecteds, double[] actuals, double delta):断言两个双精度浮点数数组相等,允许有一定的误差范围

collapse: none
assertTure和false以及assertEquals和NotEquals和assertNull和assertNotNull都可以有第一个string参数,用于自定义失败信息
`import static org.junit.Assert.*;`是JUnit4断言的包
`import static org.hamcrest.MatcherAssert.assertThat;`
`import static org.hamcrest.Matchers.*;`是Hamcrest测试断言库的包

Hamcrest测试断言库

  • assertThat(actual,metcher) 是 Hamcrest 提供的一种灵活且可读性更高的断言方式,actual是被测试的实际值,matcher是用于匹配的条件,如预期结果
  • assertThat(actual,equalTo(expected)):检查两个值是否相等
  • assertThat(actual, is(expected)):用于表示一个匹配,其中的值应与给定的值相等
  • assertThat(actual, is(not(expected)));:用于表示条件不成立
int actual = 3;
int unexpected = 5;
assertThat(actual, is(not(unexpected))); // 断言成功,因为 3 不等于 5
  • assertThat(actual, greaterThan(expectedValue));:检查一个数字是否大于另一个数字

  • assertThat(actual, lessThan(expectedValue));:检查一个数字是否小于另一个数字

  • emptyString和notEmptyString:检查字符串是否为空或非空

String actualEmpty = "";
String actualNotEmpty = "Hello";

assertThat(actualEmpty, is(emptyString())); // 断言成功,因为字符串为空
assertThat(actualNotEmpty, is(not(emptyString()))); // 断言成功,因为字符串非空

+assertThat(actual, containsString(expectedSubstring));:检查字符串是否包含某个子字符串

String actual = "Hello, World!";
String expectedSubstring = "World";
assertThat(actual, containsString(expectedSubstring)); // 断言成功,因为包含子串 "World"
  • hasItem和hasItems:检查集合中是否包含某个元素或多个元素
List<String> collection = Arrays.asList("apple", "banana", "orange");

assertThat(collection, hasItem("banana")); // 断言成功,因为集合中包含 "banana"
assertThat(collection, hasItems("apple", "orange")); // 断言成功,因为集合中同时包含 "apple" 和 "orange"
  • assertThat(array, arrayContaining(expectedElement1, expectedElement2));:检查数组是否按照顺序包含指定的元素
String[] array = {"one", "two", "three"};
assertThat(array, arrayContaining("one", "two", "three")); // 断言成功,因为数组按顺序包含这些元素

自动化测试脚本设计

术语定义

  1. 自动化测试概念:自动化测试是把以人为驱动的测试行为转化为机器执行的一种过程。
  2. 自动化测试前提条件:需求变动不频繁、项目周期足够长、自动化测试脚本可重复使用。
  3. 自动化测试的流程:(1)制定测试计划、(2)分析测试需求、(3)设计测试用例、(4)搭建测试环境、(5)编写并执行测试脚本、(6)分析测试结果并记录Bug、(7)跟踪Bug并进行回归测试。
  4. 进行自动化测试的目的:随着国家计算机信息化的发展,软件都是需要快速迭代,像一些重复性的工作可以通过自动化来完成,从而提高工作的效率和准确性,达到快速迭代的目的

首先要导入selenium和安装浏览器驱动

from selenium import webdriver
from selenium.webdriver.chrome.service import Service  
# 设置ChromeDriver的路径  
chromedriver_path = r"路径"  
  
# 创建Service对象  
service = Service(chromedriver_path)  
  
# 初始化WebDriver  
# webdriver.Chrome()要求驱动在环境变量中
driver = webdriver.Chrome(service=service)  
driver.get("http://www.bing.com")

简单元素操作

username.clear()#清除文本框中输入内容
username.send_keys("root")#模拟键盘向输入框内输入内容
username.submit()#提交表单
#除此之外还有
.size #返回元素的尺寸
.text #获取元素的文本
.get_attribute(name) #获得属性值
.is_displayed() #该元素是否用户可见,返回布尔值
.is_selected()  #判断元素是否被选中
.is_enabled()   #判断元素是否可编辑

使用 Select 类来处理下拉菜单

# 找到下拉菜单元素
select_element = driver.find_element(By.NAME, 'name')  # 替换为下拉菜单的 NAME 属性
# 创建 Select 对象
select = Select(select_element)
# 通过索引选择选项
select.select_by_index(1)  # 选择第二个选项,索引从0开始
# 通过可见文本选择选项
select.select_by_visible_text("Option Text")  # 替换为实际可见文本
# 通过值选择选项
select.select_by_value("option_value")  # 替换为实际的值

select = Select(driver.find_element(By.XPATH,'xpath'))
select.deselect_all()# 取消选择已经选择的元素

select = Select(driver.find_element(By.XPATH,'xpath'))
all_selected_options = select.all_selected_options# 获取所有已经选择的选项

拖放
将一个网页元素拖放到另一个目标元素

element = driver.find_element_by_name("source")  
target = driver.find_element_by_name("target")  
  
from selenium.webdriver import ActionChains 
action_chains = ActionChains(driver) # 创建一个新的操作链对象,用于执行复合操作
action_chains.drag_and_drop(element, target).perform()# 从源元素拖动到目标元素的操作

浏览器基本操作方法

from time import sleep#导入time模块的sleep函数
from selenium import webdriver#从selenium模块导入webdriver

#打开指定浏览器
driver = webdriver.Edge()
#跳转至指定url
driver.get("https://www.baidu.com") 
#时间等待2秒
sleep(2)  
driver.get("https://weread.qq.com/")  
sleep(2)  
#后退操作
driver.back()  
sleep(2) 
#前进操作
driver.forward()  
sleep(2)

driver.refresh()刷新页面
driver.maximize_window()将当前浏览器窗口最大化
driver.close()关闭当前浏览器窗口
driver.quit()退出当前浏览器

基本元素定位

格式:find_element("")
这个方法需要两个参数:一个定位策略(由By类提供),一个用于该定位策略的值(如元素的ID、类名、标签名等)

  • By.ID: 通过元素的ID属性值来定位元素
  • By.NAME: 通过元素的name属性值来定位元素
  • By.CLASS_NAME: 通过元素的class名来定位元素
  • By.TAG_NAME: 通过元素的标签名来定位元素
  • By.XPATH: 通过XPath表达式来定位元素。XPath是一种在XML文档中查找信息的语言,同样适用于HTML
  • By.CSS_SELECTOR: 通过CSS选择器来定位元素。CSS选择器是一种用于选择HTML元素的模式
  • By.LINK_TEXT: 通过完整的链接文本来定位元素,通常用于<a>标签,当需要进行页面跳转时可以使用
  • By.PARTIAL_LINK_TEXT: 通过部分链接文本来定位元素
from time import sleep  
from selenium import webdriver  
#导入By类,用于指定元素查找方式
from selenium.webdriver.common.by import By 
#导入WebDriverWait类,用于显式等待
from selenium.webdriver.support.ui import WebDriverWait 
#导入expected_conditions模块,并为其设置别名EC,该模块包含了一系列预定义的等待条件  
from selenium.webdriver.support import expected_conditions as EC  
  
driver = webdriver.Edge()  
driver.get("http://127.0.0.1:5500/webstorm/login.html")  


#如果10秒内该元素出现,则继续执行;否则抛出异常
#presence_of_element_located检查DOM是否存在一个元素
username=WebDriverWait(driver,10).until(EC.presence_of_element_located((By.ID, "username")))  
#在找到的用户名输入框中输入文本root
username.send_keys("root")  

password=WebDriverWait(driver,10).until(EC.presence_of_element_located((By.NAME, "password"))).send_keys("123")  

sleep(3)

下面的代码更加简洁,但使用find_element方法而不加任何等待可能会导致问题,特别是当页面元素是动态加载的,或者当网络延迟、页面渲染速度等因素导致元素在查找时还未可用时

#找到ID为username的元素并赋给username变量
username = driver.find_element(By.ID, "username") 
#模拟用户文本输入
username.send_keys("root")

driver.find_element(By.NAME, "password").send_keys("123456")  

等待可以还可以使用隐式等待

driver.implicitly_wait(10)

当在后续的代码中使用find_element或find_elements方法时,WebDriver会在指定的时间内不断尝试查找元素,直到找到元素或超时
要注意隐式等待是全局的,应用于后续的所有元素查找操作

find_elements可以定位多个元素

driver.find_elements(By.TAG_NAME, "input")[0].send_keys("root")  
driver.find_elements(By.TAG_NAME, "input")[1].send_keys("123")

鼠标模拟操作

Selenium提供ActionChains这个类来处理该类事件

#从SeleniumWebDriver模块中导入ActionChains类
from selenium.webdriver import ActionChains

driver = webdriver.Edge()  
driver.get("http://127.0.0.1:5500/webstorm/login.html")  
  
username = driver.find_element(By.ID, "username")  
#模拟全选操作
ActionChains(driver).key_down(Keys.CONTROL, username).send_keys("a").key_up(Keys.CONTROL).perform()  
sleep(2)

也可以创建Actionchains实例

actions = ActionChains(driver)
# 移动鼠标到元素上并点击  
actions.move_to_element(element).click().perform()  

# 或者发送键盘输入到元素  
actions.send_keys("Hello, World!").perform()

.perform()的作用是触发并执行之前通过ActionChains对象构建的所有动作

#假设driver是WebDriver实例,并且已经导航到了目标页面  
element = driver.find_element(By.ID, "some-element-id")  
  
# 创建ActionChains对象  
actions = ActionChains(driver)  
  
# 构建动作链:移动鼠标到元素上并点击  
actions.move_to_element(element).click()  
  
# 执行动作链中的所有动作  
actions.perform()
  1. click(on_element=None):
    • 功能:模拟鼠标左键单击事件
    • 参数:on_element,要点击的元素,如果为None,则点击当前鼠标所在位置,下同
  2. click_and_hold(on_element=None):
    • 功能:模拟按下鼠标左键并保持不放
  3. context_click(on_element=None):
    • 功能:模拟鼠标右键点击事件,通常用于弹出上下文菜单
  4. double_click(on_element=None):
    • 功能:模拟鼠标左键双击事件
  5. drag_and_drop(source, target):
    • 功能:模拟鼠标拖拽操作,从源元素拖拽到目标元素。
    • 参数:source:拖拽操作的起始元素;target:拖拽操作的目标元素
  6. drag_and_drop_by_offset(source, xoffset, yoffset):
    • 功能:模拟鼠标拖拽操作,从源元素开始,拖拽到指定的坐标偏移量
    • 参数:source:拖拽操作的起始元素;xoffset:横向偏移量;yoffset:纵向偏移量
  7. key_down(value, element=None):
    • 功能:模拟按下键盘上的某个键
    • 参数:value:要按下的键的字符或键值;element:可选参数,要在哪个元素上执行按键操作
  8. key_up(value, element=None):
    • 功能:模拟松开键盘上的某个键
    • 参数:value:要松开的键的字符或键值;element:可选参数,要在哪个元素上执行按键操作
  9. move_by_offset(xoffset, yoffset):
    • 功能:将鼠标指针从当前位置移动指定的偏移量
    • 参数:xoffset:横向偏移量;yoffset:纵向偏移量
  10. move_to_element(element):
    • 功能:将鼠标指针移动到指定的元素上
    • 参数:element:要移动到的元素
  11. pause(seconds):
    • 功能:暂停所有输入,持续时间以秒为单位。
    • 参数:seconds:暂停的时间(单位秒)
  12. perform():
    • 功能:执行所有操作,以便鼠标和键盘操作生效
  13. reset_actions():
    • 功能:结束已经存在的操作并重置
  14. release(on_element=None):
    • 功能:在某个元素位置松开鼠标左键。
    • 参数:on_element:要在其上松开的元素;如果为 None,则在当前鼠标所在位置松开

键盘模拟操作

  1. send_keys(*keys_to_send):
    • 功能:发送某个键或者输入文本到当前焦点的元素
    • 参数:*keys_to_send:要发送的键或文本,可以是一个或多个
  2. send_keys_to_element(element, *keys_to_send):
    • 功能:发送某个键到指定元素。
    • 参数:
      • element:要发送的目标元素
      • *keys_to_send:要发送的键或文本
        Keys类基本满足对键盘基本操作的需求
#导入Keys类,该类包含所有用于模拟键盘操作的常量
from selenium.webdriver import Keys  

driver = webdriver.Edge()  
driver.get("http://127.0.0.1:5500/webstorm/login.html")  

#使用变量来存储获取到的元素,简洁后续代码
username = driver.find_element(By.ID, "username")  
username.send_keys("root")  
sleep(1)  
#模拟ctrl+a
username.send_keys(Keys.CONTROL, 'A')  
sleep(2)
  • Keys.BACK_SPACE:退格键
  • Keys.TAB:Tab键
  • Keys.ENTER:回车键
  • Keys.SHIFT:Shift键
  • Keys.CONTROL:Ctrl键
  • Keys.ALT:Alt键
  • Keys.ESCAPE:Esc键
  • Keys.PAGE_DOWN:Page Down键
  • Keys.PAGE_UP:Page Up键
  • Keys.END:End键
  • Keys.HOME:Home键
  • Keys.LEFT:左箭头键
  • Keys.UP:上箭头键
  • Keys.RIGHT:右箭头键
  • Keys.DOWN:下箭头键
  • Keys.DELETE:Delete键
  • Keys.INSERT:Insert键
  • Keys.F1 到 Keys.F12:F1到F12功能键
  • Keys.META:Meta键(在某些键盘上等同于Command键或Windows键)
  • Keys.ARROW_DOWN、Keys.ARROW_UP、Keys.ARROW_LEFT、Keys.ARROW_RIGHT:箭头键的另一种表示

获取验证信息

driver = webdriver.Edge()  
driver.get("http://www.baidu.com")  
print('Before login================')  
# 打印当前页面title  
title = driver.title  
print(title)  
# 打印当前页面URL 
now_url = driver.current_url  
print(now_url)

还有一个text属性获取标签对之间的文本信息

设置元素等待

当浏览器在加载页面时,页面上的元素可能不是同时被加载完成的,因此需要设置元素等待

显式等待
前面使用过,使Webdriver等待某个条件成立时继续执行,否则在达到最大时长时抛出超时异常
WebDriverWait类是由WebDirver提供的等待方法。在设置时间内,默认每隔一段时间检测一次当前页面元素是否存在,如果超过设置时间检测不到则抛出异常

WebDriverWait(driver, timeout, poll_frequency=0.5, ignored_exceptions=None)
  • driver :浏览器驱动
  • timeout :最长超时时间,默认以秒为单位
  • poll_frequency :检测的间隔(步长)时间,默认为0.5S
  • ignored_exceptions :超时后的异常信息,默认情况下抛NoSuchElementException异常

WebDriverWait()一般由until()或until_not()方法配合使用

until(method, message='')

调用该方法提供的驱动程序作为一个参数,直到返回值为True

until_not(method, message=’ ’)

调用该方法提供的驱动程序作为一个参数,直到返回值为False

from time import sleep  
from selenium import webdriver  
from selenium.webdriver import Keys  
from selenium.webdriver.common.by import By  
from selenium.webdriver.support.ui import WebDriverWait  
from selenium.webdriver.support import expected_conditions as EC  
  
driver = webdriver.Edge()  
driver.get("http://www.baidu.com")
#等待直到元素出现,最多等待5秒,每0.5秒检查1次
#ID为kw的元素是百度搜索框
#until方法告诉WebDriverWait要等待哪个条件成立,它会不断检查传入的条件,直到该条件成立或达到最大等待时间,这里是检查元素是否存在
element = WebDriverWait(driver, 5, 0.5).until(EC.presence_of_element_located((By.ID, "kw")))  
element.send_keys('selenium')
element.send_keys(Keys.RETURN)  
sleep(3)

sleep()是强制等待,会让程序暂停运行一段时间,如果代码量大,多个强制等待会影响整体的运行速度

隐式等待
使用driver.implicitly_wait(seconds)方法来设置隐式等待的时间
seconds是希望WebDriver等待的秒数
用于在整个WebDriver生命周期中,为所有查找元素的操作设置默认的超时时间。当WebDriver执行findElement或findElements方法时,如果页面上的元素尚未加载完成,WebDriver会等待指定的时间,直到元素出现或超时为止

隐式等待是全局性的,一旦设置,就会对之后所有的元素查找操作生效。这意味着,无论你在代码中查找多少个元素,WebDriver都会按照你设置的隐式等待时间进行等待

多表单和窗口切换

如果表单位于一个iframe或frame内部,需要首先切换到该iframe或frame,然后才能与其中的元素进行交互。可以通过switch_to.frame()方法实现

driver = webdriver.Edge()  
driver.get("https://email.163.com/")  
  
 #定位到表单frame  
fr = driver.find_element(By.TAG_NAME,'iframe')  
# 切换到表单  
driver.switch_to.frame(fr)  
# 定位账号输入框并输入  
usr_email = driver.find_element(By.NAME,'email')  
usr_email.send_keys('8888888888')  
sleep(2)
# 获取当前窗口句柄
current_window_handle = driver.current_window_handle
# 获取所有窗口句柄  
window_handles = driver.window_handles
# 使用index选择窗口,这里为第一个窗口
driver.switch_to.window(window_handles[0])
#也可以使用窗口名
driver.switch_to_window("windowName")

页面元素属性删除

例如超链接的target属性是_blank,就会打开新的页面,如果不想弹出新窗口,就可以删除该属性

driver = webdriver.Edge()  
driver.get("https://www.icourse163.org/")  

delete = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.LINK_TEXT, "学校云")))  
#使用JavaScript删除该元素的target属性
driver.execute_script("arguments[0].removeAttribute('target')",delete)  
#页面在本窗口打开
delete.click()  
sleep(1)

下拉滚动条

#滚动到页面底部
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")  
#滚动到页面顶部
driver.execute_script("window.scrollTo(0, 0);") 
#向下滚动500像素
driver.execute_script("window.scrollTo(0, 500);")  
#向上滚动200像素
driver.execute_script("window.scrollTo(200, 0);")

#第一个参数控制左右,正值向右,负值向左
#第二个参数控制上下,正值向下,负值向上
driver.execute_script("window.scrollBy(100, -100)")

from selenium.webdriver import ActionChains, Keys
ActionChains(self.d).send_keys(Keys.ARROW_DOWN).send_keys(Keys.ARROW_DOWN).perform()
#执行5次向下滚动操作
i = 5  
for _ in range(i):  
    actions.send_keys(Keys.ARROW_DOWN).perform()

下拉框处理方法

from selenium.webdriver.support.select import Select

select=driver.find_element()#获取到select元素
Select(select).方法()
  • select_by_index():通过索引定位
  • select_by_value():通过value值定位
  • select_by_visible_text():通过文本值定位
  • deselect_all():取消所有选项
  • deselect_by_{index|value|visible_text}():取消对应XX选项

警告窗处理

  • alert(message)方法用于显示带有一条指定消息和一个OK按钮的警告框。

  • confirm(message)方法用于显示一个带有指定消息和OK及取消按钮的对话框。如果用户点击确定按钮,则confirm()返回true。如果点击取消按钮,则confirm()返回false

  • prompt(text,defaultText)方法用于显示可提示用户进行输入的对话框。如果用户单击提示框的取消按钮,则返回null。如果用户单击确认按钮,则返回输入字段当前显示的文本

  • alertObject.text:获取提示的文本值

  • alertObject.accept():点击『确认』按钮

  • alertObject.dismiss():点击『取消』或者叉掉对话框

  • alertObject.send_keys(message):输入文本,仅适用于prompt方法

driver = webdriver.Edge()  
url = "http://127.0.0.1:5500/webstorm/alert.html"  
driver.get(url)  
  
try:  
    # alert提示框  
    button = driver.find_element(By.ID, "alertButton")  
    button.click()  
    # 返回代表该警告框的对象  
    alertObject = driver.switch_to.alert  
    alertObject.accept()# 点击确认按钮  
    sleep(1)  
  
    driver.find_element(By.ID, "alertButton").click()  
    sleep(1)  
    alertObject.dismiss()# 点击取消按钮  
except:  
    print("try1error")  
  
try:  
    # confirm提示框  
    button = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "confirmButton")))  
    button.click()  
    confirmObject = driver.switch_to.alert  
    print("confirm提示框信息:" + confirmObject.text)  # 打印提示信息  
  
    confirmObject.accept()  
    # 根据前端js代码逻辑,当点击确定按钮后会再弹出一个提示框,因此再次点击确定  
    confirmObject.accept()  
    sleep(1)  
  
    button.click()  
    sleep(1)  
    confirmObject.dismiss()  
    confirmObject.accept()  
    #受不了了,尼玛这里少了一个确认测半天,又不报错,我要不写个try捕获异常都不知道在哪里找错误
  
except:  
    print("try2error")  
  
try:  
    #prompt提示框  
    button = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "promptButton")))  
    button.click()  
    promptObject = driver.switch_to.alert  
    promptObject.accept()  
    promptObject.accept()# 需要两次确认  
    sleep(1)  
  
    button.click()  
    promptObject.dismiss()  
    promptObject.accept()# 确认未输入值  
    sleep(1)  
  
  
    button.click()  
    promptObject.send_keys("Test")  
    promptObject.accept()  
    # 注意语句先后顺序,两次确定关闭就无法获取其值  
    print("prompt提示框信息:" + promptObject.text)  
    sleep(1)  
    promptObject.accept()  
except:  
    print("try3error")  
finally:  
    sleep(1)
    driver.quit()

文件上传

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="content-type" content="text/html;charset=utf- 8" />

    <title>upload_file</title>
    <link href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min. css" rel="stylesheet" />
</head>

<body>
    <div class="row-fluid">
        <div class="span6 well">
            <h3>upload_file</h3> <input type="file" name="file" />
        </div>
    </div>
</body>

<script src="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.j s"></script>
</html>

对于通过input标签实现的上传功能,可以将其看作是一个输入框,可通过send_keys()指定本地文件路径的方式实现文件上传

upload = WebDriverWait(driver, 5, 0.5).until(EC.presence_of_element_located((By.NAME, "file")))  
upload.send_keys(r"D:/software/OCR/Umi-OCR/asset/icon/exit24.ico")

操作cookie

driver.get("http://www.youdao.com")  
  
# 向cookie的name和value中添加会话信息  
driver.add_cookie({ 'name':'key111111', 'value':'value222222' })  
all_cookie = driver.get_cookies()  
# cookie的信息打印  
for cookie in all_cookie:  
    # 分别替换%s  
    print("cookie Name:%s -> cookie Value:%s" % (cookie['name'], cookie['value']))  
  
# 删除cookie  
driver.delete_cookie('key111111')  
all_cookie = driver.get_cookies()  
print()  
for cookie in all_cookie:  
    # 分别替换%s  
    print("cookie Name:%s -> cookie Value:%s" % (cookie['name'], cookie['value']))

还有删除所有cookie的delete_delete_all_cookies

窗口截屏

driver.get('http://www.baidu.com')  
driver.find_element(By.ID,'kw').send_keys('selenium')  
driver.find_element(By.ID,'su').click()  
sleep(1)  
# 截取当前窗口,并指定截图图片的保存位置  
driver.get_screenshot_as_file("C:/aaa.png")

选项操作

from selenium.webdriver.chrome.options import Options

o=Options()
o.add_argument('--headless')#无界面浏览
c=webdriver.Chrome(chrome_options=o)
o.set_headless()          #设置启动无界面化
o.add_argument('--window-size=600,600') #设置窗口大小
o.add_argument('--incognito') #无痕模式
o.add_argument('user-agent="XXXX"') #添加请求头
o.add_argument("--proxy-server=http://200.130.123.43:3456")#代理服务器访问
o.add_experimental_option('excludeSwitches', ['enable-automation'])#开发者模式
o.add_experimental_option("prefs",{"profile.managed_default_content_settings.images": 2})#禁止加载图片
o.add_experimental_option('prefs',
{'profile.default_content_setting_values':{'notifications':2}}) #禁用浏览器弹窗
o.add_argument('blink-settings=imagesEnabled=false')  #禁止加载图片
o.add_argument('lang=zh_CN.UTF-8') #设置默认编码为utf-8
o.add_extension(create_proxyauth_extension(
           proxy_host='host',
           proxy_port='port',
           proxy_username="username",
           proxy_password="password"
       ))# 设置有账号密码的代理
o.add_argument('--disable-gpu')  # 这个属性可以规避谷歌的部分bug
o.add_argument('--disable-javascript')  # 禁用javascript
o.add_argument('--hide-scrollbars')  # 隐藏滚动条

EC判断

from selenium.webdriver.support import expected_conditions as EC
  1. EC.title_contains(title):
    • 功能:判断页面标题是否包含给定的字符串
WebDriverWait(driver, 10).until(EC.title_contains("Python"))
  1. EC.presence_of_element_located(locator):
    • 功能:判断某个元素是否加载到 DOM 树中;该元素不一定可见
element = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "element_id")))
  1. EC.url_contains(url_substring):
    • 功能:判断当前 URL 是否包含给定的字符串
WebDriverWait(driver, 10).until(EC.url_contains("python.org"))
  1. EC.url_matches(url):
    • 功能:完全匹配URL
WebDriverWait(driver, 10).until(EC.url_matches("http://www.python.org"))
  1. EC.url_to_be(url):
    • 功能:精确匹配当前URL
WebDriverWait(driver, 10).until(EC.url_to_be("http://www.python.org"))
  1. EC.url_changes(original_url):
    • 功能:检查 URL 是否发生变化
original_url = driver.current_url
WebDriverWait(driver, 10).until(EC.url_changes(original_url))
  1. EC.visibility_of_element_located(locator):
    • 功能:判断某个元素是否可见,元素必须是非隐藏的
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "element_id")))
  1. EC.visibility_of(element):
    • 功能:判断传入的元素是否可见
element = driver.find_element(By.ID, "element_id")
WebDriverWait(driver, 10).until(EC.visibility_of(element))
  1. EC.presence_of_all_elements_located(locator):
    • 功能:判断是否至少有一个元素存在于 DOM 树中
elements = WebDriverWait(driver, 10).until(EC.presence_of_all_elements_located((By.CLASS_NAME, "class_name")))
  1. EC.text_to_be_present_in_element(locator, text):
    • 功能:判断元素中的文本是否包含预期的字符串

WebDriverWait(driver, 10).until(EC.text_to_be_present_in_element((By.ID, "element_id"), "expected text"))


11. `EC.text_to_be_present_in_element_value(locator, value)`:
    - 功能:判断元素的 value 属性是否包含预期的字符串
        ```python
 WebDriverWait(driver, 10).until(EC.text_to_be_present_in_element_value((By.ID, "input_id"), "expected value"))
        ```

12. `EC.frame_to_be_available_and_switch_to_it(locator)`:
    - 功能:判断该 frame 是否可以切换进去
        ```python
WebDriverWait(driver, 10).until(EC.frame_to_be_available_and_switch_to_it((By.ID, "frame_id")))
  1. EC.invisibility_of_element_located(locator):
    • 功能:判断某个元素是否不存在于 DOM 树或不可见

WebDriverWait(driver, 10).until(EC.invisibility_of_element_located((By.ID, "element_id")))

14. `EC.element_to_be_clickable(locator)`:
    - 功能:判断某个元素是否可见并且可点击
        ```python
element = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.ID, "element_id")))
  1. EC.staleness_of(element):
    • 功能:等待某个元素从 DOM 树中移除

WebDriverWait(driver, 10).until(EC.staleness_of(driver.find_element(By.ID, "element_id")))

16. `EC.element_to_be_selected(element)`:
    - 功能:判断某个元素是否被选中,通常用于下拉列表
        ```python
WebDriverWait(driver, 10).until(EC.element_to_be_selected(driver.find_element(By.ID, "select_id")))
  1. EC.element_located_to_be_selected(locator):
    • 功能:判断元素的选中状态是否符合预期

WebDriverWait(driver, 10).until(EC.element_located_to_be_selected((By.ID, "select_id")))

18. `EC.element_selection_state_to_be(element, state)`:
    - 功能:判断某个元素的选中状态是否符合预期
        ```python
        WebDriverWait(driver, 10).until(EC.element_selection_state_to_be(driver.find_element(By.ID, "select_id"), True))
        ```
19. `EC.element_located_selection_state_to_be(locator, state)`:
    - 功能:判断定位到的元素的选中状态是否符合预期。
        ```python
        WebDriverWait(driver, 10).until(EC.element_located_selection_state_to_be((By.ID, "select_id"), True))
        ```
20. `EC.alert_is_present()`:
    - **功能**:判断页面上是否存在 alert。
        ```python
WebDriverWait(driver, 10).until(EC.alert_is_present())

unittest框架

import unittest  
  
class BasicTestCase(unittest.TestCase):  # 设置基础测试类名,继承库中测试用例的属性  
    # setUp()和tearDown()是每个测试用例进行时都会执行的测试方法,前者为起始,后者为结束  
    # 程序执行流程:setUp()-test1()-tearDown()---setUp()-test2()-tearDown()---  
    def setUp(self):  # 每一个测试用例都会执行的"起始方法"  
        print("setUp")  
  
    def tearDown(self):  # 每一个测试用例都会执行的"结束方法"  
        print("tearDown")  
  
    def test1(self):  # 设置测试用例1,命名为test+xxx,会按照test后的阿拉伯数字顺序执行,testdemo也执行,带test都会执行  
        print("test1")  
  
    def test2(self):  # 设置测试用例2  
        print("test2")  
  
if __name__ == '__main__':  # 设定条件执行unittest的主函数  
    unittest.main()  # 调用主函数进行多个测试用例测试

"""  
执行结果:  
setUp  
test1  
tearDown  
setUp  
test2  
tearDown  
"""

特殊类方法setUpClass(),tearDownClass(),在所有测试用例前后执行

    @classmethod  # 定义类方法
    def setUpClass(cls):  # 覆盖父类的类方法
        pass

    @classmethod  # 定义类方法
    def tearDownClass(cls):  # 覆盖父类的类方法
        pass

定义类属性,普通方法访问类属性需要通过类名访问,例如test1()中想要获取guide需要通过语句BasicTestCase.guide直接访问类属性

  • 如果实例没有相应属性,类属性有,则Python自动访问类属性替代
import unittest  
class BasicTestCase(unittest.TestCase):  
  
    @classmethod  
    def setUpClass(cls):  
        cls.guide = 'yu'  # 在类方法下,定义类属性 cls.guide  
    def test1(self):  # 设置测试用例1  
        guide = BasicTestCase.guide  # 通过类名访问类属性  
        print(f"Guide from class: {guide}")  # 输出类属性的值  
  
    def test2(self):  # 设置测试用例2  
        # 也可以在其他测试用例中访问类属性  
        guide = BasicTestCase.guide  
        print(f"Guide from class in test2: {guide}")  
  
if __name__ == '__main__':  # 设定条件执行unittest的主函数  
    unittest.main()  # 调用主函数进行多个测试用例测试

"""
输出结果:
Guide from class: yu
Guide from class in test2: yu
"""

在Unittest套件中,全局实例属性可以在setUp,tearDown中设置

class BasicTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.guide = 'yu'  # 在类方法下,定义类属性cls.guide = 'yu'
        pass

    def setUp(self):
        self.guide = 'ba'   # 在setUp()方法下,定义 全局实例属性self.guide = 'ba'

    def test1(self):
        guide = self.guide  # 3.在这段话中,这句话也获取guide = 'ba',因为实例在setUp中定义全局实例属性self.guide = 'ba'

if __name__ == '__main__':  # 设定条件执行unittest的主函数
    unittest.main()   
  • 普通方法(test1)只可定义"当局"实例属性,生命周期为本方法内,无法制造依赖关系
class BasicTestCase(unittest.TestCase):
    def setUp(self):
        self.guide = 'ba'   # 在setUp()方法下,定义"全局"实例属性self.guide = 'ba'

    def test1(self):
        guide = 'shi'  # 在test1中定义"当局"实例属性guide = 'shi'
        print(guide)   # 这里拿到的guide = 'shi'

    def test2(self):
        guide = self.guide
        print(guide)   # 这里拿到的guide = 'ba',而不是'shi',说明普通方法中的实例变量生命周期仅限"当局",无法互相依赖

if __name__ == '__main__':  # 设定条件执行unittest的主函数
    unittest.main()  
  • @unittest.skip('跳过的原因')
  • @unittest.skipIf('跳过条件', '跳过的原因')
  • @unittest.skipUnless('不跳过的条件', '不跳过的原因')
    下列测试仅执行test3,因为test1跳过、test2满足跳过条件,test3满足不跳过条件
class BasicTestCase(unittest.TestCase):  
  
    @unittest.skip('因为我想跳过所以跳过')  # 直接跳过  
    def test1(self):  
        print('执行test1')  
  
    @unittest.skipIf(888 < 999, '因为888比999小,所以跳过')  # 条件性跳过  
    def test2(self):  
        print('执行test2')  
  
    @unittest.skipUnless('你真厉害', '因为你真厉害,所以不跳过')  
    def test3(self):  
        print('执行test3')  
  
if __name__ == '__main__':  # 设定执行unittest的主函数  
    unittest.main()

条件跳过参数的导入必须在类下定义
因为@unittest.skipIf()语句执行优先级大于所有def,即无论是setUp()、setUpClass()还是test2()都在其之后执行,所以定义必须在类下

class BasicTestCase(unittest.TestCase):
     number = '888'
     @unittest.skipIf(number < '999', '因为number比999小,所以跳过')
     def test2(self):     # 不会被执行,因为888满足跳过的条件
         print('执行test2')   

测试用例之间参数联动判定跳过的方法
语句编码+类属性变量->类属性变量通常用列表、字典等,解决多条件依赖时便捷

class BasicTestCase(unittest.TestCase):
    judge = {'first': 0}

    def test2(self):
        print('执行test2')
        BasicTestCase.judge['first'] = 888    # 更改下个测试所要依赖的变量值

    def test3(self):
        if BasicTestCase.judge['first'] == 888:   # 设定判定条件看是否需要跳过
            return    # 若满足条件则直接return结束,此test下的之后的语句均不执行
        # print('执行test3')  # 此段代码中这句话加与不加都并不会被执行,测试通过但执行语句并没有执行,因为根据依赖的条件test3已经结束

if __name__ == '__main__':  # 设定条件执行unittest的主函数
    unittest.main()

断言

  1. assertEqual(a, b)和assertNotEqual(a, b):检查 a 是否等于或不等于 b
  2. assertTrue(expr)和assertFalse(expr):expr是否为真或假
  3. assertIn(member, container)和assertNotIn(member, container):检查 member 是否在或不在container 中
  4. assertIsNone(expr)和assertIsNotNone(expr):检查expr是否是或不是None

数据驱动测试ddt

遇到执行步骤相同,只需要改变入口参数的测试时,使用ddt可以简化代码

import unittest  
import ddt  
  
# 未使用数据驱动测试的代码:  
class BasicTestCase(unittest.TestCase):  
    def test1(self):  
        num1 = 666  # 使用静态值  
        print('number from test1:', num1)  
  
    def test2(self):  
        num2 = 777  # 使用静态值  
        print('number from test2:', num2)  
  
    def test3(self):  
        num3 = 888  # 使用静态值  
        print('number from test3:', num3)  
  
  
# 使用数据驱动测试的代码,执行效果与上文代码相同  
@ddt.ddt  
class BasicTCase(unittest.TestCase):  
    @ddt.data('666', '777', '888')  
    def test(self, num):  
        print('数据驱动的number:', num)

单一参数的数据驱动测试

  • 步骤:导包—>设置@ddt装饰器—>写入参数—>形参传递—>调用
@ddt.ddt    # 设置@ddt装饰器
class BasicTestCase(unittest.TestCase):
    @ddt.data('666', '777', '888')    # 设置@data装饰器,并将传入参数写进括号
    def test(self, num):     # test入口设置形参
        print('数据驱动的number:', num)
# 程序会执行三次测试,入口参数分别为666、777、888

多参数的数据驱动测试(一个测试参数中含多个元素)

  • 导包—>设置@ddt装饰器—>设置@unpack解包—>写入参数—>形参传递—>调用
@ddt.ddt  
class BasicTestCase(unittest.TestCase):
    @ddt.data(['张三', '18'], ['李四', '19'])  # 设置@data装饰器,并将同一组参数写进中括号[]
    @ddt.unpack  # 设置@unpack装饰器顺序解包,缺少解包则相当于name = ['张三', '18']
    def test(self, name, age):
        print('姓名:', name, '年龄:', age)
# 程序会执行两次测试,入口参数分别为['张三', '18'],['李四', '19']

文件驱动

# 单一参数txt文件
# 新建num文件,txt格式,按行存储777,888,999
# num文件内容(参数列表):
# 777
# 888
# 999
# 编辑阅读数据文件的函数
# 记住读取文件一定要设置编码方式,否则读取的汉字可能出现乱码!!!!!!
def read_num():
    lis = []    # 以列表形式存储数据,以便传入@data区域
    with open('num', 'r', encoding='utf-8') as file:    # 以只读'r',编码方式为'utf-8'的方式,打开文件'num',并命名为file
        for line in file.readlines():   # 循环按行读取文件的每一行
            lis.append(line.strip('\n'))  # 每读完一行将此行数据加入列表元素,记得元素要删除'\n'换行符!!!
        return lis    # 将列表返回,作为@data接收的内容
@ddt
class BasicTestCase(unittest.TestCase):
    @data(*read_num())  # 入口参数设定为read_num(),因为返回值是列表,所以加*表示逐个读取列表元素
    def test(self, num):
        print('数据驱动的number:', num)
# 多参数txt文件
# dict文件内容(参数列表)(按行存储):
# 张三,18
# 李四,19
# 王五,20
def read_dict():
    lis = []  # 以列表形式存储数据,以便传入@data区域
    with open('dict', 'r', encoding='utf-8') as file:  # 以只读'r',编码方式为'utf-8'的方式,打开文件'num',并命名为file
        for line in file.readlines():  # 循环按行读取文件的每一行
            lis.append(line.strip('\n').split(','))  # 删除换行符后,列表为['张三,18', '李四,19', '王五,20']
            # 根据,分割后,列表为[['张三', '18'], ['李四', '19'], ['王五', '20']]
        return lis  # 将列表返回,作为@data接收的内容
@ddt
class BasicTestCase(unittest.TestCase):
    @data(*read_dict())  # 加*表示逐个读取列表元素,Python中可变参数,*表示逐个读取列表元素,列表为[['张三', '18'], ['李四', '19'], ['王五', '20']]
    @unpack  # 通过unpack解包,逐个传参,缺少这句会将['张三', '18']传给name,从而导致age为空
    def test(self, name, age):  # 设置两个接收参数的形参
        print('姓名为:', name, '年龄为:', age)

csv文件

"""
1,John Doe,john@example.com  
2,Jane Smith,jane@example.com  
3,Bob Johnson,bob@example.com
"""
import csv  
  
# 定义 CSV 文件的路径  
csv_file_path = 'data.csv'  
  
# 打开 CSV 文件进行读取  
with open(csv_file_path, mode='r', newline='') as csv_file:  
	data=[]
    # 创建 CSV 读取器  
    csv_reader = csv.reader(csv_file)  # 使用 DictReader 以字典形式读取数据 
    # 读取每一行并打印  
    for row in csv_reader:  
        print(row)  # 打印每一行的内容  
		data.append(row)
        # 如果需要访问特定的列,可以使用列名  
        print(f"ID: {row['id']}, Name: {row['name']}, Email: {row['email']}")

性能测试

术语定义

  1. 软件性能是软件的一种非功能特性,它关注的不是软件是否能够完成特定的功能,而是在完成该功能时展示出来的及时性、稳定性、可靠性、处理能力等。
  2. 性能测试是通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试。
  3. 响应时间分为呈现时间和服务端响应时间两个部分。
  4. 关注某个业务的响应时间,可以将该业务定义为事务。配置测试方法通过对被测系统软硬件环境的调整,了解各种不同环境对系统性能影响的程度,从而找到系统各项资源的最优分配原则

Virtual User Generator

每个Vuser脚本至少包含一个vuser_init,一个或多个Action,一个vuser_end
![[Pasted image 20241109112218.png]]

多次迭代运行Vuser脚本时,只有Action部分脚本可以重复执行

![[Pasted image 20241109112631.png]]

VuGen录制原理
![[Pasted image 20241109112736.png]]

VuGen函数

集合点
lr_rendezvous 确保所有虚拟用户在执行下一步操作之前都到达了这个集合点。这可以用来模拟多个用户同时对系统进行操作的场景,如同时登录、同时提交订单等

检查点

  • web_reg_find:检查文本
  • web_image_check:检查图片

参数关联

  • lr_eval_string("{param_name}"): 解析参数名并返回其值
  • lr_save_string("value", "param_name"):保存字符串值到参数中
  • lr_save_int(int_value, "param_name")
  • lr_save_double(double_value, "param_name")
  • lr_save_substring("Hello, World!", 7, 5, "greeting"):从7位提取5个字符:World,保存到greeting参数
  • atoi(lr_eval_string("variable")):将字符串转为整数
  • atof(lr_eval_string("param_name")):将字符串转为浮点数

JMeter

非GUI模式运行
cd到bin目录下,输入:
jmeter -n -t jmx脚本路径 -l log日志路径 -e -o 测试报表路径

作用域
取样器:不与其他元件相互作用,没有作用域
逻辑控制器:只对其子节点中的取样器和逻辑控制器起作用
其他元件:

  • 如果是某个取样器的子节点,则该元件只对其父节点起作用
  • 如果其父节点不是取样器,则其作用域是该元件父节点下的其他所有后代节点

接口测试

术语定义

  1. 接口测试概念:是测试系统组件间接口的一种测试方法。
  2. 接口测试的重点:检查数据的交换,数据传递的正确性,以及接口间的逻辑依赖关系。
  3. 接口测试的意义:在软件开发的同时实现并行测试,减少页面层测试的深度,缩短整个项目的测试周期。
  4. 接口测试能发现哪些问题:可以发现很多在页面上操作发现不了的Bug、检查系统的异常处理能力、检查系统的安全性、稳定性、可以修改请求参数,突破前端页面输入限制。
1. 接口:指系统或组件之间的交互点,通过这些交互点可以实现数据的交互(数据传递交互的通道)
2. 接口测试:对系统或组件之间的接口进行测试,校验传递的数据正确性和逻辑依赖关系的正确性

获取和设置不同级别变量

如果存在同名的变量,Postman会 按照优先级顺序解析这些变量。优先级从高到低依次为:脚本变量、集合变量、环境变量和全局变量

本地变量也称为请求级别的变量,只在当前请求有效

  • pm.variables.has("变量名"):检查是否存在指定的变量,返回boolean
  • pm.variables.get("变量名"):获取变量,如果变量不存在则返回undefined
  • pm.variables.set("变量名", 任意类型的变量值):设置指定变量的值
  • pm.variables.replaceIn("字符串"):在给定的字符串中替换所有动态变量的占位符
// 检查是否存在变量 endpoint 
if (pm.variables.has("endpoint")) { 
// 获取变量 endpoint 的值
var endpointValue = pm.variables.get("endpoint"); 
console.log("Endpoint: " + endpointValue); 
// 使用变量值构建完整的 URL
var url = pm.variables.replaceIn("http://example.com/api/{{endpoint}}"); 
console.log("URL: " + url); 
// 设置一个新的变量completeUrl
pm.variables.set("completeUrl", url);
} 
else { 
console.log("变量 endpoint 不存在");
}

集合变量在整个集合中使用,用于同一个集合内的请求之间共享数据

  • pm.collectionVariables.has("变量名")
  • pm.collectionVariables.get("变量名")
  • pm.collectionVariables.set("变量名", 任意类型的变量值)
  • pm.collectionVariables.unset("变量名")
  • pm.collectionVariables.clear():清除所有集合变量
  • pm.collectionVariables.replaceIn(”变量名")

环境变量在整个环境中有效,可以跨多个请求使用

  • pm.environment.has("变量名"):检查是否存在指定的环境变量
  • pm.environment.get("变量名"):获取指定环境变量的值
  • pm.environment.set("变量名", 任意类型的变量值):设置指定环境变量的值
  • pm.environment.unset("变量名"):删除指定的环境变量
  • pm.environment.clear()
  • pm.environment.replaceIn("变量名")

全局变量在整个Postman应用中有效,可以跨多个环境和请求使用

  • pm.globals.has("变量名"):检查是否存在指定的全局变量
  • pm.globals.get("变量名"):获取指定全局变量的值
  • pm.globals.set("变量名", 任意类型的变量值):设置指定全局变量的值
  • pm.globals.unset("变量名"):删除指定的全局变量
  • pm.globals.clear()
  • pm.globals.replaceIn("变量名")

迭代变量是一种特殊的变量,用于在数据驱动测试(Data-Driven Testing)中存储和使用数据。迭代变量在每次运行集合时都会从外部数据源(如CSV文件或JSON文件)中读取数据,并在每次迭代中使用这些数据

  • pm.iterationData .has("变量名")
  • pm.iterationData .get(”变量名“)
  • pm.iterationData.unset(”变量名“)
  • pm.iterationData .toJSON(”变量名“):将 iterationData 对象转换为 JSON 格式

操作请求数据

  • pm.request.url:当前请求URL
  • pm.request.headers:当前请求的Headers
  • pm.request.meth:当前请求的方法
  • pm.request.body:当前请求的Body
pm.request.url = "http://example.com/api/new-endpoint";
pm.request.method = "PUT";
pm.request.body = { mode: "raw", raw: JSON.stringify({ key: "new-value" }) };
//添加一个新的Header
pm.request.headers.add({ key: "Authorization", value: "Bearer your-token" });
//删除指定名称的header
pm.request.headers.remove("Authorization");

操作响应数据

  • pm.response.code:获取响应的HTTP状态码
  • pm.response.status:获取响应的HTTP状态信息
  • pm.response.headers:获取响应头的集合,可以访问特定的头信息
  • pm.response.responseTime:获取服务器响应请求所花费的毫秒数
  • pm.response.responseSize:获取响应体大小,字节为单位
  • pm.response.text():将响应体转为字符串返回
  • pm.response.json():将响应体转为json返回

断言

同步和异步测试

//测试检查响应是否有效
pm.test("response should be okay to process", function () {
  pm.response.to.not.be.error;
  pm.response.to.have.jsonBody('');
  pm.response.to.not.have.jsonBody('error');
});

//1.5秒后检查响应状态码是否为200
pm.test('async test', function (done) {
  setTimeout(() => {
    pm.expect(pm.response.code).to.equal(200);
    done();
  }, 1500);
});

输出变量值或者变量类型

console.log(pm.collectionVariables.get("name"));
console.log(pm.response.json().name);
console.log(typeof pm.response.json().id);
if (pm.response.json().id) {
  console.log("id was found!");
} else {
  console.log("no id ...");
  //do something else
}//for循环读取for(条件){语句;}

状态码检测

//pm.test.to.have方式
pm.test("Status code is 200", function () {
  pm.response.to.have.status(200);
});
//expect方式
pm.test("Status code is 200", () => {
  pm.expect(pm.response.code).to.eql(200);
});

多个断言作为单个测试的结果

pm.test("The response has all properties", () => {
    //parse the response JSON and test three properties
    const responseJson = pm.response.json();
    pm.expect(responseJson.type).to.eql('vip');
    pm.expect(responseJson.name).to.be.a('string');//检查是否是string类型
    pm.expect(responseJson.id).to.have.lengthOf(1);//检查是否是一个长度为1的数组
});

不同类型的返回结果解析

//获取返回的json数据
const responseJson = pm.response.json();
//将number转换为JSON格式
JSON.stringify(pm.response.code)
//将json数据转换为数组
JSON.parse(jsondata)
//获取xml数据
const responseJson = xml2Json(pm.response.text());
//获取csv数据
const parse = require('csv-parse/lib/sync');
const responseJson = parse(pm.response.text());
//获取html数据
const $ = cheerio.load(pm.response.text());
//output the html for testing
console.log($.html());

测试响应正文reponseBody中的特定值

pm.test("Person is Jane", () => {
  const responseJson = pm.response.json();
  pm.expect(responseJson.name).to.eql("Jane");
  pm.expect(responseJson.age).to.eql(23);
});//获取响应正文responseBodypm.response.text()

测试响应headers

//测试响应头是否存在
pm.test("Content-Type header is present", () => {
  pm.response.to.have.header("Content-Type");
});
//测试响应头的特定值
pm.test("Content-Type header is application/json", () => {
  pm.expect(pm.response.headers.get('Content-Type')).to.eql('application/json');
});
//获取响应头”Server“数据
 postman.getResponseHeader("Server")

功能测试

测试分类
按代码可见度划分:

  • 黑盒测试
    • 源代码不可见
    • UI功能可见
  • 灰盒测试
    • 部分源代码可见
    • UI功能不可见
  • 白盒测试
    • 源代码可见
    • UI功能不可见

等价类划分法

在所有测试数据中,具有某种共同特征的数据集合进行划分
分类:

  • 有效等价类:满足需求的数据集合
  • 无效等价类:不满足需求的数据集合
    适用场景
    需要大量数据测试输入,但无法穷举的地方
  • 输入框
  • 下拉列表
  • 单选/复选框

示例:
需求:

  1. 区号:空或三位数字
  2. 前缀码:非"0"非"1"开头的三位数字
  3. 后缀码:四位数字

定义有效等价和无效等价

参数 说明 有效 有效数据 无效 无效数据
区号 长度 空,3位 1. 空
2. 123
非3位 12
前缀码 长度 3位 234 非3位 23
后缀码 长度 4位 1234 非4位 123
区号 类型 数字 / 非数字 12A
前缀码 类型 数字 / 非数字 23A
后缀码 类型 数字 / 非数字 123A
区号 规则 / / / /
前缀码 规则 非0非1开头 / 1. 0开头
2. 1开头
1. 012
2. 123
后缀码 规则 / / / /

有效数据两条,无效数据8条

边界值分析法

选取==或>或<边界的值作为测试数据

  • 上点:边界上的点(等于)
  • 离点:距离上点最近的点(刚好大于/刚好小于)
  • 内点:范围内的点(区间范围内的数据)
posted on 2025-02-28 22:43  非衣居士  阅读(236)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3