Pytest简单介绍
(pytest是python的一个测试框架,主要是用来进行一些小的测试)
- 安装:pip install -U pytest
- 查看是否安装成功:pytest --version
- 运行:在当前文件所在目录下执行pytest,会寻找当前目录以及子目录下以test开头的py文件或者以test结尾的py文件,找到文件后,在文件中找到以test开头函数并执行。(或者执行pytest 文件名--这样可以指定某个文件进行pytest的测试 或者 python -m pytest xxx.py)
在执行pytest xxx.py时,若增加参数:pytest -v -s xxx.py (-v:显示运行的函数;-s:显示内部的打印信息)
- 编写pytest测试样例的规则:
- 测试文件以test_开头
- 测试类以Test开头,并且不能带有init方法
- 测试函数以test_开头
- 断言使用基本的assert即可
- 断言--正常:
assert value == 0
- 断言--异常(pytest.raise方法):
1 #断言1除以0,将产生一个ZeroDivisionError类型的异常。 2 import pytest 3 def test_zero_division(): 4 with pytest.raises(ZeroDivisionError): 5 1 / 0 6 7 #有的时候,我们可能需要在测试中用到产生的异常中的某些信息,比如异常的类型type,异常的值value等等。下面我们修改下上面的测试: 8 import pytest 9 def test_recursion_depth(): 10 with pytest.raises(ZeroDivisionError) as excinfo: 11 1/0 12 assert excinfo.type == 'RuntimeError' 13 #因为该测试断言产生的异常类型是RuntimeError,而实际上产生的异常类型是ZeroDivisionError,所以测试失败了。在测试结果中,可以看到assert子表达式excinfo.type的值。
- 在test前,可以通过增加skip或xfail进行跳过(失败)测试案例
1 import pytest 2 3 # @pytest.mark.skipif() 跳过 4 # @pytest.mark.skip() 跳过 5 # @pytest.mark.xfail() 若出错跳过,pass也跳过,但会显示pass 6 def test_123(): 7 # pytest.skip() 也可以实现'跳过'目的 8 assert 1 == 0
- 简单例子:
1 # coding:utf-8 2 import pytest 3 4 def fun(x): 5 return x + 1 6 7 def test_fun(): 8 assert fun(2) == 3
结果:
(venvX) F: est_jiaxin>pytest test_para1.py ============================= test session starts ============================= platform win32 -- Python 2.7.9, pytest-3.7.1, py-1.5.3, pluggy-0.7.1 rootdir: F: est_jiaxin, inifile: plugins: allure-adaptor-1.7.10 collected 1 item test_para1.py . [100%] ========================== 1 passed in 0.09 seconds ===========================
pytest之feature-scope
- function:每个test都运行,默认是function的scope.
- class:每个class的所有test只运行一次.
- module:每个module的所有test只运行一次.
- session:每个session只运行一次.
比如你的所有test都需要连接同一个数据库,那可以设置为module,只需要连接一次数据库,对于module内的所有test,这样可以极大的提高运行效率。
代码:
1 @pytest.fixture(scope="module") 2 def hero_backup_policy(self, acmp_cfg): 3 return AcloudBackupPolicy(acmp_cfg) 4 5 @pytest.fixture(scope="function") 6 def hero_acloud_backup_policy(self, acmp_cfg): 7 return Server(acmp_cfg)
pytest的参数化方式
-
pytest.fixture()方式进行参数化,fixture装饰的函数可以作为参数传入其他函数
-
conftest.py 文件中存放参数化函数,可作用于模块内的所有测试用例
-
pytest.mark.parametrize()方式进行参数化
待测试代码片段:(is_leap_year.py)
1 # coding:utf-8 2 def is_leap_year(year): 3 # 先判断year是不是整型 4 if isinstance(year, int) is not True: 5 raise TypeError("传入的参数不是整数") 6 elif year == 0: 7 raise ValueError("公元元年是从公元一年开始!!") 8 elif abs(year) != year: 9 raise ValueError("传入的参数不是正整数") 10 elif (year % 4 == 0 and year % 100 != 0) or year % 400 == 0: 11 print"%d年是闰年" % year 12 return True 13 else: 14 print"%d年不是闰年" % year 15 return False
-
pytest.fixture()
-
pytest.fixture()中传入的参数为list,用例执行时,遍历list中的值,每传入一次值,则相当于执行一次用例。
-
@pytest.fixture()装饰的函数中,传入了一个参数为request。
-
这里的测试数据是直接存在list中的,能否存入json文件或者xml文件再进行读取转换为list呢?
代码:
1 # coding:utf-8 2 import is_leap_year 3 import pytest 4 5 class TestPara(): 6 is_leap = [4, 40, 400, 800, 1996, 2996] 7 is_not_leap = [1, 100, 500, 1000, 1999, 3000] 8 is_valueerror = [0, -4, -100, -400, -1996, -2000] 9 is_typeerror = ['-4', '4', '100', 'ins', '**', '中文'] 10 11 @pytest.fixture(params=is_leap) 12 def is_leap(self, request): 13 return request.param 14 15 @pytest.fixture(params=is_typeerror) 16 def is_typeerror(self, request): 17 return request.param 18 19 def test_is_leap(self, is_leap): 20 assert is_leap_year.is_leap_year(is_leap) == True 21 22 def test_is_typeerror(self, is_typeerror): 23 with pytest.raises(TypeError): 24 is_leap_year.is_leap_year(is_typeerror)
-
conftest.py 文件 -- 测试数据与用例分离
- 采用conftest.py文件存储参数化数据和函数,模块下的用例执行时,会自动读取conftest.py文件中的数据。
代码:
conftest.py文件
1 # coding:utf-8 2 import pytest 3 4 is_leap = [4, 40, 400, 800, 1996, 2996] 5 is_not_leap = [1, 100, 500, 1000, 1999, 3000] 6 is_valueerror = [0, -4, -100, -400, -1996, -2000] 7 is_typeerror = ['-4', '4', '100', 'ins', '**', '中文'] 8 9 @pytest.fixture(params=is_leap) 10 def is_leap(request): 11 return request.param 12 13 @pytest.fixture(params=is_typeerror) 14 def is_typeerror(request): 15 return request.param
test_para.py文件
1 # coding:utf-8 2 3 import is_leap_year 4 import pytest 5 6 class TestPara(): 7 def test_is_leap(self, is_leap): 8 assert is_leap_year.is_leap_year(is_leap) == True 9 10 def test_is_typeerror(self, is_typeerror): 11 with pytest.raises(TypeError): 12 is_leap_year.is_leap_year(is_typeerror)
-
pytest.mark.parametrize() -- 进行参数化
- 采用标记函数参数化,传入单个参数,pytest.mark.parametrize("参数名",lists)。
- 采用标记函数传入多个参数,如pytest.mark.parametrize("para1, para2", [(p1_data_0, p2_data_0), (p1_data_1, p2_data_1),...]。
- 这里:测试用例中传入2个参数,year和期望结果,使输入数据与预期结果对应,构造了2组会失败的数据,在执行结果中,可以看到失败原因。
代码:
1 # coding:utf-8 2 import is_leap_year 3 import pytest 4 5 class TestPara(): 6 # 参数传入year中 7 @pytest.mark.parametrize('year, expected', [(1, False), (4,True), (100, False), (400, True), (500, True)]) 8 def test_is_leap(self, year, expected): 9 assert is_leap_year.is_leap_year(year) == expected 10 11 @pytest.mark.parametrize('year, expected', [(0, ValueError),('-4', TypeError), (-4, ValueError), ('ss', TypeError), (100.0, ValueError)]) 12 def test_is_typeerror(self, year, expected): 13 if expected == ValueError: 14 with pytest.raises(ValueError) as excinfo: 15 is_leap_year.is_leap_year(year) 16 assert excinfo.type == expected 17 else: 18 with pytest.raises(TypeError) as excinfo: 19 is_leap_year.is_leap_year(year) 20 assert excinfo.type == expected
参考链接:https://blog.csdn.net/zhusongziye/article/details/79902772
pytest-fixture扩展内容
1. 把一个函数定义为Fixture很简单,只能在函数声明之前加上“@pytest.fixture”。其他函数要来调用这个Fixture,只用把它当做一个输入的参数即可。
1 import pytest 2 3 @pytest.fixture() 4 def before(): 5 print ' before each test' 6 7 def test_1(before): 8 print 'test_1()' 9 10 def test_2(before): 11 print 'test_2()'
result:
(venvX) F: est_jiaxin>pytest -v -s test_compute.py ============================= test session starts ============================= platform win32 -- Python 2.7.9, pytest-3.7.1, py-1.5.3, pluggy-0.7.1 -- f:projectssandomvenvxscriptspython.exe cachedir: .pytest_cache rootdir: F: est_jiaxin, inifile: plugins: allure-adaptor-1.7.10 collected 2 items test_compute.py::test_1 before each test test_1() PASSED test_compute.py::test_2 before each test test_2() PASSED ========================== 2 passed in 0.17 seconds ===========================
2. 进行封装
1 import pytest 2 3 @pytest.fixture() 4 def before(): 5 print ' before each test' 6 7 # 每个函数前声明 8 @pytest.mark.usefixtures("before") 9 def test_1(): 10 print 'test_1()' 11 @pytest.mark.usefixtures("before") 12 def test_2(): 13 print 'test_2()' 14 15 #封装在类里,类里的每个成员函数声明 16 class Test1: 17 @pytest.mark.usefixtures("before") 18 def test_3(self): 19 print 'test_1()' 20 @pytest.mark.usefixtures("before") 21 def test_4(self): 22 print 'test_2()' 23 24 #封装在类里在前声明 25 @pytest.mark.usefixtures("before") 26 class Test2: 27 def test_5(self): 28 print 'test_1()' 29 def test_6(self): 30 print 'test_2()'
result:
(venvX) F: est_jiaxin>pytest -v -s test_compute.py ============================= test session starts ============================= platform win32 -- Python 2.7.9, pytest-3.7.1, py-1.5.3, pluggy-0.7.1 -- f:projectssandomvenvxscriptspython.exe cachedir: .pytest_cache rootdir: F: est_jiaxin, inifile: plugins: allure-adaptor-1.7.10 collected 6 items test_compute.py::test_1 before each test test_1() PASSED test_compute.py::test_2 before each test test_2() PASSED test_compute.py::Test1::test_3 before each test test_1() PASSED test_compute.py::Test1::test_4 before each test test_2() PASSED test_compute.py::Test2::test_5 before each test test_1() PASSED test_compute.py::Test2::test_6 before each test test_2() PASSED ========================== 6 passed in 0.11 seconds ===========================
3. fixture还可以带参数,可以把参数赋值给params,默认是None。对于param里面的每个值,fixture都会去调用执行一次,就像执行for循环一样把params里的值遍历一次。
1 import pytest 2 3 @pytest.fixture(params=[1, 2, 3]) 4 def test_data(request): 5 return request.param 6 7 def test_not_2(test_data): 8 print('test_data: %s' % test_data) 9 assert test_data != 2
result:
(venvX) F: est_jiaxin>pytest -v -s test_compute.py ============================= test session starts ============================= platform win32 -- Python 2.7.9, pytest-3.7.1, py-1.5.3, pluggy-0.7.1 -- f:projectssandomvenvxscriptspython.exe cachedir: .pytest_cache rootdir: F: est_jiaxin, inifile: plugins: allure-adaptor-1.7.10 collected 3 items test_compute.py::test_not_2[1] test_data: 1 PASSED test_compute.py::test_not_2[2] test_data: 2 FAILED test_compute.py::test_not_2[3] test_data: 3 PASSED ================================== FAILURES =================================== ________________________________ test_not_2[2] ________________________________ test_data = 2 def test_not_2(test_data): print('test_data: %s' % test_data) > assert test_data != 2 E assert 2 != 2 test_compute.py:64: AssertionError ===================== 1 failed, 2 passed in 0.12 seconds ======================