easy start 直接运行pytest
1 2 3 4 5 6 def inc (x ): return x + 1 def test_answer (): assert inc(3 ) == 5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $ pytest ========== test session starts ======== platform linux -- Python 3. x.y, pytest-4. x.y, py-1. x.y, pluggy-0. x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 1 item test_sample.py F [100 %] ============= FAILURES ============= ___________ test_answer _____________ def test_answer (): > assert inc(3 ) == 5 E assert 4 == 5 E + where 4 = inc(3 ) test_sample.py:6 : AssertionError ========== 1 failed in 0.12 seconds ==========
运行多个文件
在没有定制的默认情况下 的缺省规则
pytest会找当前以及递查找子文件夹下面所有的test_*.py或*_test.py的文件,把其当作测试文件
在这些文件里,pytest会收集下面的一些函数或方法,当作测试用例
不在类定义中的以test_开头的函数或方法
在以Test开头的类中(不能包含__init__方法),以test_开头的方法
pytest也支持unittest模式的用例定义
Assert Assert就是断言,每个测试用例都需要断言。
与unittest不同,pytest使用的是python自带的assert关键字来进行断言,大大降低了学习成本。
assert关键字后面可以接一个表达式,只要表达式的最终结果为True,那么断言通过,用例执行成功,否则用例执行失败。
Fixture 可以把Fixture理解为准备测试数据和初始化测试对象的阶段。
一般我们对测试数据和测试对象的管理有这样的一些场景
所有用例开始之前初始化测试数据或对象
所有用例结束之后销毁测试数据或对象
每个用例开始之前初始化测试数据或对象
每个用例结束之后销毁测试数据或对象
在每个/所有module的用例开始之前初始化数据或对象
在每个/所有module的用例开始之后销毁数据或对象
……
举例1
1 2 3 4 5 6 7 8 9 10 11 12 import pytest@pytest.fixture def smtp_connection (): import smtplib return smtplib.SMTP("smtp.gmail.com" , 587 , timeout=5 ) def test_ehlo (smtp_connection ): response, msg = smtp_connection.ehlo() assert response == 250 assert 0
这里test_ehlo需要smtp_connection 的fixture值,pytest将会发现并且调用@pytest.fixture 标志着smtp_connection fixture函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 $ pytest test_smtpsimple.py ============ test session starts =============== platform linux -- Python 3. x.y, pytest-4. x.y, py-1. x.y, pluggy-0. x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 1 item test_smtpsimple.py F [100 %] =========== FAILURES ================= ___________ test_ehlo ________________ smtp_connection = <smtplib.SMTP object at 0xdeadbeef > def test_ehlo (smtp_connection ): response, msg = smtp_connection.ehlo() assert response == 250 > assert 0 E assert 0 test_smtpsimple.py:11 : AssertionError ======== 1 failed in 0.12 seconds ========
举例2
考虑一场景,需要判断用户的密码中包含简单密码,密码必须至少6位,满足6位后判断用户的密码不是password123或者password之类的弱密码。
我们将用户的信息导出成名为users.dev.json的文件,该文件如下所示
1 2 3 4 5 [ {"name" :"jack" ,"password" :"Iloverose" }, {"name" :"rose" ,"password" :"Ilovejack" }, {"name" :"tom" ,"password" :"password123" }, ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import pytestimport jsonclass TestUserPassword (object ): @pytest.fixture def users (self ): return json.loads(open ('./users.dev.json' , 'r' ).read()) def test_user_password (self, users ): for user in users: passwd = user['password' ] assert len (passwd) >= 6 msg = "user %s has a weak password" %(user['name' ]) assert passwd != 'password' , msg assert passwd != 'password123' , msg
运行
pytest可以通过指定文件名的方式运行单个用例文件,这里我们只运行test_user_password.py文件
1 pytest test_user_password.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 $ pytest test_user_password.py ============= test session starts ================ platform darwin -- Python 2.7 .12 , pytest-3.2 .3 , py-1.4 .34 , pluggy-0.4 .0 rootdir: /Users/easonhan/code/testclass.net/src/pytest, inifile: collected 1 item test_user_password.py F ================ FAILURES ================ ________________ TestUserPassword.test_user_password __________ self = <test_user_password.TestUserPassword object at 0x1046e3290 >users = [{'name' : 'jack' , 'password' : 'Iloverose' }, {'name' : 'rose' , 'password' : 'Ilovejack' }, {'name' : 'tom' , 'password' : 'password123' }] def test_user_password (self, users ): for user in users: passwd = user['password' ] assert len (passwd) >= 6 msg = "user %s has a weak password" %(user['name' ]) assert passwd != 'password' , msg > assert passwd != 'password123' , msg E AssertionError: user tom has a weak password E assert 'password123' != 'password123' test_user_password.py:14 : AssertionError =============== 1 failed in 0.03 seconds ============
分析
使用@pytest.fixture装饰器可以定义feature
在用例的参数中传递fixture的名称以便直接调用fixture,拿到fixture的返回值
3个assert是递进关系,前1个assert断言失败后,后面的assert是不会运行的,因此重要的assert放到前面
E AssertionError: user tom has a weak password可以很容易的判断出是哪条数据出了问题,所以定制可读性好的错误信息是很必要的
任何1个断言失败以后,for循环就会退出,所以上面的用例1次只能发现1条错误数据,换句话说任何1个assert失败后,用例就终止运行了
执行顺序
pytest是这样运行上面的用例的
pytest找到以test_开头的方法,也就是test_user_password方法,执行该方法时发现传入的参数里有跟fixture users名称相同的参数
pytest认定users是fixture,执行该fixture,读取json文件解析成dict实例
test_user_password方法真正被执行,users fixture被传入到该方法
举例3
例2中任何1条测试数据导致断言不通过后测试用例就会停止运行,这样每次只能检查出1条不符合规范的数据,很多时候需要一次性把所有的不符合结果都测出来。
users.test.json文件:
1 2 3 4 5 6 7 [ {"name" :"jack" ,"password" :"Iloverose" }, {"name" :"rose" ,"password" :"Ilovejack" } {"name" :"tom" ,"password" :"password123" }, {"name" :"mike" ,"password" :"password" }, {"name" :"james" ,"password" :"AGoodPasswordWordShouldBeLongEnough" } ]
参数化fixture
参数化fixture允许我们向fixture提供参数,参数可以是list,该list中有几条数据,fixture就会运行几次,相应的测试用例也会运行几次。
参数化fixture的语法是
1 @pytest.fixture(params=["smtp.gmail.com" , "mail.python.org" ] )
其中len(params)的值就是用例执行的次数
在fixture的定义中,可以使用request.param来获取每次传入的参数,如下:
1 2 3 4 5 6 7 @pytest.fixture(scope="module" , params=["smtp.gmail.com" , "mail.python.org" ] )def smtp (request ): smtp = smtplib.SMTP(request.param, 587 , timeout=5 ) yield smtp print ("finalizing %s" % smtp) smtp.close()
上面的代码smtp fixture会执行2次
第1次request.param == 'smtp.gmail.com'
第2次request.param == 'mail.python.org'
实现用例
我们现在使用参数化fixtures来实现一次性检查出弱密码的用例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import pytestimport jsonusers = json.loads(open ('./users.test.json' , 'r' ).read()) class TestUserPasswordWithParam (object ): @pytest.fixture(params=users ) def user (self, request ): return request.param def test_user_password (self, user ): passwd = user['password' ] assert len (passwd) >= 6 msg = "user %s has a weak password" %(user['name' ]) assert passwd != 'password' , msg assert passwd != 'password123' , msg
上面的例子里,我们先把所有用户信息读到users变量里,注意users这时候是list类型,可以直接传入到fixture的params
运行
1 pytest test_user_password_with_params.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 $ pytest test_user_password_with_params.py ================== test session starts ==================== platform darwin -- Python 2.7 .12 , pytest-3.2 .3 , py-1.4 .34 , pluggy-0.4 .0 rootdir: /Users/easonhan/code/testclass.net/src/pytest, inifile: collected 5 items test_user_password_with_params.py ..FF. ====================== FAILURES ========================= ______ TestUserPasswordWithParam.test_user_password[user2] ____ self = <test_user_password_with_params.TestUserPasswordWithParam object at 0x10de1d790 >, user = {'name' : 'tom' , 'password' : 'password123' } def test_user_password (self, user ): passwd = user['password' ] assert len (passwd) >= 6 msg = "user %s has a weak password" %(user['name' ]) assert passwd != 'password' , msg > assert passwd != 'password123' , msg E AssertionError: user tom has a weak password E assert 'password123' != 'password123' test_user_password_with_params.py:15 : AssertionError _____ TestUserPasswordWithParam.test_user_password[user3]_______ self = <test_user_password_with_params.TestUserPasswordWithParam object at 0x10de1df50 >, user = {'name' : 'mike' , 'password' : 'password' } def test_user_password (self, user ): passwd = user['password' ] assert len (passwd) >= 6 msg = "user %s has a weak password" %(user['name' ]) > assert passwd != 'password' , msg E AssertionError: user mike has a weak password E assert 'password' != 'password' test_user_password_with_params.py:14 : AssertionError ======== 2 failed, 3 passed in 0.05 seconds =============
总共运行了5个用例,3个成功,2个失败。
举例4
@pytest.mark.parametrize 装饰器可以让我们每次参数化fixture的时候传入多个项目,parametrize每次多个参数,更加灵活。
1 2 3 4 5 6 7 8 import pytest@pytest.mark.parametrize("test_input,expected" , [ ("3+5" , 8 ), ("2+4" , 6 ), ("6*9" , 42 ), ] )def test_eval (test_input, expected ): assert eval (test_input) == expected
test_eval方法中传入了2个参数,就如同@pytest.mark.parametrize装饰器中定义的那样,因此简单理解,我们可以把parametrize装饰器想象成是数据表格,有表头(test_input,expected)以及具体的数据。
运行结果
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 $ pytest ======= test session starts ======== platform linux -- Python 3. x.y, pytest-3. x.y, py-1. x.y, pluggy-0. x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 3 items test_expectation.py ..F ======= FAILURES ======== _______ test_eval[6 *9 -42 ] ________ test_input = '6*9' , expected = 42 @pytest.mark.parametrize("test_input,expected" , [ ("3+5" , 8 ), ("2+4" , 6 ), ("6*9" , 42 ), ] ) def test_eval (test_input, expected ): > assert eval (test_input) == expected E AssertionError: assert 54 == 42 E + where 54 = eval ('6*9' ) test_expectation.py:8 : AssertionError ======= 1 failed, 2 passed in 0.12 seconds ========
转载自:http://www.testclass.net/pytest