pytest
一、Pytest特点
基于Python语言的自动化测试框架最知名的有如下
1、 unitest
2、 pytest
3、 robotframework
前两款框架主要聚焦在开发者的白盒单元测试
而robotframework主要聚焦在系统测试上。
Pytest可以用来做系统测试的自动化,它的特点有:
1、 用Python编写测试用例,简便易用
2、 可以用文件系统目录层次对应手工测试用例层次结构
3、 灵活的初始化清除机制
4、 可以灵活挑选测试用例执行
5、 利用第三方插件,可以生成不错的报表
Pytest的功能非常多,我们这里主要介绍常用的功能。
安装
直接执行如下命令即可安装pytest
1 | pip install pytest |
我们还需要产生测试报表,所以需要安装一个pytest-html,执行如下命令安装
1 | pip install pytest - html |
二、快速上手
Pytest如何知道你哪些代码是自动化的测试用例
官方文档给出了pytest寻找测试项的具体规则
1、 如果未指定命令行参数,则从testpath(如果已配置)或当前目录开始收集
如果命令行参数,指定了目录、文件名或node id的任何组合,则按参数来找
2、 寻找过程会递归到目录中,除非它们匹配上norecursedirs
3、 在这些目录中,搜索由其测试包名称导入的test_*.py或*_test.py文件
4、 从这些文件中,收集如下测试项:
test为前缀的函数
Test为前缀的类里面的test为前缀的方法。
测试用例代码
首先,我们编写的测试用例代码文件,必须以test_开头,或者以_test结尾
比如我们创建一个文件名为test_错误登录.py,放在目录autotest\cases\登录下面。
其中autotest是我们创建的自动化项目根目录。
Test_错误密码.py
1 2 3 4 5 6 7 8 9 10 11 12 | class Test_错误密码: def test_c001001( self ): print ( '\n用例C001001' ) assert 1 = = 1 def test_c001002( self ): print ( '\n用例C001002' ) assert 2 = = 2 def test_c001003( self ): print ( '\n用例C001003' ) assert 3 = = 2 |
运行用例,打开cmd,进入项目根目录
显示找到3个测试项,2个执行通过,1个执行不通过。
通过的用例用一个绿色小点表示,不通过的用例用一个红色的F表示,并且会在后面显示具体不通过的用例和不通过的检查点代码细节。用例代码中有些打印语句没有显示出具体内容,如果想显示测试代码中print的内容,可以加上命令行参数-s。
1 | pytest - s |
如果希望得到更详细的执行信息,包括每个测试类、测试函数的名字,可以加上参数-v,这个参数可以和-s合并为-sv
1 | pytest - sv |
执行pytest时,如果没有指定目标目录或者文件,它会自动搜索当前目录下所有符合条件的文件、类、函数。所以上面,就找到了3个测试方法,对应3个用例。我们目前项目根目录中只有一个cases目录用例存放测试用例,将来还会有其他目录。
产生报告
前面安装pytest,我们也安装了pytest-html插件,这个插件就是用来产生测试报告的。要产生报告,在命令行加上参数—html=report.html –self-contained-html,如下
1 | pytest cases - - html = report.html - - self - contained - html |
生成报告
三、初始化清除
对于自动化测试框架来说,初始化清除功能至关重要。
模块级别
模块级别的初始化、清除分别在整个模块的测试用例执行前后执行,并且只会执行1次。它主要是用来为该模块中所有的测试用例做公共的初始化和清除。
如下定义setup_module和teardown_module全局函数
Test_错误密码.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | def setup_module(): print ( "\n *** 初始化-模块 ***" ) def teardown_module(): print ( "\n *** 清除-模块 ***" ) class Test_错误密码: def test_c001001( self ): print ( '\n用例C001001' ) assert 1 = = 1 def test_c001002( self ): print ( '\n用例C001002' ) assert 2 = = 2 def test_c001003( self ): print ( '\n用例C001003' ) assert 3 = = 2 class Test_错误密码 2 : def test_c002001( self ): print ( '\n用例C001001' ) assert 1 = = 1 def test_c002002( self ): print ( '\n用例C001002' ) assert 2 = = 2 def test_c002003( self ): print ( '\n用例C001003' ) assert 3 = = 2 |
运行结果:
方法级别
方法级别的初始化、清除分别在每个测试方法执行前后执行,并且每个用例分别执行1次,如下定义setup_class和teardown_class类方法
Test_错误密码.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | def setup_module(): print ( "\n *** 初始化-模块 ***" ) def teardown_module(): print ( "\n *** 清除-模块 ***" ) class Test_错误密码: @classmethod def setup_class( cls ): print ( "\n *** 初始化-类 ***" ) @classmethod def teardown_class( cls ): print ( "\n *** 清除-类 ***" ) def setup_method( self ): print ( "\n *** 初始化-方法 ***" ) def teardown_method( self ): print ( "\n *** 清除-方法 ***" ) def test_c001001( self ): print ( '\n用例C001001' ) assert 1 = = 1 def test_c001002( self ): print ( '\n用例C001002' ) assert 2 = = 2 def test_c001003( self ): print ( '\n用例C001003' ) assert 3 = = 2 class Test_错误密码 2 : def test_c002001( self ): print ( '\n用例C001001' ) assert 1 = = 1 def test_c002002( self ): print ( '\n用例C001002' ) assert 2 = = 2 def test_c002003( self ): print ( '\n用例C001003' ) assert 3 = = 2 |
运行结果:
类级别
类级别的初始化、清除分别在整个类的测试用例执行前后执行,并且只会执行1次。
Test_错误密码.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | def setup_module(): print ( "\n *** 初始化-模块 ***" ) def teardown_module(): print ( "\n *** 清除-模块 ***" ) class Test_错误密码: @classmethod def setup_class( cls ): print ( "\n *** 初始化-类 ***" ) @classmethod def teardown_class( cls ): print ( "\n *** 清除-类 ***" ) def setup_method( self ): print ( "\n *** 初始化-方法 ***" ) def teardown_method( self ): print ( "\n *** 清除-方法 ***" ) def test_c001001( self ): print ( '\n用例C001001' ) assert 1 = = 1 def test_c001002( self ): print ( '\n用例C001002' ) assert 2 = = 2 def test_c001003( self ): print ( '\n用例C001003' ) assert 3 = = 2 class Test_错误密码 2 : def test_c002001( self ): print ( '\n用例C001001' ) assert 1 = = 1 def test_c002002( self ): print ( '\n用例C001002' ) assert 2 = = 2 def test_c002003( self ): print ( '\n用例C001003' ) assert 3 = = 2 |
运行结果:
目录级别
目录级别的初始化清除,就是针对整个目录执行的初始化、清除。我们在需要初始化的目录下面创建一个名为conftest.py的文件,里面的内容如下:
conftest.py
1 2 3 4 5 6 7 8 | import pytest @pytest .fixture(scope = 'package' ,autouse = True ) def st_emptyEnv(): print (f '\n### 初始化-目录甲' ) yield print (f '\n### 清除-目录甲' ) |
Test_错误密码.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | def setup_module(): print ( "\n *** 初始化-模块 ***" ) def teardown_module(): print ( "\n *** 清除-模块 ***" ) class Test_错误密码: @classmethod def setup_class( cls ): print ( "\n *** 初始化-类 ***" ) @classmethod def teardown_class( cls ): print ( "\n *** 清除-类 ***" ) def setup_method( self ): print ( "\n *** 初始化-方法 ***" ) def teardown_method( self ): print ( "\n *** 清除-方法 ***" ) def test_c001001( self ): print ( '\n用例C001001' ) assert 1 = = 1 def test_c001002( self ): print ( '\n用例C001002' ) assert 2 = = 2 def test_c001003( self ): print ( '\n用例C001003' ) assert 3 = = 2 class Test_错误密码 2 : def test_c002001( self ): print ( '\n用例C001001' ) assert 1 = = 1 def test_c002002( self ): print ( '\n用例C001002' ) assert 2 = = 2 def test_c002003( self ): print ( '\n用例C001003' ) assert 3 = = 2 |
运行结果如下:
我们可以在多个目录下面放置这样的文件,定义该目录的初始化清除。
Pytest在执行测试时,会层层调用。
挑选用例执行
Pytest有灵活的挑选测试用例执行的机制
指定一个模块,可以像这样只挑选一个模块执行
1 | Pytest cases\登录\test_错误登录.py |
指定目录
可以像这样只挑选一个目录执行
1 | Pytest cases |
也可以指定多个目录
1 | Pytest cases1 cases2\登录 |
指定模块里面的函数或者类
指定一个类
1 | pytest cases\login\Test_错误密码.py::Test_错误密码 |
也可以指定类里面的方法
1 | pytest cases\login\Test_错误密码.py::Test_错误密码::test_c001001 |
根据名字
可以使用命令行参数-k后面加名字来挑选要执行的测试项
比如像这样后面跟测试函数名字的一部分:
1 | pytest - k c001001 - s |
注意,-k后面的名字
可以是测试函数的名字,可以是类的名字,可以是模块文件名,可以是目录的名字
是大小写敏感的
不一定要完整,只要能有部分匹配上就行
可以用not表示选择名字中不包含,比如
1 | pytest - k "not c001001" - s |
可以用and表示选择名字同时包含多个关键字,比如
1 | pytest - k "错 and 密码2" - s |
也可以使用or表示选择名字
1 | pytest - k "错 or 密码2" - s |
根据用例标签执行用例
可以这样给某个方法打上标签webtest
1 2 3 4 5 6 | class Test_错误密码 2 : @pytest .mark.smoke def test_c002001( self ): print ( '\n用例C002001' ) assert 1 = = 1 |
然后,可以这样运行指定标签的用例pytest cases -m smoke -s
也可以给整个类加上标签
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @pytest .mark.Test_错误密码 2 class Test_错误密码 2 : @pytest .mark.smoke def test_c002001( self ): print ( '\n用例C002001' ) assert 1 = = 1 def test_c002002( self ): print ( '\n用例C001002' ) assert 2 = = 2 def test_c002003( self ): print ( '\n用例C001003' ) assert 3 = = 2 |
也可以同时添加多个标签
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @pytest .mark.Test_错误密码 2 @pytest .mark.网页测试 class Test_错误密码 2 : @pytest .mark.smoke def test_c002001( self ): print ( '\n用例C002001' ) assert 1 = = 1 def test_c002002( self ): print ( '\n用例C001002' ) assert 2 = = 2 def test_c002003( self ): print ( '\n用例C001003' ) assert 3 = = 2 |
实际案例
Test_错误登录.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import time from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.by import By class Test_错误登录: def test_UI_0001( self ): print ( '\n用例UI_001' ) wd = webdriver.Chrome(service = Service(r "D:\个人知识总结\python project\pytest\chromedriver-win64\chromedriver-win64\chromedriver.exe" )) wd.implicitly_wait( 10 ) wd.get( 'http://127.0.0.1/mgr/sign.html' ) # wd.find_element(By.ID,'username').send_keys() wd.find_element(By. ID , 'password' ).send_keys( '888' ) wd.find_element(By.CSS_SELECTOR, "button[type='submit']" ).click() time.sleep( 2 ) alertText = wd.switch_to.alert.text print (alertText) assert alertText = = '请输入用户名' |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)