在进行UI自动化测试的时候,我们需要工具来对测试用例进行收集,执行,标记,参数化。pytest就是这样一个工具。
pytest实际是python的一个单元测试框架,其他还有如unittest等,它可以实现按照规则搜索测试用例,对测试用例进行标记,如只执行L1
级别的测试用例,测试执行用例失败后重新执行,测试用例的参数化等。
pytest不仅适用于UI自动化测试,也适用于接口自动化,对python语言进行单元测试等。
可以使用pip或者pycharm方式进行安装
打开pycharm,左上角File-settings-project:项目名-python interpreter,点击+,搜索pytest,然后install package
import pytest
class TestDemo:
def test_01(self):
assert 1==1 # 断言成功
def test_02(self):
assert 1==2 # 断言失败
if __name__ == '__main__':
pytest.main(['-s','test.py'])
在文件输入如上代码。
在这个脚本中,我们首先导入了pytest模块,然后定义了一个class,并定义了两个方法,在方法里用assert进行断言。
assert是python自带的关键字。assert后面接一个表达式,只要表达式的最终结果为True,那么断言通过,用例执行成功,否则用例执行失败。可以看出,第一个方法里结果为true,第二个方法里结果为false。
在main主入口里,调用pytest的main方法,并传入模块名(即文件名)。
点击运行按钮,结果如下:
由结果可以看出,一共收集了两个用例,其中一个运行成功(用.表示),一个运行失败(F表示),失败的原因是1!=2
pytest按照一定的规则进行用例的收集。
1. 模块名必须以test_开头或_test结尾
这里适用于未指定模块时的收集,如果运行单个文件,在main里指定要执行的文件,则无论这个文件名是什么,都会被执行。
如果在main里不指定模块,如只写pytest.main(),则pytest会默认从当前路径及其所有子目录中搜索py源文件,所有名字以test_开头或者以_test结尾的python源文件(.py文件)被认为是测试模块源文件,不符合这个命名规则的文件会被忽略。
2. 测试类必须以Test开头且不能有init方法
一个文件也可以只写测试方法,不写测试类。
3. 测试方法必须以test开头
在上面的示例脚本中,我们定义了两个test_开头的方法,所以收集了两个用例。非test开头的方法,会被pytest忽略。
我们可以在main入口中调用pytest的main方法来运行文件,如上例。
pytest.main(['-s'])
-s参数表示在控制台输出信息,当不指定模块名时,可以看到,收集了7条用例
pytest.main(['-s','test.py'])
当指定了模块名后,运行文件,只收集当前文件的两条用例。
pytest.main(["-s", "test.py::TestDemo"])
我们也可以在模块名上加上指定的class名
打开terminal,进入目标目录,直接执行“pytest”即可自动寻找当前目录下的测试用例
打开terminal,输入pytest+模块名,指定模块执行。
pytest test.py
打开terminal,输入pytest+模块名+函数名,指定模块执行。
pytest test.py::TestDemo
在File-Settings如图路径,设置default test runner为pytest,点击OK。
再回到测试文件,可以看到类,方法前面都显示运行按钮,点击对应按钮,可以运行类,单独运行某个test。
也可以在不写main函数的情况下,右键以pytest运行文件。注意在配置了runner后,执行不会进入main主入口。
pytest提供了标记机制,借助“mark”关键字,我们可以对测试函数(类、方法)进行标记。
我们可以利用标记,对测试用例进行分级,例如某些主流程的用例可以标记为L1,次要流程的用例标记为L2等。这样有一个好处,我们可以在不同的情况执行不同的测试用例,例如,在做冒烟测试的时候,只需要执行L1级别的用例就行了。
●一个测试函数(类、方法)可以有多个标记。
●一个标记也可以应用于多个函数(类、方法)。
●执行参数使用:pytest -m mark名。
●执行多个标记:pytest -m “L1 or L2”。
import pytest
class TestDemo:
@pytest.mark.L1
@pytest.mark.L2
def test_01(self):
assert 1==1 # 断言成功
@pytest.mark.L2
def test_02(self):
assert 1==2 # 断言失败
if __name__ == '__main__':
pytest.main(['-m L2','test.py'])
如上,给测试用例添加标记,并在main中以-m参数指定选择的标记,也可以使用or和and。
pytest.main(['-m L2 or L1','test.py']) # 执行标记为L1或者L2的
pytest.main(['-m L2 and L1','test.py']) #执行标记同时为L1和L2的
在进行UI测试时,可能每个用例的操作前提都是需要登录系统,那么我们也不能在每次写用例的时候,都把登录写一遍,或者调用一遍,这样会造成代码冗余。有没有可能,在整个测试类或者所有测试用例执行之前,调用一次登录呢?
这样的工具,就是测试固件,测试固件有不同级别。
●setup_module、teardown_module,在整个文件的开始和最后执行一次。
●setup_function和teardown_function,在每个函数开始前后执行。
import pytest
'''
在函数中使用
1.setup_module、teardown_module,在整个文件的开始和最后执行一次
2.setup_function和teardown_function,在每个函数开始前后执行
'''
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_a():
print('aaaa')
assert 1 == 1
def test_b():
print('bbbb')
assert 1 == 2
if __name__ == '__main__':
pytest.main(["-s", "test.py"])
●setup_class、teardown_class,在整个class的开始和最后执行一次。
●setup_method和teardown_method,在每个方法开始前后执行。
class TestDemo():
def setup_class(self):
print('setup_class')
def teardown_class(self):
print('teardown_class')
def setup_method(self):
print('setup_method')
def teardown_method(self):
print('teardown_method')
def test_a(self):
print('aaaa')
assert 1 == 1
def test_b(self):
print('bbbb')
assert 1 == 2
if __name__ == '__main__':
pytest.main(["-s", "test.py"])
setup和teardown既可以应用在函数中,也可以应用在class中,作用对象是函数或方法,在每个测试用例执行前后执行一次。
我们通常使用setup来进行一些数据准备工作,使用teardown来进行数据清理工作。
import pytest
class TestDemo:
def setup(self):
print('setup')
def teardown(self):
print('teardown')
@pytest.mark.L1
@pytest.mark.L2
def test_01(self):
assert 1==1 # 断言成功
@pytest.mark.L2
def test_02(self):
assert 1==2 # 断言失败
if __name__ == '__main__':
pytest.main(['-s','test.py'])
我们在进行手工测试时,测试用例有预期结果,那么在进行UI测试时,我们也需要来判定结果是否符合我们的预期。
在python中用断言来实现。断言assert用于检查指定的表达式的结果是否为True。
一个测试用例可以写多个断言,当有一个断言失败时,pytest就认为测试的结果为失败,该测试用例的执行会被终止。
如果一条测试用例不写断言,那么它的结果为通过。所以一定要写断言,否则测试执行就没有了意义。
import pytest
class TestDemo:
@pytest.mark.L1
@pytest.mark.L2
def test_01(self):
assert 1==1 # 断言成功
assert 1==3
@pytest.mark.L2
def test_02(self):
assert 1==2 # 断言失败
if __name__ == '__main__':
pytest.main(['-s','test.py'])
在进行UI测试时,我们需要给测试用例传测试数据,那么如何做呢?
pytest使用@pytest.mark.parametrize装饰器来实现数据驱动测试,也就是常说的参数化。
import pytest
class TestDemo:
data = ["小红", "小明"]
@pytest.mark.parametrize("username",data)
def test_01(self,username):
print("我是{}".format(username))
if __name__ == '__main__':
pytest.main(['-s','test.py'])
运行结果如下,这里给一条测试用例传入两条数据,显示收集到了两条用例。
import pytest
class TestDemo:
data_1 = [
{"username": "admin1", "password": "123456"},
{"username": "admin2", "password": "12345678"},
]
@pytest.mark.parametrize("data", data_1)
def test_login(self,data):
print("账号:{},密码:{}".format(data["username"], data["password"]))
if __name__ == '__main__':
pytest.main(['-s','test.py'])
可以看到这里传入两条数据,显示收集了两条用例。
import pytest
class TestDemo:
data_1 = [
["admin1", "123456"],
["admin2", "12345678"],
]
@pytest.mark.parametrize("username,password", data_1)
def test_login(self,username, password):
print("账号:{},密码:{}".format(username, password))
if __name__ == '__main__':
pytest.main(['-s','test.py'])
import pytest
class TestDemo:
data_1 = [
("admin1", "123456"),
("admin2", "12345678"),
]
@pytest.mark.parametrize("username,password", data_1)
def test_login(self,username, password):
print("账号:{},密码:{}".format(username, password))
if __name__ == '__main__':
pytest.main(['-s','test.py'])
本文介绍了pytest测试框架的安装、运行、用例收集、标记、测试固件、断言、参数化等内容,实际这些内容只是pytest的冰山一角,还有
失败重跑,测试报告,全局设置等内容没有进行介绍,但学习了本文的内容,相信你已经可以上手进行自动化测试用例的编写了。
欢迎交流,拜拜!