0 写在前面
距离辞去上一份实习已经过去了6天,为了来到华泰也下了好大决心,到底能学到多少东西,还需时间来给出答案。体检完已经11点20了,打了电话给实习导师王工,很热情的山东老哥,给人特别亲切的感觉。聊了一会儿发现不仅仅是校友,最骚的竟然是老费的师弟(吃惊。听说华泰的食堂巨好吃而且免费,很遗憾今天工卡没下来,王工请我去公司外面吃了顿小炒,也大致交流了一下现在项目组正在做的事情。目前要开发一个平台和一些算法包,我目前要做的就是做测试部分,完成代码的自动测试并自动发送测试结果到指定邮箱。之前完全没了解过pytest,王工说一周肯定能玩的转,那加油吧宇酱!!
1.1 pytest测试发现规则
在批量执行用例之前,我们需要了解一下pytest的潜规则,注意,由于pytest可以支持丰富的定制选项,下面的潜规则是在没有定制的默认情况下的缺省规则
- pytest会找当前以及递查找子文件夹下面所有的test_.py或_test.py的文件,把其当作测试文件
在这些文件里 - pytest会收集下面的一些函数或方法,当作测试用例,包括:
- 不在类定义中的以test_开头的函数或方法
- 在以Test开头的类中(不能包含__init__方法),以test_开头的方法
- pytest也支持unittest模式的用例定义
1.2 多个文件运行实例
新建test_calc.py和test_quick_start.py文件放在同一文件夹下
test_calc.py
def add(x, y):
return x + y
def test_add():
assert add(1, 0) == 1
assert add(1, 1) == 2
assert add(1, 99) == 100
test_quick_start.py
def reverse(string):
return string[::-1]
def test_reverse():
string = "good"
assert reverse(string) == "doog"
another_string = "itest"
assert reverse(another_string) == "tseti"
在终端下进入目标文件夹,敲pytest命令:
在这个demo中:
- 所有以test_开头的py文件都被当成了测试文件
- 所有测试文件中以test开头的方法被当成了测试用例执行
2 为失败的assert定义自己的解释
- 新建conftest.py文件,在conftest .py文件中添加下面的hook,它为Foo对象提供了另一种解释:
from test_foocompare import Foo
def pytest_assertrepr_compare(op, left, right):
if isinstance(left, Foo) and isinstance(right, Foo) and op == "==":
return ["Comparing Foo instances:", " vals: %s != %s" % (left.val, right.val)]
- 再建立test代码块:
class Foo(object):
def __init__(self, val):
self.val = val
def __eq__(self, other):
return self.val == other.val
def test_compare():
f1 = Foo(1)
f2 = Foo(2)
assert f1 == f2
终端测试此时报的异常已经比较了f1和f2的value结果为:(如果不自定义异常会比较f1f2的地址)
3 fixture部分
在编写测试函数的时候,可以将此函数名称做为传入参数,pytest将会以依赖注入方式,将该函数的返回值作为测试函数的传入参数。fixture是基于模块来执行的,每个fixture的名字就可以触发一个fixture的函数,它自身也可以调用其他的fixture。 我们可以把fixture看做是资源,在你的测试用例执行之前需要去配置这些资源,执行完后需要去释放资源。比如module类型的fixture,适合于那些许多测试用例都只需要执行一次的操作。 fixture还提供了参数化功能,根据配置和不同组件来选择不同的参数。 fixture主要的目的是为了提供一种可靠和可重复性的手段去运行那些最基本的测试内容。比如在测试网站的功能时,每个测试用例都要登录和退出,利用fixture就可以只做一次,否则每个测试用例都要做这两步也是冗余。
3.1 fixture demo
考虑这种场景,我们需要判断用户的密码中包含简单密码,规则是这样的,密码必须至少6位,满足6位的话判断用户的密码不是password123或者password之类的弱密码。
将用户的信息导出成名为users.dev.json的文件,该文件如下所示
[
{"name":"jack","password":"Iloverose"},
{"name":"rose","password":"Ilovejack"},
{"name":"tom","password":"password123"}
]
新建名为test_user_password.py的文件,键入以下内容,一定要保证users.dev.json文件与该文件在同一路径下
import pytest
import json
class TestUserPassword(object):
@pytest.fixture
def users(self):
return json.loads(open('./users.dev.json', 'r').read()) # 读取当前路径下的users.dev.json文件,返回的结果是dict
def test_user_password(self, users):
# 遍历每条user数据
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
运行结果:
3.2 demo分析
- 使用@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失败后,用例就终止运行了
3.3 demo运行步骤
pytest是这样运行上面的用例的
- pytest找到以test_开头的方法,也就是test_user_password方法,执行该方法时发现传入的参数里有跟fixture users名称相同的参数
- pytest认定users是fixture,执行该fixture,读取json文件解析成dict实例
- test_user_password方法真正被执行,users fixture被传入到该方法
3.4 跨类、模块、会话的共享fixture
在conftext.py中输入以下代码:
# content of conftest.py
import pytest
import smtplib
@pytest.fixture(scope="module")
def smtp_connection():
return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
加了(scope=“module”)的fixture可以在同目录下的test中调用
4 setup和teardown(前置和后置)
4.1 用例运行级别
-
模块级(setup_module/teardown_module)开始于模块始末,全局的
-
函数级(setup_function/teardown_function)只对函数用例生效(不在类中)
-
类级(setup_class/teardown_class)只在类中前后运行一次(在类中)
-
方法级(setup_method/teardown_method)开始于方法始末(在类中)
-
类里面的(setup/teardown)运行在调用方法的前后
4.2 函数式
(下班啦!)