环境配置
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删除整个元组与字符串一样,元组之间可以使用+号和*号进行运算。这就意味着他们可以组合和复制,运算后会生成一个新的元组- #元组内置函数
- 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中的类型属于对象,变量没有类型[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尚未实现的方法SyntaxErrorPython 语法错误IndentationError缩进错误TabErrorTab 和空格混用SystemError一般的解释器系统错误TypeError对类型无效的操作ValueError传入无效的参数UnicodeErrorUnicode 相关的错误UnicodeDecodeErrorUnicode 解码时的错误UnicodeEncodeErrorUnicode 编码时错误UnicodeTranslateErrorUnicode 转换时错误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[]包含一组参数,这些参数将被用来构造测试类的实例。通常返回一个列表(如 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)设计测试用例、(4)搭建测试环境、(5)编写并执行测试脚本、(6)分析测试结果并记录Bug、(7)跟踪Bug并进行回归测试。
- 进行自动化测试的目的:随着国家计算机信息化的发展,软件都是需要快速迭代,像一些重复性的工作可以通过自动化来完成,从而提高工作的效率和准确性,达到快速迭代的目的
首先要导入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: 通过完整的链接文本来定位元素,通常用于标签,当需要进行页面跳转时可以使用
- 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()
复制代码
- click(on_element=None):
- 功能:模拟鼠标左键单击事件
- 参数:on_element,要点击的元素,如果为None,则点击当前鼠标所在位置,下同
- click_and_hold(on_element=None):
- context_click(on_element=None):
- 功能:模拟鼠标右键点击事件,通常用于弹出上下文菜单
- double_click(on_element=None):
- drag_and_drop(source, target):
- 功能:模拟鼠标拖拽操作,从源元素拖拽到目标元素。
- 参数:source:拖拽操作的起始元素;target:拖拽操作的目标元素
- drag_and_drop_by_offset(source, xoffset, yoffset):
- 功能:模拟鼠标拖拽操作,从源元素开始,拖拽到指定的坐标偏移量
- 参数:source:拖拽操作的起始元素;xoffset:横向偏移量;yoffset:纵向偏移量
- key_down(value, element=None):
- 功能:模拟按下键盘上的某个键
- 参数:value:要按下的键的字符或键值;element:可选参数,要在哪个元素上执行按键操作
- key_up(value, element=None):
- 功能:模拟松开键盘上的某个键
- 参数:value:要松开的键的字符或键值;element:可选参数,要在哪个元素上执行按键操作
- move_by_offset(xoffset, yoffset):
- 功能:将鼠标指针从当前位置移动指定的偏移量
- 参数:xoffset:横向偏移量;yoffset:纵向偏移量
- move_to_element(element):
- 功能:将鼠标指针移动到指定的元素上
- 参数:element:要移动到的元素
- pause(seconds):
- 功能:暂停所有输入,持续时间以秒为单位。
- 参数:seconds:暂停的时间(单位秒)
- perform():
- reset_actions():
- release(on_element=None):
- 功能:在某个元素位置松开鼠标左键。
- 参数:on_element:要在其上松开的元素;如果为 None,则在当前鼠标所在位置松开
键盘模拟操作
- send_keys(*keys_to_send):
- 功能:发送某个键或者输入文本到当前焦点的元素
- 参数:*keys_to_send:要发送的键或文本,可以是一个或多个
- 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>
-
-
- <h3>upload_file</h3> <input type="file" name="file" />
-
-
- </body>
- </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
复制代码
- EC.title_contains(title):
- WebDriverWait(driver, 10).until(EC.title_contains("Python"))
复制代码
- EC.presence_of_element_located(locator):
- 功能:判断某个元素是否加载到 DOM 树中;该元素不一定可见
- element = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "element_id")))
复制代码
- EC.url_contains(url_substring):
- WebDriverWait(driver, 10).until(EC.url_contains("python.org"))
复制代码- WebDriverWait(driver, 10).until(EC.url_matches("http://www.python.org"))
复制代码- WebDriverWait(driver, 10).until(EC.url_to_be("http://www.python.org"))
复制代码
- EC.url_changes(original_url):
- original_url = driver.current_url
- WebDriverWait(driver, 10).until(EC.url_changes(original_url))
复制代码
- EC.visibility_of_element_located(locator):
- WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "element_id")))
复制代码
- EC.visibility_of(element):
- element = driver.find_element(By.ID, "element_id")
- WebDriverWait(driver, 10).until(EC.visibility_of(element))
复制代码
- EC.presence_of_all_elements_located(locator):
- elements = WebDriverWait(driver, 10).until(EC.presence_of_all_elements_located((By.CLASS_NAME, "class_name")))
复制代码
- EC.text_to_be_present_in_element(locator, text):
- 功能:判断元素中的文本是否包含预期的字符串
- [/code]
- [/list]
- [/list]WebDriverWait(driver, 10).until(EC.text_to_be_present_in_element((By.ID, "element_id"), "expected text"))
- [code]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")))
复制代码
- EC.invisibility_of_element_located(locator):
- 功能:判断某个元素是否不存在于 DOM 树或不可见
- [/code]
- [/list]
- [/list]WebDriverWait(driver, 10).until(EC.invisibility_of_element_located((By.ID, "element_id")))
- [code]14. `EC.element_to_be_clickable(locator)`:
- - 功能:判断某个元素是否可见并且可点击
- ```python
- element = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.ID, "element_id")))
复制代码
- EC.staleness_of(element):
- 功能:等待某个元素从 DOM 树中移除
- [/code]
- [/list]
- [/list]WebDriverWait(driver, 10).until(EC.staleness_of(driver.find_element(By.ID, "element_id")))
- [code]16. `EC.element_to_be_selected(element)`:
- - 功能:判断某个元素是否被选中,通常用于下拉列表
- ```python
- WebDriverWait(driver, 10).until(EC.element_to_be_selected(driver.find_element(By.ID, "select_id")))
复制代码
- EC.element_located_to_be_selected(locator):
- 功能:判断元素的选中状态是否符合预期
- [/code]
- [/list]
- [/list]WebDriverWait(driver, 10).until(EC.element_located_to_be_selected((By.ID, "select_id")))
- [code]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()
复制代码 断言
- assertEqual(a, b)和assertNotEqual(a, b):检查 a 是否等于或不等于 b
- assertTrue(expr)和assertFalse(expr):expr是否为真或假
- assertIn(member, container)和assertNotIn(member, container):检查 member 是否在或不在container 中
- 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']}")
复制代码 性能测试
术语定义
- 软件性能是软件的一种非功能特性,它关注的不是软件是否能够完成特定的功能,而是在完成该功能时展示出来的及时性、稳定性、可靠性、处理能力等。
- 性能测试是通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试。
- 响应时间分为呈现时间和服务端响应时间两个部分。
- 关注某个业务的响应时间,可以将该业务定义为事务。配置测试方法通过对被测系统软硬件环境的调整,了解各种不同环境对系统性能影响的程度,从而找到系统各项资源的最优分配原则
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 测试报表路径
作用域
取样器:不与其他元件相互作用,没有作用域
逻辑控制器:只对其子节点中的取样器和逻辑控制器起作用
其他元件:
- 如果是某个取样器的子节点,则该元件只对其父节点起作用
- 如果其父节点不是取样器,则其作用域是该元件父节点下的其他所有后代节点
接口测试
术语定义
- 接口测试概念:是测试系统组件间接口的一种测试方法。
- 接口测试的重点:检查数据的交换,数据传递的正确性,以及接口间的逻辑依赖关系。
- 接口测试的意义:在软件开发的同时实现并行测试,减少页面层测试的深度,缩短整个项目的测试周期。
- 接口测试能发现哪些问题:可以发现很多在页面上操作发现不了的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")
复制代码 功能测试
测试分类
按代码可见度划分:
等价类划分法
在所有测试数据中,具有某种共同特征的数据集合进行划分
分类:
- 有效等价类:满足需求的数据集合
- 无效等价类:不满足需求的数据集合
适用场景
需要大量数据测试输入,但无法穷举的地方
- 输入框
- 下拉列表
- 单选/复选框
示例:
需求:
- 区号:空或三位数字
- 前缀码:非"0"非"1"开头的三位数字
- 后缀码:四位数字
定义有效等价和无效等价
参数说明有效有效数据无效无效数据区号长度空,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条
边界值分析法
选取==或>或
|