安装
直接执行 如下命令即可安装 pytest
pip install pytest
我们还需要产生测试报表,所以要安装一个第三方插件 pytest-html ,执行如下命令安装
pip install pytest-html
重大BUG:目录级别用例清除,不一按是在目录结束清除
快速上手
pytest 如何知道你哪些代码是自动化的测试用例?
官方文档 给出了 pytest 寻找 测试项 的 具体规则
如果未指定命令行参数,则从 testpath(如果已配置)或当前目录开始收集。
如果命令行参数, 指定了 目录、文件名 或 node id 的任何组合,则按参数来找
寻找过程会递归到目录中,除非它们匹配上 norecursedirs。
文件 :
test_*.py或*_test.py。测试项:
- test为前缀 的
函数 - Test为前缀的
类里面的 test 为前缀的方法
- test为前缀 的
初始化清除
对自动化测试框架来说,初始化清除功能 至关重要。
模块级别
模块级别 的初始化、清除 分别 在整个模块的测试用例 执行前后执行,并且 只会执行1次 。
如下定义 setup_module 和 teardown_module 全局函数
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_C001021(self):
print('\n用例C001021')
assert 1 == 1
def test_C001022(self):
print('\n用例C001022')
assert 2 == 2
执行命令 pytest cases -s ,运行结果如下
collected 5 items
cases\登录\test_错误登录.py
*** 初始化-模块 ***
用例C001001
.
用例C001002
.
用例C001003
F
用例C001021
.
用例C001022
.
*** 清除-模块 ***
可以发现,模块级别的初始化、清除 在 整个模块所有用例 执行前后 分别 执行1次
它主要是用来为该 模块 中 所有的测试用例做 公共的 初始化 和 清除
类级别
类级别` 的初始化、清除 分别 在整个类的测试用例 执行前后执行,并且 `只会执行1次
如下定义 setup_class 和 teardown_class 类方法
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 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_C001021(self):
print('\n用例C001021')
assert 1 == 1
def test_C001022(self):
print('\n用例C001022')
assert 2 == 2
执行命令 pytest cases -s ,运行结果如下
collected 5 items
cases\登录\test_错误登录.py
*** 初始化-模块 ***
=== 初始化-类 ===
用例C001001
.
用例C001002
.
用例C001003
F
=== 清除 - 类 ===
用例C001021
.
用例C001022
.
*** 清除-模块 ***
可以发现,类级别的初始化、清除 在 整个模块所有用例 执行前后 分别 执行1次 。
它主要是用来为该 类 中的所有测试用例做 公共的 初始化 和 清除
方法级别
方法级别 的初始化、清除 分别 在类的 每个测试方法 执行前后执行,并且 每个用例分别执行1次
如下定义 setup_method 和 teardown_method 实例方法
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_C001021(self):
print('\n用例C001021')
assert 1 == 1
def test_C001022(self):
print('\n用例C001022')
assert 2 == 2
执行命令 pytest cases -s ,运行结果如下
collected 5 items
cases\登录\test_错误登录.py
*** 初始化-模块 ***
=== 初始化-类 ===
--- 初始化-方法 ---
用例C001001
.
--- 清除 -方法 ---
--- 初始化-方法 ---
用例C001002
.
--- 清除 -方法 ---
--- 初始化-方法 ---
用例C001003
F
--- 清除 -方法 ---
=== 清除 - 类 ===
用例C001021
.
用例C001022
.
*** 清除-模块 ***
可以发现,方法别的初始化、清除 在 整个模块所有用例 执行前后 分别 执行一次
目录级别
目标级别的 初始化清除,就是针对整个目录执行的初始化、清除。
我们在需要初始化的目录下面创建 一个名为 conftest.py 的文件,里面内容如下所示
import pytest
@pytest.fixture(scope='package',autouse=True)
def st_emptyEnv():
print(f'\n#### 初始化-目录甲')
yield
print(f'\n#### 清除-目录甲')
注意:这里清除环境的代码就是 yield 之后的代码。 这是一个生成器,具体的说明参见视频讲解。
我们可以在多个目录下面放置这样的文件,定义该目录的初始化清除。
pytest 在执行测试时,会层层调用。
但是我发现了pytest一个重要的bug: 清除操作并不一定会在该目录最后一个测试用例执行完进行调用。
在我看来,这个问题是非常大的。
因为,一个目录下的用例执行完后,该清除的数据没有清除,这可能会导致其他目录下的用例执行失败。
所以在这个问题解决前,白月黑羽推荐大家先不要使用这种 目录级别 的初始化清除。
数据驱动
用例 UI-0001 到 UI-0005 这5个登录的测试用例,共同的特点是,它们测试步骤是一模一样的,只是输入的数据(用户名、密码)不同,要检查的输出数据(错误提示)不同。
这批测试用例,就是典型的 可以用 数据驱动 方式进行自动化的用例。
如果有一批测试用例,具有 相同的测试步骤 ,只是 测试参数数据不同 。
自动化测试时,把测试数据从用例代码中 分离 开来,以后增加新的测试用例,只需要修改数据。
这就是数据驱动。
这种情况可以使用 pytest 用例 的 数据驱动格式,只需如下定义即可
class Test_错误登录:
@pytest.mark.parametrize('username, password, expectedalert', [
(None, '88888888', '请输入用户名'),
('byhy', None, '请输入密码'),
('byh', '88888888', '登录失败 : 用户名或者密码错误'),
('byhy', '8888888', '登录失败 : 用户名或者密码错误'),
('byhy', '888888888', '登录失败 : 用户名或者密码错误'),
]
)
def test_UI_0001_0005(self, username, password, expectedalert):
alertText = loginAndCheck(username, password)
assert alertText == expectedalert
这样,我们就不需要定义那么多的测试用例方法了, 而且测试数据也可以集中存放。
更多细节,参考pytest官方文档
挑选用例执行
pytest 可以灵活的挑选测试用例执行
指定一个模块
可以像这样只挑选一个模块执行
pytest cases\登录\test_错误登录.py
指定目录
可以像这样只挑选一个目录执行
pytest cases
也可以指定多个目录
pytest cases1 cases2\登录
指定模块里面的函数或者类
指定一个类
pytest cases\登录\test_错误登录.py::Test_错误密码
也可以指定类里面的方法
pytest cases\登录\test_错误登录.py::Test_错误密码::test_C001001
根据名字
可以使用 命令行参数 -k 后面加名字来挑选要执行的测试项
比如像这样后面跟测试函数名字的一部分:
pytest -k C001001 -s
注意,-k 后面的名字
可以是测试函数的名字,可以是类的名字,可以是模块文件名,可以是目录的名字
是大小写敏感的
不一定要完整,只要能有部分匹配上就行
可以用 not 表示选择名字中不包含,比如
pytest -k "not C001001" -s可以用 and 表示选择名字同时包含多个关键字,比如
pytest -k "错 and 密码2" -s可以用 or 表示选择名字 包含指定关键字之一即可,比如
pytest -k "错 or 密码2" -s
根据标签
可以这样给 某个方法加上标签 webtest
import pytest
class Test_错误密码2:
@pytest.mark.webtest
def test_C001021(self):
print('\n用例C001021')
assert 1 == 1
然后,可以这样运行指定标签的用例
pytest cases -m webtest -s
也可以这样给整个类加上标签
@pytest.mark.webtest
class Test_错误密码2:
def test_C001021(self):
print('\n用例C001021')
assert 1 == 1
当然标签也支持中文
@pytest.mark.网页测试
class Test_错误密码2:
def test_C001021(self):
print('\n用例C001021')
assert 1 == 1
然后,运行命令行指定标签
pytest cases -m 网页测试 -s
可以这样同时添加多个标签
@pytest.mark.网页测试
@pytest.mark.登录测试
class Test_错误密码2:
def test_C001021(self):
print('\n用例C001021')
assert 1 == 1
可以这样定义一个全局变量 pytestmark 为 整个模块文件 设定标签
import pytest
pytestmark = pytest.mark.网页测试
如果你需要定义多个标签,可以定义一个列表
import pytest
pytestmark = [pytest.mark.网页测试, pytest.mark.登录测试]
运行测试
执行测试非常简单,打开命令行窗口,进入自动化项目根目录(我们这里就是 autotest),执行命令程序 pytest 即可
上面的示例执行结果如下

