|
环境配置
Linux相关
[[鸟哥Linux基础]]
[[Linux命令行与Shell脚本编程大全]]
>centos无网络,使用virtualBox的NAT连接解1:1. su切换到root2. cd到/etc/sysconfig/network-scripts/3. vi编辑ifcfg-enp0s3文件4. HWADDR=00:00:00:00(这个替换为MAC地址) ONBOOT=no改为yes,添加BOOTPROTO=dhcp5. 重启网络service network restart解2:1. vi /etc/resolv.conf2. 增加一行nameserver 后面是主机地址,这里是添加DNS服务器配置JDK
centos系统自带OpenJDK,如果需要安装其他版本,可能需要先卸载
[test@localhost ~]$ java -versionopenjdk 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/profileexport JAVA_HOME=jdk所在目录export JRE_HOME=$JAVA_HOME/jreexport 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的密码修改为123456alter user 'root'@'localhost' identified by '123456';//将用户名为root的用户的主机字段设置为%,表示从任何主机连接到MySQL服务器,而不仅仅是从localhostuse 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 --reloadmy.cnf文件
[mysqld]port=3306basedir=/opt/mysqldatadir=/opt/mysql/datasocket=/opt/mysql/mysql.sockcharacter-set-server=utf8symbolic-links=0bind_address=0.0.0.0[mysqld_safe]log-error=/opt/mysql/mariadb/log/mariadb.logpid-file=/opt/mysql/mariadb/run/mariadb.pid[client]socket=/opt/mysql/mysql.sockdefault-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.servicePython基础
输出
n=100 print("one:%d"%n) #整数dn=33.333 print("two:%f"%n) #浮点数fn="sss" print("three:%s"%n) #字符串sn = { 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("对不起,您输入的不是一个有效的年龄。请重新输入一个整数")类型转换
#转换为floatheight = 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,使用elifresults = 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() #移除并返回列表中指定位置的元素,未指定位置默认移除并返回最后一个元素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,则返回Noneitems() #返回包含字典中所有键值对的视图对象keys() #返回包含字典中所有键的视图对象values() #返回包含字典中所有值的视图对象pop(key,default) #移除并返回列表中指定位置的元素,未指定位置默认移除并返回最后一个元素,default是可选参数popitem() #随机移除字典中的一对键值对,并作为一个元组返回,如果字典为空,抛出KeyError异常setdefault(key,default) #如果键不存在则插入键并将值设置为default,如果键已经存在则返回其值。default的默认值为Noneupdate() #使用另一个字典的键值对更新该字典元组
元祖的元素不能被修改,元祖使用圆括号创建,逗号隔开,元组中只包含一个元素时,需要在元素后面添加逗号
元组与字符串类似,下标索引从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,函数2from 模块 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[]> 类型的数据集合,其中每个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: noneassertTure和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")); // 断言成功,因为数组按顺序包含这些元素<hr>自动化测试脚本设计
术语定义
- 自动化测试概念:自动化测试是把以人为驱动的测试行为转化为机器执行的一种过程。
- 自动化测试前提条件:需求变动不频繁、项目周期足够长、自动化测试脚本可重复使用。
- 自动化测试的流程:(1)制定测试计划、(2)分析测试需求、(3)设计测试用例、(4)搭建测试环境、(5)编写并执行测试脚本、(6)分析测试结果并记录Bug、(7)跟踪Bug并进行回归测试。
- 进行自动化测试的目的:随着国家计算机信息化的发展,软件都是需要快速迭代,像一些重复性的工作可以通过自动化来完成,从而提高工作的效率和准确性,达到快速迭代的目的
首先要导入selenium和安装浏览器驱动
from selenium import webdriverfrom 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()#跳转至指定urldriver.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"))) #在找到的用户名输入框中输入文本rootusername.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 ActionChainsdriver = 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+ausername.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, KeysActionChains(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 Selectselect=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 Optionso=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-8o.add_extension(create_proxyauth_extension( proxy_host='host', proxy_port='port', proxy_username="username", proxy_password="password" ))# 设置有账号密码的代理o.add_argument('--disable-gpu') # 这个属性可以规避谷歌的部分bugo.add_argument('--disable-javascript') # 禁用javascripto.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_urlWebDriverWait(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):
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 是否可以切换进去 ```pythonWebDriverWait(driver, 10).until(EC.frame_to_be_available_and_switch_to_it((By.ID, "frame_id")))
- 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)`: - 功能:判断某个元素是否可见并且可点击 ```pythonelement = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.ID, "element_id")))
- EC.staleness_of(element):
WebDriverWait(driver, 10).until(EC.staleness_of(driver.find_element(By.ID, "element_id")))
16. `EC.element_to_be_selected(element)`: - 功能:判断某个元素是否被选中,通常用于下拉列表 ```pythonWebDriverWait(driver, 10).until(EC.element_to_be_selected(driver.find_element(By.ID, "select_id")))
- 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。 ```pythonWebDriverWait(driver, 10).until(EC.alert_is_present())<hr>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: yuGuide 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接收的内容@ddtclass BasicTestCase(unittest.TestCase): @data(*read_num()) # 入口参数设定为read_num(),因为返回值是列表,所以加*表示逐个读取列表元素 def test(self, num): print('数据驱动的number:', num)# 多参数txt文件# dict文件内容(参数列表)(按行存储):# 张三,18# 李四,19# 王五,20def 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接收的内容@ddtclass 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); // 使用变量值构建完整的 URLvar url = pm.variables.replaceIn("http://example.com/api/{{endpoint}}"); console.log("URL: " + url); // 设置一个新的变量completeUrlpm.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" }) };//添加一个新的Headerpm.request.headers.add({ key: "Authorization", value: "Bearer your-token" });//删除指定名称的headerpm.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秒后检查响应状态码是否为200pm.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 testingconsole.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条
边界值分析法
选取==或>或<边界的值作为测试数据
- 上点:边界上的点(等于)
- 离点:距离上点最近的点(刚好大于/刚好小于)
- 内点:范围内的点(区间范围内的数据)
|
|