环境配置
Linux相关
[[鸟哥Linux基础]]
[[Linux命令行与Shell脚本编程大全]]
>centos无网络,使用virtualBox的NAT连接 解1: 1. su切换到root 2. cd到/etc/sysconfig/network-scripts/ 3. vi编辑ifcfg-enp0s3文件 4. HWADDR=00:00:00:00(这个替换为MAC地址) ONBOOT=no改为yes,添加BOOTPROTO=dhcp 5. 重启网络service network restart 解2: 1. vi /etc/resolv.conf 2. 增加一行nameserver 后面是主机地址,这里是添加DNS服务器
配置JDK
centos系统自带OpenJDK,如果需要安装其他版本,可能需要先卸载
[test@localhost ~]$ java -version openjdk version "1.8.0_262" OpenJDK Runtime Environment (build 1.8.0_262-b10) OpenJDK 64-Bit Server VM (build 25.262-b10, mixed mode)
卸载OpenJDK
rpm -qa | grep java//查询相关java套件 //.noarch文件可以不用管 rpm -e --nodeps java文件
rpm安装
一般不需要手动配置环境变量,因为rpm
包安装过程中会自动将必要的路径添加到系统的环境变量中
# rpm包的安装命令 rpm -ivh 包全名 选项: -i(install) 安装 -v(verbose) 显示详细信息 -h(hash) 显示进度 --nodeps 不检测依赖性
tar.gz安装
# 解压gz压缩包 tar -zxvf 包全名 选项: -z: 通过gzip过滤归档文件,用于处理.gz压缩文件 -x: 提取文件 -v: 显示详细信息 -f: 指定归档文件的名称 # 创建文件夹 mkdir -p # 复制jdk到上一步创建的文件夹 cp -r # 编辑全局变量文件 vim /etc/profile export JAVA_HOME=jdk所在目录 export JRE_HOME=$JAVA_HOME/jre export PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin # 使配置文件生效 source /etc/profile
配置Mysql5.7
Mysql相关
[[MySQL基础]]
tar.gz安装
环境检查
// 检查 是否有 mysql 的进程 ps ajx | grep mysql // 检查 是否有 mariabd 的进程 ps ajx | grep mariabd //如果发现有进程在运行需要关闭进程 systemctl stop mysqld // 检查是否有安装包 rpm -qa | grep mysql //若有安装包出现,并且之前没有用过MySQL,那就将这些安装包删除 //批量化删除安装包 rpm -qa | grep mysql | xargs yum -y remove //检查是否有配置文件 ls /etc/my.cnf //删除配置文件 rm -rf /etc/my.cnf
安装配置
//解压 tar -zxvf //创建用户组 groupadd mysql /* -r 选项表示创建一个系统用户,系统用户通常没有登录 shell,它们通常用于运行服务 -g mysql 选项指定新用户的主组为mysql,这个组必须已经存在 -s /bin/false 选项指定用户的登录shell 为 /bin/false,这是一个假的shell,意味着这个用户不能通过密码登录系统 mysql 是新用户的用户名 */ useradd -r -g mysql -s /bin/false mysql /* 将当前目录及其子目录和文件所有权改为用户mysql和组mysql -R表示递归更改 */ chown -R mysql:mysql . //安装mysql,路径根据实际情况更改 ./bin/mysqld --user=mysql --basedir=/opt/mysql --datadir=/opt/mysql/data --initialize //修改MySQL配置文件 vi /etc/my.cnf //开启mysql ./support-files/mysql.server start //配置环境变量 export PATH=$PATH:/opt/mysql/bin //将mysql进程放入系统进程中 cp support-files/mysql.server /etc/init.d/mysqld //重新启动mysql服务 service mysqld restart //使用随机密码登录mysql数据库 mysql -u root -p //将名为root的用户,其登录地址为localhost的密码修改为123456 alter user 'root'@'localhost' identified by '123456'; //将用户名为root的用户的主机字段设置为%,表示从任何主机连接到MySQL服务器,而不仅仅是从localhost use mysql; user SET Host = '%' WHERE User = 'root'; //查看修改后的值 select user,host from user; //刷新权限 flush privileges; //确保防火墙允许MySQL的默认端口(3306)通过 firewall-cmd --zone=public --add-port=3306/tcp --permanent firewall-cmd --reload
my.cnf文件
[mysqld] port=3306 basedir=/opt/mysql datadir=/opt/mysql/data socket=/opt/mysql/mysql.sock character-set-server=utf8 symbolic-links=0 bind_address=0.0.0.0 [mysqld_safe] log-error=/opt/mysql/mariadb/log/mariadb.log pid-file=/opt/mysql/mariadb/run/mariadb.pid [client] socket=/opt/mysql/mysql.sock default-character-set=utf8 !includedir /etc/my.cnf.d
配置Tomcat与war包的部署
配置tomcat
//进入到bin下 ./startup.sh //开启 ./shutdown.sh //关闭 //查看系统中的所有开放端口 firewall-cmd --zone=public --list-ports //打开8080端口 firewall-cmd --zone=public --add-port=8080/tcp --permanent //重启防火墙 systemctl restart firewalld.service
Python基础
输出
n=100 print("one:%d"%n) #整数d n=33.333 print("two:%f"%n) #浮点数f n="sss" print("three:%s"%n) #字符串s n = { 1, 2, 3 } print("four:%r"%n) #万能r,输出原始表示 #f-string字符串格式化,类似字符串内插 age=18 name="root" print(f"my name is {name}, my age is {age}") #str.format方法,格式控制符通常包含在大括号{}中,并可以使用命名参数或位置参数 template = "整数:{}, 浮点数:{:.2f}, 字符串:{}" print(template.format(123, 45.6789, "hello"))
print()
有一个可选参数end=
,可以指定为空字符串''
就不会换行,可以设置为其他字符或字符串,以便在输出后添加自定义的分隔符
输入
while (1): try: age = int(input("请输入您的年龄:")) print(f"您的年龄是:{age}岁") break except ValueError: print("对不起,您输入的不是一个有效的年龄。请重新输入一个整数")
类型转换
#转换为float height = float(input("请输入您的身高(米):") print(f"您的身高是:{height}米")
[!hint]
- Python不区分单引号和双引号,它们都可以表示一个字符串
- 单引号和双引号可以互相嵌套使用,但不能交叉使用
- 单行注释#,多行注释三对引号,不区分单双引号
- Python使用花括号表示语句体,使用语句缩进判断语句体
Python的字符串运算符
str1 + str2 #字符串连接 str * n #重复n次字符串 [] #索引获取字符串中字符,也可以使用冒号获取部分字符 str in a #字符串是否包含给定字符 str not in a #字符串是否不包含给定字符 r/R"str" #让字符串原始输出,不转义
格式化字符串和内建函数自查
分支和循环结构
if语句
a = 1 b = 2 if a > b: print("a max!") else: print("b max!") #多重条件判断,不能使用else if,使用elif results = 99 if results >= 90: print('优秀') elif results >= 70: print('良好') elif results >= 60: print('及格') else: print('不及格')
三元表达式
x = 10 y = "positive" if x > 0 else "non-positive" print(y)
for语句
#遍历字符串,只有一条语句可以写在一行 for i in "hello world": print(i, end = '') #遍历数组(列表) fruits=['banana', 'apple', 'mango'] for fruit in fruits: print(fruit)
如果需要进行一定次数的循环,则需要借助range()
函数
for i in range(5, 10, 2): print(i, end = ' ')
range()
函数,第一个参数是开始位置,第二个参数是结束位置,第三个参数是循环步长,后两个参数是可选参数
如果只想循环而不在乎每次迭代的索引或值:
for _ in range(5): print("Hello World")
_
只是一个普通的变量名,也可以使用其他变量名来替代,只是按照编程惯例,它表示一个占位符来表示该变量不会被使用
数组(列表)
数组是方括号表示,每一项使用逗号隔开,数组下标从零开始,Python将数组称为列表
#Python列表内置方法 append(x) #列表末尾添加一个元素 extend(x) #将另一个列表的所有元素添加到列表中 insert(i, x) #在指定位置插入元素 remove(x) #移除列表中第一个值为x的元素 pop([i]) #移除并返回列表中指定位置的元素,未指定位置默认移除并返回最后一个元素 clear() #移除列表所有元素 index(x, strat,stop) #返回第一个值为x的元素的索引,后为可选参数,指定范围搜索 count(x) #返回列表中值为x的元素的数量 sort(key=,reverse=) #对列表进行原地排序,都是可选参数,key指定一个函数,该函数会在排序之前应用于每个元素。这允许你基于元素的某个属性或转换后的值进行排序.reverse布尔值,true为降序,默认false为升序 reverse() #反转列表元素顺序 copy() #返回列表的浅拷贝
可以使用下标索引访问列表中的值.也可以使用方括号的形式截取字符
list = ['physics', 'chemistry', 1997, 2000, 1, 2] print("list[1:3]: ", list[1:3])#不包含3 #1到末尾 for i in list[1:]: print("list[1:4]: ", i) #负数是反转读取,-1就是倒数第一个元素
列表对+
和*
的操作符与字符串类似,+
组合列表,*
重复列表
print(3 in list) #元素是否在列表中 len(list) #列表长度
字典
字典使用花括号表示(就是键值对),一个key对应一个value,之间使用冒号分隔,不同项之间使用逗号分隔
Python规定一个字典中的key必须独一无二,value可以相同
#Python字典内置方法 clear() #清除字典所有项 copy() #返回字典浅拷贝 fromkeys(seq,value) #创建新字典,以序列seq中元素作为字典建,value是可选参数,为字典所有键对应的初始值 get(key,default) #返回指定键的值,如果键不存在则返回default值,如果未指定default,则返回None items() #返回包含字典中所有键值对的视图对象 keys() #返回包含字典中所有键的视图对象 values() #返回包含字典中所有值的视图对象 pop(key,default) #移除并返回列表中指定位置的元素,未指定位置默认移除并返回最后一个元素,default是可选参数 popitem() #随机移除字典中的一对键值对,并作为一个元组返回,如果字典为空,抛出KeyError异常 setdefault(key,default) #如果键不存在则插入键并将值设置为default,如果键已经存在则返回其值。default的默认值为None update() #使用另一个字典的键值对更新该字典
元组
元祖的元素不能被修改,元祖使用圆括号创建,逗号隔开,元组中只包含一个元素时,需要在元素后面添加逗号
元组与字符串类似,下标索引从0开始,可以进行截取,组合等
元组中的元素值是不允许修改的,但可以对元组进行连接组合
tup1 = (12, 34.56) tup2 = ('abc', 'xyz') #修改元组元素操作是非法的 tup1[0] = 100 #创建一个新的元组 tup3 = tup1 + tup2 print(tup3)
[!caution]
del
语句在Python中用于删除对象。它可以用于删除变量、列表中的元素、字典中的键值对,甚至是整个对象(比如列表或字典)
del
语句用于解除一个或多个对象与它们名称之间的绑定
元组中的元素值不能修改,但可以使用del
删除整个元组
del tup3
与字符串一样,元组之间可以使用+
号和*
号进行运算。这就意味着他们可以组合和复制,运算后会生成一个新的元组
#元组内置函数 cmp(tuple1, tuple2) #比较两个元组元素 len(tuple) #计算元组元素个数 max(tuple) #返回元组中元素最大值 min(tuple) #返回元组中元素最小值 tuple(seq) #将列表转换为元组
函数
def
关键字定义,后接函数标识符和圆括号
使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为Python解释器能够用参数名匹配参数值
function(agr=2,str="meet") #默认参数 def function(name, age = 12): #不定长参数,带星号的变量名会存放所有未命名的变量参数 def printinfo( arg1, *vartuple ): print("输出: ") print(arg1) for var in vartuple: print(var)
Python使用lambda表达式创建匿名函数,只包含一条语句
#将它赋给变量即可作为函数使用,这怎么那么像C#的委托 sum = lambda arg1, arg2: arg1 + arg2 # 调用sum函数 print("相加后的值为: "), sum( 10, 20 ) print("相加后的值为: "), sum( 20, 20 )
Python中的类型属于对象,变量没有类型
a = [1, 2, 3] a = 'Apple'
[1,2,3]
是list类型,'Apple'
是String类型,变量a没有类型,它仅是一个对象的引用(指针)
python函数的参数传递:
-
不可变类型:类似c++的值传递,如整数、字符串、元组,只传递值副本,不传递本身
-
可变类型:类似c++的引用传递,如列表,字典,传递对象本身
类和方法
class
关键字创建类
class A(object): #创建了A类,默认继承object,不显式声明继承也可以
方法和函数唯一的不同是,方法第一个参数必须存在,一般命名为self
,但在调用这个方法时不需要为这个参数传值
一般在创建类时会首先声明初始化方法__init__()
class A(): def__init__(self, a, b): self.a=int(a) self.b=int(b)
就是构造函数
模块
也就是类库,一个模块只会被导入一次,不管执行多少次import
。这样可以防止导入模块被一遍又一遍地执行
#引入模块或模块中的函数 import 模块.函数 #从模块中导入一个指定的部分到当前命名空间中 from 模块 import 函数1,函数2 from 模块 import * #把该模块的所有内容导入到当前的命名空间
模块搜索路径存储在system
模块的sys.path
变量中。变量里包含当前目录,PYTHONPATH和由安装过程决定的默认目录
Python标准异常
BaseException |
所有异常的基类 |
SystemExit |
解释器请求退出 |
KeyboardInterrupt |
用户中断执行(通常是输入^C) |
Exception |
常规错误的基类 |
StopIteration |
迭代器没有更多的值 |
GeneratorExit |
生成器(generator)发生异常来通知退出 |
StandardError |
所有的内建标准异常的基类 |
ArithmeticError |
所有数值计算错误的基类 |
FloatingPointError |
浮点计算错误 |
OverflowError |
数值运算超出最大限制 |
ZeroDivisionError |
除(或取模)零 (所有数据类型) |
AssertionError |
断言语句失败 |
AttributeError |
对象没有这个属性 |
EOFError |
没有内建输入,到达EOF 标记 |
EnvironmentError |
操作系统错误的基类 |
IOError |
输入/输出操作失败 |
OSError |
操作系统错误 |
WindowsError |
系统调用失败 |
ImportError |
导入模块/对象失败 |
LookupError |
无效数据查询的基类 |
IndexError |
序列中没有此索引(index) |
KeyError |
映射中没有这个键 |
MemoryError |
内存溢出错误(对于Python 解释器不是致命的) |
NameError |
未声明/初始化对象 (没有属性) |
UnboundLocalError |
访问未初始化的本地变量 |
ReferenceError |
弱引用(Weak reference)试图访问已经垃圾回收了的对象 |
RuntimeError |
一般的运行时错误 |
NotImplementedError |
尚未实现的方法 |
SyntaxError |
Python 语法错误 |
IndentationError |
缩进错误 |
TabError |
Tab 和空格混用 |
SystemError |
一般的解释器系统错误 |
TypeError |
对类型无效的操作 |
ValueError |
传入无效的参数 |
UnicodeError |
Unicode 相关的错误 |
UnicodeDecodeError |
Unicode 解码时的错误 |
UnicodeEncodeError |
Unicode 编码时错误 |
UnicodeTranslateError |
Unicode 转换时错误 |
Warning |
警告的基类 |
DeprecationWarning |
关于被弃用的特征的警告 |
FutureWarning |
关于构造将来语义会有改变的警告 |
OverflowWarning |
旧的关于自动提升为长整型(long)的警告 |
PendingDeprecationWarning |
关于特性将会被废弃的警告 |
RuntimeWarning |
可疑的运行时行为(runtime behavior)的警告 |
SyntaxWarning |
可疑的语法的警告 |
UserWarning |
用户代码生成的警告 |
除了在except
中使用,还可以使用raise
语句抛出异常
raise 异常类型(可以附加值,通常是用于描述异常的原因)
单元测试Junit框架
JUnit4注解
@Test
:注释方法为测试方法,JUnit 运行器将执行标有@Test
注解的所有方法expected=
指定预期的异常。如果测试方法抛出了指定的异常,则测试通过;否则,测试失败timeout=
用于指定测试方法的最大运行时间(以毫秒为单位)。如果测试方法在指定时间内没有完成,则测试失败
@Before
:在每个测试方法之前执行。用于初始化测试环境@After
:在每个测试方法之后执行。用于清理测试环境
![[Pasted image 20240930160011.png]]@BeforeClass
:在所有测试方法之前仅执行一次。用于执行一次性的初始化,如数据库连接@AfterClass
:在所有测试方法之后仅执行一次。用于执行一次性的清理,如关闭数据库连接
![[Pasted image 20240930160026.png]]@Ignore
:忽略某个测试方法或测试类。被忽略的测试不会被 JUnit 运行器执行
参数化测试
允许使用不同的值反复运行同一测试,遵循5个步骤创建参数化测试 1. 使用`@RunWith(Parameterized.class)`注释指定测试运行器 2. 创建一个由`@Parameters`注释的公共的静态方法,它返回一个对象的集合(数组)来作为测试数据集合 3. 创建一个公共的构造函数,它接受和一行测试数据相等同的东西 4. 为每一列测试数据创建一个实例变量 5. 用实例变量作为测试数据的来源来创建你的测试用例
@RunWith(Parameterized.class)
:在一个测试类上使用@RunWith(Parameterized.class)
注解时,告诉JUnit使用Parameterized
运行器来执行这个测试类中的所有测试方法。这个运行器知道如何处理参数化测试,即它会为@Parameters
注解方法提供的每组数据创建一个测试实例,并为每个实例运行测试方法@Parameters
:这个注解用于定义参数化测试的数据。它修饰一个静态方法,该方法返回一个Collection<Object[]>
类型的数据集合,其中每个Object[]
包含一组参数,这些参数将被用来构造测试类的实例。通常返回一个列表(如Arrays.asList
),其中包含了多个数组,每个数组代表一组测试参数。这些参数将按照它们在列表中的顺序被用来创建测试实例,并分别运行测试方法
import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.assertEquals; import java.util.Arrays; import java.util.Collection; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) public class FourFlowChartTest { static FourFlowChart fourFlowChart; @BeforeClass public static void setUP() { fourFlowChart=new FourFlowChart(); } private String usernameString; private String passwordString; private String expectedString; public FourFlowChartTest(String u,String p,String e) { this.usernameString=u; this.passwordString=p; this.expectedString=e; } @Parameters public static Collection<Object[]> datas(){ return Arrays.asList(new Object[][] { {"","","用户名或密码不能为空"}, {"admin","123","登录成功"}, {"test","123","请输入正确的用户名"}, {"admin","1","请输入正确的密码"}, {"test","1","请输入正确的用户名和密码"}, }); } @Test public void test() { String result=fourFlowChart.getResult(usernameString, passwordString); assertEquals(expectedString, result); } }
使用
BufferedReader
和FileReader
读取csv文件
package test; import static org.junit.Assert.assertEquals; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) public class ReadCSVAddTest { private ReadCSVAdd readCSVAdd=new ReadCSVAdd(); private int a; private int b; private int expected; public ReadCSVAddTest(int a,int b,int expected) { this.a=a; this.b=b; this.expected=expected; } @Parameters public static Collection<Object[]> datas() throws IOException{ ArrayList<Object[]> datas=new ArrayList<>(); BufferedReader br=new BufferedReader(new FileReader("F:\\AutoTest\\Eclipse\\code\\code\\review\\junitCode\\test\\data.csv")); String line; try { while((line=br.readLine())!=null) { String[] values=line.split(","); int a=Integer.parseInt(values[0]); int b=Integer.parseInt(values[1]); int expected=Integer.parseInt(values[2]); datas.add(new Object[] {a,b,expected}); } }finally { br.close(); } return datas; } @Test public void testAdd() { int result=readCSVAdd.getResult(a, b); assertEquals("不满足加法需求",result,expected); } }
测试套件
一种可批量运行测试类的方法
import org.junit.runner.RunWith; import org.junit.runners.Suite; @RunWith(Suite.class) @Suite.SuiteClasses({//测试类数组 EmailRegisterTest.class, GetDaysTest.class }) public class SuiteTest {//空类作为测试套件的入口 }
Rule注解
import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; public class TestNameExample { @Rule public TestName testName = new TestName();//在测试方法中获取当前测试方法的名称 @Test public void testMethod1() { System.out.println("Running test: " + testName.getMethodName()); } @Test public void testMethod2() { System.out.println("Running test: " + testName.getMethodName()); } }
import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import java.io.File; import java.io.IOException; public class TemporaryFolderExample { @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();//在测试中创建临时文件和目录,并在测试结束后自动删除它们。这对于需要文件系统操作的测试非常有用 @Test public void testCreateFile() throws IOException { File file = temporaryFolder.newFile("test.txt"); System.out.println("Created file: " + file.getAbsolutePath()); } @Test public void testCreateDirectory() throws IOException { File directory = temporaryFolder.newFolder("testDir"); System.out.println("Created directory: " + directory.getAbsolutePath()); } }
ExternalResource
是 JUnit 提供的一个基类,用于在测试前后执行资源设置和清理工作
每个测试方法执行前都会打印 "Before test: Setting up resources",执行后都会打印 "After test: Tearing down resources"
import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExternalResource; public class ExternalResourceExample { @Rule public ExternalResource resource = new ExternalResource() { @Override protected void before() throws Throwable { System.out.println("Before test: Setting up resources"); } @Override protected void after() { System.out.println("After test: Tearing down resources"); } }; @Test public void testMethod1() { System.out.println("Running test method 1"); } @Test public void testMethod2() { System.out.println("Running test method 2"); } }
JUnit4断言
-
assertEquals(expected, actual)
: 断言检查两个值是否相等 -
assertEquals(double expected, double actual, double delta)
: 断言检查两个双精度浮点数是否在指定的误差范围内相等 -
assertEquals(float expected, float actual, float delta)
: 断言检查两个浮点数是否在指定的误差范围内相等 -
assertNotNull(Object object)
: 断言检查对象不为空 -
assertNull(Object object)
: 断言检查对象为空 -
assertTrue(boolean condition)
: 断言检查条件是否为true
-
assertFalse(boolean condition)
: 断言检查条件是否为false
-
assertSame(Object expected, Object actual)
: 断言检查两个对象引用是否指向同一个对象 -
assertNotSame(Object expected, Object actual)
: 断言检查两个对象引用是否指向不同的对象 -
assertArrayEquals(Object[] expecteds, Object[] actuals)
: 断言检查两个数组是否相等 -
assertArrayEquals(double[] expecteds, double[] actuals, double delta)
:断言两个双精度浮点数数组相等,允许有一定的误差范围
collapse: none assertTure和false以及assertEquals和NotEquals和assertNull和assertNotNull都可以有第一个string参数,用于自定义失败信息 `import static org.junit.Assert.*;`是JUnit4断言的包 `import static org.hamcrest.MatcherAssert.assertThat;` `import static org.hamcrest.Matchers.*;`是Hamcrest测试断言库的包
Hamcrest测试断言库
assertThat(actual,metcher)
是 Hamcrest 提供的一种灵活且可读性更高的断言方式,actual
是被测试的实际值,matcher
是用于匹配的条件,如预期结果assertThat(actual,equalTo(expected))
:检查两个值是否相等assertThat(actual, is(expected))
:用于表示一个匹配,其中的值应与给定的值相等assertThat(actual, is(not(expected)));
:用于表示条件不成立
int actual = 3; int unexpected = 5; assertThat(actual, is(not(unexpected))); // 断言成功,因为 3 不等于 5
-
assertThat(actual, greaterThan(expectedValue));
:检查一个数字是否大于另一个数字 -
assertThat(actual, lessThan(expectedValue));
:检查一个数字是否小于另一个数字 -
emptyString
和notEmptyString
:检查字符串是否为空或非空
String actualEmpty = ""; String actualNotEmpty = "Hello"; assertThat(actualEmpty, is(emptyString())); // 断言成功,因为字符串为空 assertThat(actualNotEmpty, is(not(emptyString()))); // 断言成功,因为字符串非空
+assertThat(actual, containsString(expectedSubstring));
:检查字符串是否包含某个子字符串
String actual = "Hello, World!"; String expectedSubstring = "World"; assertThat(actual, containsString(expectedSubstring)); // 断言成功,因为包含子串 "World"
hasItem
和hasItems
:检查集合中是否包含某个元素或多个元素
List<String> collection = Arrays.asList("apple", "banana", "orange"); assertThat(collection, hasItem("banana")); // 断言成功,因为集合中包含 "banana" assertThat(collection, hasItems("apple", "orange")); // 断言成功,因为集合中同时包含 "apple" 和 "orange"
assertThat(array, arrayContaining(expectedElement1, expectedElement2));
:检查数组是否按照顺序包含指定的元素
String[] array = {"one", "two", "three"}; assertThat(array, arrayContaining("one", "two", "three")); // 断言成功,因为数组按顺序包含这些元素
自动化测试脚本设计
术语定义
- 自动化测试概念:自动化测试是把以人为驱动的测试行为转化为机器执行的一种过程。
- 自动化测试前提条件:需求变动不频繁、项目周期足够长、自动化测试脚本可重复使用。
- 自动化测试的流程:(1)制定测试计划、(2)分析测试需求、(3)设计测试用例、(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文档中查找信息的语言,同样适用于HTMLBy.CSS_SELECTOR
: 通过CSS选择器来定位元素。CSS选择器是一种用于选择HTML元素的模式By.LINK_TEXT
: 通过完整的链接文本来定位元素,通常用于<a>
标签,当需要进行页面跳转时可以使用By.PARTIAL_LINK_TEXT
: 通过部分链接文本来定位元素
from time import sleep from selenium import webdriver #导入By类,用于指定元素查找方式 from selenium.webdriver.common.by import By #导入WebDriverWait类,用于显式等待 from selenium.webdriver.support.ui import WebDriverWait #导入expected_conditions模块,并为其设置别名EC,该模块包含了一系列预定义的等待条件 from selenium.webdriver.support import expected_conditions as EC driver = webdriver.Edge() driver.get("http://127.0.0.1:5500/webstorm/login.html") #如果10秒内该元素出现,则继续执行;否则抛出异常 #presence_of_element_located检查DOM是否存在一个元素 username=WebDriverWait(driver,10).until(EC.presence_of_element_located((By.ID, "username"))) #在找到的用户名输入框中输入文本root username.send_keys("root") password=WebDriverWait(driver,10).until(EC.presence_of_element_located((By.NAME, "password"))).send_keys("123") sleep(3)
下面的代码更加简洁,但使用find_element
方法而不加任何等待可能会导致问题,特别是当页面元素是动态加载的,或者当网络延迟、页面渲染速度等因素导致元素在查找时还未可用时
#找到ID为username的元素并赋给username变量 username = driver.find_element(By.ID, "username") #模拟用户文本输入 username.send_keys("root") driver.find_element(By.NAME, "password").send_keys("123456")
等待可以还可以使用隐式等待
driver.implicitly_wait(10)
当在后续的代码中使用find_element
或find_elements
方法时,WebDriver会在指定的时间内不断尝试查找元素,直到找到元素或超时
要注意隐式等待是全局的,应用于后续的所有元素查找操作
find_elements
可以定位多个元素
driver.find_elements(By.TAG_NAME, "input")[0].send_keys("root") driver.find_elements(By.TAG_NAME, "input")[1].send_keys("123")
鼠标模拟操作
Selenium
提供ActionChains
这个类来处理该类事件
#从SeleniumWebDriver模块中导入ActionChains类 from selenium.webdriver import ActionChains driver = webdriver.Edge() driver.get("http://127.0.0.1:5500/webstorm/login.html") username = driver.find_element(By.ID, "username") #模拟全选操作 ActionChains(driver).key_down(Keys.CONTROL, username).send_keys("a").key_up(Keys.CONTROL).perform() sleep(2)
也可以创建Actionchains
实例
actions = ActionChains(driver) # 移动鼠标到元素上并点击 actions.move_to_element(element).click().perform() # 或者发送键盘输入到元素 actions.send_keys("Hello, World!").perform()
.perform()
的作用是触发并执行之前通过ActionChains
对象构建的所有动作
#假设driver是WebDriver实例,并且已经导航到了目标页面 element = driver.find_element(By.ID, "some-element-id") # 创建ActionChains对象 actions = ActionChains(driver) # 构建动作链:移动鼠标到元素上并点击 actions.move_to_element(element).click() # 执行动作链中的所有动作 actions.perform()
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.5Signored_exceptions
:超时后的异常信息,默认情况下抛NoSuchElementException
异常
WebDriverWait()
一般由until()
或until_not()
方法配合使用
until(method, message='')
调用该方法提供的驱动程序作为一个参数,直到返回值为True
until_not(method, message=’ ’)
调用该方法提供的驱动程序作为一个参数,直到返回值为False
from time import sleep from selenium import webdriver from selenium.webdriver import Keys from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver = webdriver.Edge() driver.get("http://www.baidu.com") #等待直到元素出现,最多等待5秒,每0.5秒检查1次 #ID为kw的元素是百度搜索框 #until方法告诉WebDriverWait要等待哪个条件成立,它会不断检查传入的条件,直到该条件成立或达到最大等待时间,这里是检查元素是否存在 element = WebDriverWait(driver, 5, 0.5).until(EC.presence_of_element_located((By.ID, "kw"))) element.send_keys('selenium') element.send_keys(Keys.RETURN) sleep(3)
sleep()
是强制等待,会让程序暂停运行一段时间,如果代码量大,多个强制等待会影响整体的运行速度
隐式等待
使用driver.implicitly_wait(seconds)
方法来设置隐式等待的时间
seconds
是希望WebDriver等待的秒数
用于在整个WebDriver生命周期中,为所有查找元素的操作设置默认的超时时间。当WebDriver执行findElement
或findElements
方法时,如果页面上的元素尚未加载完成,WebDriver会等待指定的时间,直到元素出现或超时为止
隐式等待是全局性的,一旦设置,就会对之后所有的元素查找操作生效。这意味着,无论你在代码中查找多少个元素,WebDriver都会按照你设置的隐式等待时间进行等待
多表单和窗口切换
如果表单位于一个iframe或frame内部,需要首先切换到该iframe或frame,然后才能与其中的元素进行交互。可以通过switch_to.frame()
方法实现
driver = webdriver.Edge() driver.get("https://email.163.com/") #定位到表单frame fr = driver.find_element(By.TAG_NAME,'iframe') # 切换到表单 driver.switch_to.frame(fr) # 定位账号输入框并输入 usr_email = driver.find_element(By.NAME,'email') usr_email.send_keys('8888888888') sleep(2)
# 获取当前窗口句柄 current_window_handle = driver.current_window_handle # 获取所有窗口句柄 window_handles = driver.window_handles # 使用index选择窗口,这里为第一个窗口 driver.switch_to.window(window_handles[0]) #也可以使用窗口名 driver.switch_to_window("windowName")
页面元素属性删除
例如超链接的target
属性是_blank
,就会打开新的页面,如果不想弹出新窗口,就可以删除该属性
driver = webdriver.Edge() driver.get("https://www.icourse163.org/") delete = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.LINK_TEXT, "学校云"))) #使用JavaScript删除该元素的target属性 driver.execute_script("arguments[0].removeAttribute('target')",delete) #页面在本窗口打开 delete.click() sleep(1)
下拉滚动条
#滚动到页面底部 driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") #滚动到页面顶部 driver.execute_script("window.scrollTo(0, 0);") #向下滚动500像素 driver.execute_script("window.scrollTo(0, 500);") #向上滚动200像素 driver.execute_script("window.scrollTo(200, 0);") #第一个参数控制左右,正值向右,负值向左 #第二个参数控制上下,正值向下,负值向上 driver.execute_script("window.scrollBy(100, -100)") from selenium.webdriver import ActionChains, Keys ActionChains(self.d).send_keys(Keys.ARROW_DOWN).send_keys(Keys.ARROW_DOWN).perform() #执行5次向下滚动操作 i = 5 for _ in range(i): actions.send_keys(Keys.ARROW_DOWN).perform()
下拉框处理方法
from selenium.webdriver.support.select import Select select=driver.find_element()#获取到select元素 Select(select).方法()
select_by_index()
:通过索引定位select_by_value()
:通过value值定位select_by_visible_text()
:通过文本值定位deselect_all()
:取消所有选项deselect_by_{index|value|visible_text}()
:取消对应XX选项
警告窗处理
-
alert(message)
方法用于显示带有一条指定消息和一个OK按钮的警告框。 -
confirm(message)
方法用于显示一个带有指定消息和OK及取消按钮的对话框。如果用户点击确定按钮,则confirm()
返回true
。如果点击取消按钮,则confirm()
返回false
-
prompt(text,defaultText)
方法用于显示可提示用户进行输入的对话框。如果用户单击提示框的取消按钮,则返回null
。如果用户单击确认按钮,则返回输入字段当前显示的文本 -
alertObject.text
:获取提示的文本值 -
alertObject.accept()
:点击『确认』按钮 -
alertObject.dismiss()
:点击『取消』或者叉掉对话框 -
alertObject.send_keys(message)
:输入文本,仅适用于prompt方法
driver = webdriver.Edge() url = "http://127.0.0.1:5500/webstorm/alert.html" driver.get(url) try: # alert提示框 button = driver.find_element(By.ID, "alertButton") button.click() # 返回代表该警告框的对象 alertObject = driver.switch_to.alert alertObject.accept()# 点击确认按钮 sleep(1) driver.find_element(By.ID, "alertButton").click() sleep(1) alertObject.dismiss()# 点击取消按钮 except: print("try1error") try: # confirm提示框 button = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "confirmButton"))) button.click() confirmObject = driver.switch_to.alert print("confirm提示框信息:" + confirmObject.text) # 打印提示信息 confirmObject.accept() # 根据前端js代码逻辑,当点击确定按钮后会再弹出一个提示框,因此再次点击确定 confirmObject.accept() sleep(1) button.click() sleep(1) confirmObject.dismiss() confirmObject.accept() #受不了了,尼玛这里少了一个确认测半天,又不报错,我要不写个try捕获异常都不知道在哪里找错误 except: print("try2error") try: #prompt提示框 button = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "promptButton"))) button.click() promptObject = driver.switch_to.alert promptObject.accept() promptObject.accept()# 需要两次确认 sleep(1) button.click() promptObject.dismiss() promptObject.accept()# 确认未输入值 sleep(1) button.click() promptObject.send_keys("Test") promptObject.accept() # 注意语句先后顺序,两次确定关闭就无法获取其值 print("prompt提示框信息:" + promptObject.text) sleep(1) promptObject.accept() except: print("try3error") finally: sleep(1) driver.quit()
文件上传
<!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content="text/html;charset=utf- 8" /> <title>upload_file</title> <link href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min. css" rel="stylesheet" /> </head> <body> <div class="row-fluid"> <div class="span6 well"> <h3>upload_file</h3> <input type="file" name="file" /> </div> </div> </body> <script src="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.j s"></script> </html>
对于通过input
标签实现的上传功能,可以将其看作是一个输入框,可通过send_keys()
指定本地文件路径的方式实现文件上传
upload = WebDriverWait(driver, 5, 0.5).until(EC.presence_of_element_located((By.NAME, "file"))) upload.send_keys(r"D:/software/OCR/Umi-OCR/asset/icon/exit24.ico")
操作cookie
driver.get("http://www.youdao.com") # 向cookie的name和value中添加会话信息 driver.add_cookie({ 'name':'key111111', 'value':'value222222' }) all_cookie = driver.get_cookies() # cookie的信息打印 for cookie in all_cookie: # 分别替换%s print("cookie Name:%s -> cookie Value:%s" % (cookie['name'], cookie['value'])) # 删除cookie driver.delete_cookie('key111111') all_cookie = driver.get_cookies() print() for cookie in all_cookie: # 分别替换%s print("cookie Name:%s -> cookie Value:%s" % (cookie['name'], cookie['value']))
还有删除所有cookie的delete_delete_all_cookies
窗口截屏
driver.get('http://www.baidu.com') driver.find_element(By.ID,'kw').send_keys('selenium') driver.find_element(By.ID,'su').click() sleep(1) # 截取当前窗口,并指定截图图片的保存位置 driver.get_screenshot_as_file("C:/aaa.png")
选项操作
from selenium.webdriver.chrome.options import Options o=Options() o.add_argument('--headless')#无界面浏览 c=webdriver.Chrome(chrome_options=o)
o.set_headless() #设置启动无界面化 o.add_argument('--window-size=600,600') #设置窗口大小 o.add_argument('--incognito') #无痕模式 o.add_argument('user-agent="XXXX"') #添加请求头 o.add_argument("--proxy-server=http://200.130.123.43:3456")#代理服务器访问 o.add_experimental_option('excludeSwitches', ['enable-automation'])#开发者模式 o.add_experimental_option("prefs",{"profile.managed_default_content_settings.images": 2})#禁止加载图片 o.add_experimental_option('prefs', {'profile.default_content_setting_values':{'notifications':2}}) #禁用浏览器弹窗 o.add_argument('blink-settings=imagesEnabled=false') #禁止加载图片 o.add_argument('lang=zh_CN.UTF-8') #设置默认编码为utf-8 o.add_extension(create_proxyauth_extension( proxy_host='host', proxy_port='port', proxy_username="username", proxy_password="password" ))# 设置有账号密码的代理 o.add_argument('--disable-gpu') # 这个属性可以规避谷歌的部分bug o.add_argument('--disable-javascript') # 禁用javascript o.add_argument('--hide-scrollbars') # 隐藏滚动条
EC判断
from selenium.webdriver.support import expected_conditions as EC
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)
:- 功能:判断当前 URL 是否包含给定的字符串
WebDriverWait(driver, 10).until(EC.url_contains("python.org"))
EC.url_matches(url)
:- 功能:完全匹配URL
WebDriverWait(driver, 10).until(EC.url_matches("http://www.python.org"))
EC.url_to_be(url)
:- 功能:精确匹配当前URL
WebDriverWait(driver, 10).until(EC.url_to_be("http://www.python.org"))
EC.url_changes(original_url)
:- 功能:检查 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)
:- 功能:判断是否至少有一个元素存在于 DOM 树中
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 是否可以切换进去 ```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 树或不可见
- 功能:判断某个元素是否不存在于 DOM 树或不可见
WebDriverWait(driver, 10).until(EC.invisibility_of_element_located((By.ID, "element_id")))
14. `EC.element_to_be_clickable(locator)`: - 功能:判断某个元素是否可见并且可点击 ```python element = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.ID, "element_id")))
EC.staleness_of(element)
:- 功能:等待某个元素从 DOM 树中移除
- 功能:等待某个元素从 DOM 树中移除
WebDriverWait(driver, 10).until(EC.staleness_of(driver.find_element(By.ID, "element_id")))
16. `EC.element_to_be_selected(element)`: - 功能:判断某个元素是否被选中,通常用于下拉列表 ```python WebDriverWait(driver, 10).until(EC.element_to_be_selected(driver.find_element(By.ID, "select_id")))
EC.element_located_to_be_selected(locator)
:- 功能:判断元素的选中状态是否符合预期
- 功能:判断元素的选中状态是否符合预期
WebDriverWait(driver, 10).until(EC.element_located_to_be_selected((By.ID, "select_id")))
18. `EC.element_selection_state_to_be(element, state)`: - 功能:判断某个元素的选中状态是否符合预期 ```python WebDriverWait(driver, 10).until(EC.element_selection_state_to_be(driver.find_element(By.ID, "select_id"), True)) ``` 19. `EC.element_located_selection_state_to_be(locator, state)`: - 功能:判断定位到的元素的选中状态是否符合预期。 ```python WebDriverWait(driver, 10).until(EC.element_located_selection_state_to_be((By.ID, "select_id"), True)) ``` 20. `EC.alert_is_present()`: - **功能**:判断页面上是否存在 alert。 ```python WebDriverWait(driver, 10).until(EC.alert_is_present())
unittest框架
import unittest class BasicTestCase(unittest.TestCase): # 设置基础测试类名,继承库中测试用例的属性 # setUp()和tearDown()是每个测试用例进行时都会执行的测试方法,前者为起始,后者为结束 # 程序执行流程:setUp()-test1()-tearDown()---setUp()-test2()-tearDown()--- def setUp(self): # 每一个测试用例都会执行的"起始方法" print("setUp") def tearDown(self): # 每一个测试用例都会执行的"结束方法" print("tearDown") def test1(self): # 设置测试用例1,命名为test+xxx,会按照test后的阿拉伯数字顺序执行,testdemo也执行,带test都会执行 print("test1") def test2(self): # 设置测试用例2 print("test2") if __name__ == '__main__': # 设定条件执行unittest的主函数 unittest.main() # 调用主函数进行多个测试用例测试 """ 执行结果: setUp test1 tearDown setUp test2 tearDown """
特殊类方法setUpClass()
,tearDownClass()
,在所有测试用例前后执行
@classmethod # 定义类方法 def setUpClass(cls): # 覆盖父类的类方法 pass @classmethod # 定义类方法 def tearDownClass(cls): # 覆盖父类的类方法 pass
定义类属性,普通方法访问类属性需要通过类名访问,例如test1()
中想要获取guide
需要通过语句BasicTestCase.guide
直接访问类属性
- 如果实例没有相应属性,类属性有,则Python自动访问类属性替代
import unittest class BasicTestCase(unittest.TestCase): @classmethod def setUpClass(cls): cls.guide = 'yu' # 在类方法下,定义类属性 cls.guide def test1(self): # 设置测试用例1 guide = BasicTestCase.guide # 通过类名访问类属性 print(f"Guide from class: {guide}") # 输出类属性的值 def test2(self): # 设置测试用例2 # 也可以在其他测试用例中访问类属性 guide = BasicTestCase.guide print(f"Guide from class in test2: {guide}") if __name__ == '__main__': # 设定条件执行unittest的主函数 unittest.main() # 调用主函数进行多个测试用例测试 """ 输出结果: Guide from class: yu Guide from class in test2: yu """
在Unittest套件中,全局实例属性可以在setUp
,tearDown
中设置
class BasicTestCase(unittest.TestCase): @classmethod def setUpClass(cls): cls.guide = 'yu' # 在类方法下,定义类属性cls.guide = 'yu' pass def setUp(self): self.guide = 'ba' # 在setUp()方法下,定义 全局实例属性self.guide = 'ba' def test1(self): guide = self.guide # 3.在这段话中,这句话也获取guide = 'ba',因为实例在setUp中定义全局实例属性self.guide = 'ba' if __name__ == '__main__': # 设定条件执行unittest的主函数 unittest.main()
- 普通方法(test1)只可定义"当局"实例属性,生命周期为本方法内,无法制造依赖关系
class BasicTestCase(unittest.TestCase): def setUp(self): self.guide = 'ba' # 在setUp()方法下,定义"全局"实例属性self.guide = 'ba' def test1(self): guide = 'shi' # 在test1中定义"当局"实例属性guide = 'shi' print(guide) # 这里拿到的guide = 'shi' def test2(self): guide = self.guide print(guide) # 这里拿到的guide = 'ba',而不是'shi',说明普通方法中的实例变量生命周期仅限"当局",无法互相依赖 if __name__ == '__main__': # 设定条件执行unittest的主函数 unittest.main()
@unittest.skip('跳过的原因')
@unittest.skipIf('跳过条件', '跳过的原因')
@unittest.skipUnless('不跳过的条件', '不跳过的原因')
下列测试仅执行test3,因为test1跳过、test2满足跳过条件,test3满足不跳过条件
class BasicTestCase(unittest.TestCase): @unittest.skip('因为我想跳过所以跳过') # 直接跳过 def test1(self): print('执行test1') @unittest.skipIf(888 < 999, '因为888比999小,所以跳过') # 条件性跳过 def test2(self): print('执行test2') @unittest.skipUnless('你真厉害', '因为你真厉害,所以不跳过') def test3(self): print('执行test3') if __name__ == '__main__': # 设定执行unittest的主函数 unittest.main()
条件跳过参数的导入必须在类下定义
因为@unittest.skipIf()
语句执行优先级大于所有def,即无论是setUp()、setUpClass()还是test2()都在其之后执行,所以定义必须在类下
class BasicTestCase(unittest.TestCase): number = '888' @unittest.skipIf(number < '999', '因为number比999小,所以跳过') def test2(self): # 不会被执行,因为888满足跳过的条件 print('执行test2')
测试用例之间参数联动判定跳过的方法
语句编码+类属性变量->类属性变量通常用列表、字典等,解决多条件依赖时便捷
class BasicTestCase(unittest.TestCase): judge = {'first': 0} def test2(self): print('执行test2') BasicTestCase.judge['first'] = 888 # 更改下个测试所要依赖的变量值 def test3(self): if BasicTestCase.judge['first'] == 888: # 设定判定条件看是否需要跳过 return # 若满足条件则直接return结束,此test下的之后的语句均不执行 # print('执行test3') # 此段代码中这句话加与不加都并不会被执行,测试通过但执行语句并没有执行,因为根据依赖的条件test3已经结束 if __name__ == '__main__': # 设定条件执行unittest的主函数 unittest.main()
断言
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
:当前请求URLpm.request.headers
:当前请求的Headerspm.request.meth
:当前请求的方法pm.request.body
:当前请求的Body
pm.request.url = "http://example.com/api/new-endpoint"; pm.request.method = "PUT"; pm.request.body = { mode: "raw", raw: JSON.stringify({ key: "new-value" }) }; //添加一个新的Header pm.request.headers.add({ key: "Authorization", value: "Bearer your-token" }); //删除指定名称的header pm.request.headers.remove("Authorization");
操作响应数据
pm.response.code
:获取响应的HTTP状态码pm.response.status
:获取响应的HTTP状态信息pm.response.headers
:获取响应头的集合,可以访问特定的头信息pm.response.responseTime
:获取服务器响应请求所花费的毫秒数pm.response.responseSize
:获取响应体大小,字节为单位pm.response.text()
:将响应体转为字符串返回pm.response.json()
:将响应体转为json返回
断言
同步和异步测试
//测试检查响应是否有效 pm.test("response should be okay to process", function () { pm.response.to.not.be.error; pm.response.to.have.jsonBody(''); pm.response.to.not.have.jsonBody('error'); }); //1.5秒后检查响应状态码是否为200 pm.test('async test', function (done) { setTimeout(() => { pm.expect(pm.response.code).to.equal(200); done(); }, 1500); });
输出变量值或者变量类型
console.log(pm.collectionVariables.get("name")); console.log(pm.response.json().name); console.log(typeof pm.response.json().id); if (pm.response.json().id) { console.log("id was found!"); } else { console.log("no id ..."); //do something else }//for循环读取for(条件){语句;}
状态码检测
//pm.test.to.have方式 pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); //expect方式 pm.test("Status code is 200", () => { pm.expect(pm.response.code).to.eql(200); });
多个断言作为单个测试的结果
pm.test("The response has all properties", () => { //parse the response JSON and test three properties const responseJson = pm.response.json(); pm.expect(responseJson.type).to.eql('vip'); pm.expect(responseJson.name).to.be.a('string');//检查是否是string类型 pm.expect(responseJson.id).to.have.lengthOf(1);//检查是否是一个长度为1的数组 });
不同类型的返回结果解析
//获取返回的json数据 const responseJson = pm.response.json(); //将number转换为JSON格式 JSON.stringify(pm.response.code) //将json数据转换为数组 JSON.parse(jsondata) //获取xml数据 const responseJson = xml2Json(pm.response.text()); //获取csv数据 const parse = require('csv-parse/lib/sync'); const responseJson = parse(pm.response.text()); //获取html数据 const $ = cheerio.load(pm.response.text()); //output the html for testing console.log($.html());
测试响应正文reponseBody中的特定值
pm.test("Person is Jane", () => { const responseJson = pm.response.json(); pm.expect(responseJson.name).to.eql("Jane"); pm.expect(responseJson.age).to.eql(23); });//获取响应正文responseBodypm.response.text()
测试响应headers
//测试响应头是否存在 pm.test("Content-Type header is present", () => { pm.response.to.have.header("Content-Type"); }); //测试响应头的特定值 pm.test("Content-Type header is application/json", () => { pm.expect(pm.response.headers.get('Content-Type')).to.eql('application/json'); }); //获取响应头”Server“数据 postman.getResponseHeader("Server")
功能测试
测试分类
按代码可见度划分:
- 黑盒测试
- 源代码不可见
- UI功能可见
- 灰盒测试
- 部分源代码可见
- UI功能不可见
- 白盒测试
- 源代码可见
- UI功能不可见
等价类划分法
在所有测试数据中,具有某种共同特征的数据集合进行划分
分类:
- 有效等价类:满足需求的数据集合
- 无效等价类:不满足需求的数据集合
适用场景
需要大量数据测试输入,但无法穷举的地方 - 输入框
- 下拉列表
- 单选/复选框
示例:
需求:
- 区号:空或三位数字
- 前缀码:非"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条
边界值分析法
选取==
或>
或<
边界的值作为测试数据
- 上点:边界上的点(等于)
- 离点:距离上点最近的点(刚好大于/刚好小于)
- 内点:范围内的点(区间范围内的数据)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)