显示找到3个测试项,2个执行通过,1个不通过。
通过的用例 是用一个绿色小点表示, 不通过的用例用一个红色的F表示
并且会在后面显示具体不通过的用例 和不通过的检查点 代码细节。
大家可以发现,用例代码中有些打印语句没有显示出内容。
因为pytest 会 截获print打印的内容。
如果我们希望 显示测试代码中print的内容,因为这些打印语句在调试代码时很有用,可以加上命令行参数 -s
如下
pytest -s
如果我们希望得到更详细的执行信息,包括每个测试类、测试函数的名字,可以加上参数 -v,这个参数可以和 -s 合并为 -sv
如下
pytest -sv
执行 pytest 时, 如果命令行没有指定目标目录 或者 文件, 它会自动搜索当前目录下所有符合条件的文件、类、函数。
所以上面,就找到了3个测试方法,对应3个用例。
我们目前 项目根目录 中 只有一个cases 目录用例存放测试用例, 将来还会有其他目录,比如:
lib目录存放库代码、cfg目录存放配置数据 等等。
为了防止 pytest 到其他目录中找测试用例项,执行测试时,我们可以在命令行加上目标目录 cases ,就是这样
pytest cases
产生报告
前面在安装pytest,我们也安装了 pytest-html 插件,这个插件就是用来产生测试报告的。
要产生报告,在命令行加上 参数 --html=report.html --self-contained-html ,如下
pytest cases --html=report.html --self-contained-html
这样就会产生名为 report.html 的测试报告文件,可以在浏览器中打开
但是这个工具有个bug,导致测试目录、文件、类名 中,如果有中文,显示为乱码
可以这样修复:
打开该插件对应的代码文件,通常在解释器目录下:site-packages\pytest_html\plugin.py
找到如下代码
class TestResult: def __init__(self, outcome, report, logfile, config): self.test_id = report.nodeid.encode("utf-8").decode("unicode_escape")改为
class TestResult: def __init__(self, outcome, report, logfile, config): # 白月黑羽修改方法,解决乱码问题 # self.test_id = report.nodeid.encode("utf-8").decode("unicode_escape") self.test_id = report.nodeid
然后再次运行,就可以发现中文乱码问题已经解决了。