python 测试框架
单元测试
什么是单元测试
单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用来判断某个特定条件(或者场景)下某个特定函数的行为
单元测试越早介入越好
单元测试需要注意什么?
单元测试的时候一个大前提就是需要清除的知道,自己要测试的程序块所预期的输入和输出,然后根据这个预期和程序逻辑来书写case。这里的预期结果一定要针对需求/设计的逻辑去写,而不是针对程序的实现去写,否则单元测试就失去了意义,照着错误的实现设计出的case很可能也是错的。
单元测试框架
Unittest
- python内置的标准类库。它的API与Java的JUnit、.net的NUnit,C++的CppUnit很相似
Pytest
- 丰富、灵活的测试框架,语法简单,可以结合allure生成一个炫酷的测试报告,现在比较主流
Nose
- Nose是对unittest的扩展,使得python的测试更加简单
Mock
*unittest.mock是用来测试python的库。这个是一个标准库(出现在Python3.3版本以后)
单元测试覆盖率
也叫代码覆盖率,也被用于自动化测试和手工测试来度量测试是否全面的指标之一,应用覆盖率的思想增强测试用例的设计
单元测试覆盖类型:
语句覆盖:
- 通过设计一定量的测试用例,保证被测试的方法每一行代码都会被执行一遍
- 运行测试用例的时候被击中的代码行即称为被覆盖的语句
- 对于例子中的程序,只需要一条case,即可实现行覆盖(a=3,b=0,x=3)
- 漏洞:如果and写成or,仍能通过测试
条件覆盖
- 条件覆盖与判断覆盖类似,不过判定覆盖关注整个判定语句,而条件覆盖则关注某个判断条件
- 测试用例: if(a>1 and b==0)
- 缺陷:测试用例指数级增加(2**conditions)
判断覆盖
- 运行测试用例的过程被击中的判定语句
- 测试用例
- 漏洞
- 大部分的判定语句是由多个逻辑条件组合而成,若仅仅判断每个条件的取值情况,必然会遗漏部分测试路径(a==2 or x>1 --------> a==2 or x<1)
路径覆盖
- 覆盖所有可能执行的路径
- 测试用例(画一下流程图)
例子:
ubbitest框架介绍
- python自带的单元测试框架,常用在单元测试
- 在自动化测试中提供用例组织与执行
- 提供丰富的断言方法-验证函数等功能
- 加上HTML TestRunner可以生成html的报告
- 现在依然有一些公司在用这个框架
unittest
unitest编写与规范
Unittest提供了test cases、test suites、test fixtures、test runner相关的组件
- test cases:测试用例
- test suites:测试用例集合
- test fixtures:执行测试用例之前和之后会进行一些清理工作
- test runner:测试用例执行
编写规范
- 测试模块首先import unittest
- 测试类必须继承unittest.TestCase
- 测试方法必须以“test_”开头
- 模块名字,类名没有特殊要求
测试框架结构
import unittest class demo(unittest.TestCase): @classmethod def setUpClass(cls) -> None: #setUpClass和tearDownClass作用域是整个类,在整个类运行之前和之后分别执行 print("setup class") @classmethod def tearDownClass(cls) -> None: print("teardown class") def setUp(self) -> None: #setUp 和tearDown的作用域是测试用例,在每个测试用例运行之前和之后分别执行 print("setup") def tearDown(self) -> None: #回收资源 print("teardown") def test_case01(self): print("testcase01") self.assertEqual(2,2,"判断相等") #self.assertIn('h','this') def test_case02(self): print("testcase02") self.assertEqual(1,2,"判断相等") #self.assertIn('h','this') # 跳过某个测试用例 #@unittest.skip #也可以在满足某个条件时跳过: #@unittest.skip(1+1==2,"跳过这条用例") def test_case03(self): print("testcase03") self.assertEqual(4,4,"判断相等") #self.assertIn('h','this') if __name__ == '__main__': unittest.main()
unittest断言
常用断言:
assertEqual
assertIn
assertTrue
基本的断言方法提供了测试结果是True还是False,所有的断言方法都有一个msg参数,如果指定msg参数的值,则将该信息作为失败的错误信息返回
unittest执行测试用例
方法一
unittest.main():把当前模块下的测试用例(以test_开头的方法)全部执行方法二——加入容器中执行(需要在pycharm进行设置,把默认的unittest执行去除,添加要执行的文件)
suite=unittest.TestSuite
suite.addTest(TestMethod("test_01"))
suite.addTest(TestMethod("test_02"))
unittest.TextTestRunner().run(suite)方法三——此用法可以同时测试多个类
suite1=unittest.TestLoader().loadTestsFromTestCase(TestCase1)
suite2=unittest.TestLoader().loadTestsFromTestCase(TestCase2)
suite=unittest.TestSuite([suite1,suite2])
unittest.TextTestRunner(verbosity=2).run(suite)方法四——匹配某个目录下所有以test开头的py文件,执行这些文件下的所有测试用例
- test_dir="./test_case"
- discover=unittest..defaultTestLoader.discover(test_dir,pattern="test*.py")
- discover可以一次调用多个脚本
- test_dir被测试脚本的路径
- pattern脚本名称匹配规则
测试用例执行过程
unittest结合htmltestrunner生成带日志的测试报告
pytest测试框架
pytest介绍
- pytest是一个非常成熟的全功能的Python测试框架
- 简单灵活,容易上手
- 支持参数化
- 测试用例的skip和xfail,自动失败重试等处理
- 能够支持简单的单元测试和复杂的功能测试,还可以用来做selenium/appnium等自动化测试、接口自动化测试(pytest+requests)
- pytest具有很多第三方插件,并且可以自定义扩展,比较好用的如pytest-allure(完美html测试报告生成),pytest-xdist(多CPU分发)等
- 可以很好的和jenkins集成
- 文档:https://docs.pytest.org/en/latest/contents.html#toc
- 第三方库:https://pypi.org/search/?q=pytest
pytest安装与依赖
- pip install -U pytest:U表示升级
- pip install pytest-sugar:对运行过程的界面进行美化
- pip install pytest-rerunfailures:重新运行出错的测试用例
- pip install pytest-xdist:多任务同时并发的执行测试用例
- pip install pytest-assume:执行每一条断言
- pip install pytest-html:生成测试报告
... - pip list:查看
- pytest -h:帮助
测试用例的识别与运行
- 测试文件
- test_*.py
- *_test.py
- 用例识别
- Test类包含的所有test_的方法(测试类中不能带有init方法)
- 不在class中的所有test_*方法
- pytest也可以执行unittest框架写的用例和方法
- pytest执行
可以在命令行中直接执行,输入pytest 文件名(要在包含该文件的目录下执行)
常用参数:- -v:(最高级别信息--verbose)打印详细的日志信息(哪一个模块的哪一个方法执行成功/失败)
- -s:s是带控制台输出结果,也是输出详细(可以将文件中print的信息打印到终端中)
- pytest 文件名::类名:可以执行某一个类里的测试用例
- pytest 文件名::类名::方法名:可以执行某个模块里面某个类里面的方法
- pytest -v -k "类名 and not 方法名":跳过运行某个用例
- pytest -m[标记名] :@pytest.mark.[标记名]将运行有这个标记的测试用例
- pytest -x 文件名:一旦运行到报错就停止
- pytest --maxfail=[num]:当运行错误达到num的时候就停止运行
pytest执行-失败重新运行
- 场景:测试失败后要重新运行n次,要在重新运行之间添加延迟时间,间隔n秒再运行
- 安装:pip install pytest-rerunfailures
- 执行:
- pytest --reruns 3 -v -s test_class.py(重复运行3次)
- pytest -v --reruns 5 --reruns-delay 1(间隔1s)
pytest执行-多条断言有失败也都运行
- 场景:一个方法中写多条断言,通常第一条过不去,下面就不执行了。但是我们想报错也都执行一下就可以采用这种方法
- 安装:pip install pytest-assume
- 执行:
- pytest.assume(1==4)
- pytest.assume(2==4)
- pytest.assume('h' in 'this')
(把每一条的信息都打印出来)
pycharm中配置与执行pytest测试框架
- 运行方法:pytest.main(['-v','TestClass'])(所有参数和pytest命令行方式是一样的)
- 配置:
- 执行:
pytest框架结构
import pytest类似的setup,teardown同样更加灵活
- 模块级:
setup_module/teardowm_module:模块始末,全局的(优先最高) - 函数级:
setup_function/teardown_function:只对函数用例生效(不在类中) - 类级:
setup_class/teardown_calss:只在类中前后运行一次(在类中) - 方法级:
setup_method/teardown_method:开始于方法始末(在类中) - 类里面的:
setup/teardown:运行在调用方法的前后
例:
import pytest def setup_module(): print("这是个setup_module方法") def teardown_module(): print("这是个teardown_module方法") def setup_function(): print("这是个setup_function方法") def teardown_function(): print("这是个teardown_function方法") def test_login(): print("这是个外部的方法") class TestDemo: def setup_class(self): print("这是个setup_class方法") def setup_method(self): print("这是个setup_method方法") def setup(self): print("这是个setup方法") def teardown_class(self): print("这是个teardown_class方法") def teardown_method(self): print("这是个teardown_method方法") def teardown(self): print("这是个teardown方法") def test_one(self): print("开始执行test_one方法") x = 'this' assert 'h' in x def test_two(self): print("开始执行test_two方法") x = 'hello' assert 'e' not in x def test_three(self): print("开始执行test_three方法") a = 'hello' b = 'hello world' assert a in b if __name__ == '__main__': pytest.main()
结果:
pytest-fixture的用法
场景:
- 用例1需要先登录
- 用例2不需要登录
- 用例3需要登录
这种场景无法用setup和teardown实现
这时候可以通过pytest-fixture实现
用法:在方法前面加@pytest.fixture()
例:前端自动化中的应用
- 场景:测试用例执行时,有的用例需要登录才能执行,有些用例不需要登录。setup和teardown无法满足,fixture可以。默认scope(范围)function
- 步骤:
- 导入pyteat
- 在登录的函数上面加@pytest.fixture()
- 在要使用的测试方法中传入(登录函数名称),就先登录
- 不传入的就不登录,直接执行测试方法
import pytest @pytest.fixture() def login(): print("这是个登录方法") def test_case1(login): print("test_case1,需要登录") pass def test_case2(): print("test_case2,不需要登录") pass def test_case3(login): print("test_case3,需要登录") pass if __name__ == '__main__': pytest.main()
执行结果:
例:前端自动化中应用-conftest
- 场景:
与其他测试工程师合作一起开发时,公共模块要在不同文件中,要在大家都访问到的地方 - 解决:
conftest.py这个文件进行数据共享,并且可以放在不同位置起着不同范围共享作用 - 执行:
系统执行到参数login时先从本文件中查找是否有这个名字的变量,之后再conftest.py中找是否有 - 步骤:
将登陆模块带@pytest.fixture写在conftest.py中 - conftest.py配置需要注意:
- conftest文件名不能换
- conftest.py与运行的用例要写在同一个package下,并且有init/py文件
- 不需要import导入conftest.py,pytest用例会自动查找
- 全局的配置和前期工作都可以写在这里,放在某个包下,就是这个包数据共享的地方
例:前端自动化中应用——yield
- 场景:使用fixture可以将测试方法前要执行的或依赖的解决了,测试方法后销毁清除数据的要如何进行呢?范围是模块级别的。类似setupClass
- 解决:
通过在同一模块中加入yield关键字,yield是调用第一次返回结果,第二次执行它下面的语句返回 - 步骤:
在方法前面加上pytest.fixture(scope=module) - 在登录的方法中加yield,之后加销毁清楚的步骤注意,这种方式没有返回值,如果希望返回使用addfinalizer
多线程并行与分布式执行
- 场景:测试用例1000条,一个用例执行1分钟,1个测试人员执行需要1000分钟,通常我们会用人力成本换取时间成本,加几个人一起执行,时间就会缩短。如果10个人一起执行只需要100分钟,这就是一种并行测试,分布式场景
- 解决:pytest分布式执行插件,多个CPU或主机执行。前提:用例之间都是独立的,没有先后顺序,随机都能执行,可重复运行不影响其他用例
- 安装:
- pip3 install pytest-xdist
- 多个CPU并行执行用例,直接加-n 3是并行数量:pytest -n 3
- 在多个终端下一起执行
pytest-html生成报告
- 安装:
pip install pytest-html
生成html报告
pytest -v -s --html=report.html--self-contained-html
pytest参数化
@pytest.mark.parametrize(argnames,argvalues)
- argnames:要参数化的变量,string(逗号分隔),list,tuple
- argvalues:参数化的值,list,list[tuple]
使用string
@pytest.mark.parametrize("a,b",[(10,20),(10,30)])
def test_param(self,a,b)
print(a+b)
使用list
使用tuple
yaml数据参数化
- yaml实现list:-10 -20 -30
- yaml实现字典:key:value
- yaml进行嵌套
- 加载yaml文件:
- yaml.safe_load(open("./data.yaml"))
- pytest与yaml连用:
@pytest.mark.parametrize(["a","b"],yaml.safe_load(open("./data.yaml")))
def test_param(self,a,b):
print(a+b)
(yaml内容是个二位数组)