pytest
一、Pytest特点
基于Python语言的自动化测试框架最知名的有如下
1、 unitest
2、 pytest
3、 robotframework
前两款框架主要聚焦在开发者的白盒单元测试
而robotframework主要聚焦在系统测试上。
Pytest可以用来做系统测试的自动化,它的特点有:
1、 用Python编写测试用例,简便易用
2、 可以用文件系统目录层次对应手工测试用例层次结构
3、 灵活的初始化清除机制
4、 可以灵活挑选测试用例执行
5、 利用第三方插件,可以生成不错的报表
Pytest的功能非常多,我们这里主要介绍常用的功能。
安装
直接执行如下命令即可安装pytest
pip install pytest
我们还需要产生测试报表,所以需要安装一个pytest-html,执行如下命令安装
pip install pytest-html
二、快速上手
Pytest如何知道你哪些代码是自动化的测试用例
官方文档给出了pytest寻找测试项的具体规则
1、 如果未指定命令行参数,则从testpath(如果已配置)或当前目录开始收集
如果命令行参数,指定了目录、文件名或node id的任何组合,则按参数来找
2、 寻找过程会递归到目录中,除非它们匹配上norecursedirs
3、 在这些目录中,搜索由其测试包名称导入的test_*.py或*_test.py文件
4、 从这些文件中,收集如下测试项:
test为前缀的函数
Test为前缀的类里面的test为前缀的方法。
测试用例代码
首先,我们编写的测试用例代码文件,必须以test_开头,或者以_test结尾
比如我们创建一个文件名为test_错误登录.py,放在目录autotest\cases\登录下面。
其中autotest是我们创建的自动化项目根目录。

Test_错误密码.py
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
运行用例,打开cmd,进入项目根目录

显示找到3个测试项,2个执行通过,1个执行不通过。
通过的用例用一个绿色小点表示,不通过的用例用一个红色的F表示,并且会在后面显示具体不通过的用例和不通过的检查点代码细节。用例代码中有些打印语句没有显示出具体内容,如果想显示测试代码中print的内容,可以加上命令行参数-s。
pytest -s

如果希望得到更详细的执行信息,包括每个测试类、测试函数的名字,可以加上参数-v,这个参数可以和-s合并为-sv
pytest -sv

执行pytest时,如果没有指定目标目录或者文件,它会自动搜索当前目录下所有符合条件的文件、类、函数。所以上面,就找到了3个测试方法,对应3个用例。我们目前项目根目录中只有一个cases目录用例存放测试用例,将来还会有其他目录。
产生报告
前面安装pytest,我们也安装了pytest-html插件,这个插件就是用来产生测试报告的。要产生报告,在命令行加上参数—html=report.html –self-contained-html,如下
pytest cases --html=report.html --self-contained-html

生成报告


三、初始化清除
对于自动化测试框架来说,初始化清除功能至关重要。
模块级别
模块级别的初始化、清除分别在整个模块的测试用例执行前后执行,并且只会执行1次。它主要是用来为该模块中所有的测试用例做公共的初始化和清除。
如下定义setup_module和teardown_module全局函数
Test_错误密码.py
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_c002001(self):
print('\n用例C001001')
assert 1==1
def test_c002002(self):
print('\n用例C001002')
assert 2 == 2
def test_c002003(self):
print('\n用例C001003')
assert 3 == 2
运行结果:

方法级别
方法级别的初始化、清除分别在每个测试方法执行前后执行,并且每个用例分别执行1次,如下定义setup_class和teardown_class类方法
Test_错误密码.py
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_c002001(self):
print('\n用例C001001')
assert 1==1
def test_c002002(self):
print('\n用例C001002')
assert 2 == 2
def test_c002003(self):
print('\n用例C001003')
assert 3 == 2
运行结果:

类级别
类级别的初始化、清除分别在整个类的测试用例执行前后执行,并且只会执行1次。
Test_错误密码.py
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_c002001(self):
print('\n用例C001001')
assert 1==1
def test_c002002(self):
print('\n用例C001002')
assert 2 == 2
def test_c002003(self):
print('\n用例C001003')
assert 3 == 2
运行结果:

目录级别
目录级别的初始化清除,就是针对整个目录执行的初始化、清除。我们在需要初始化的目录下面创建一个名为conftest.py的文件,里面的内容如下:

conftest.py
import pytest
@pytest.fixture(scope='package',autouse=True)
def st_emptyEnv():
print(f'\n### 初始化-目录甲')
yield
print(f'\n### 清除-目录甲')
Test_错误密码.py
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_c002001(self):
print('\n用例C001001')
assert 1==1
def test_c002002(self):
print('\n用例C001002')
assert 2 == 2
def test_c002003(self):
print('\n用例C001003')
assert 3 == 2
运行结果如下:


我们可以在多个目录下面放置这样的文件,定义该目录的初始化清除。
Pytest在执行测试时,会层层调用。
挑选用例执行
Pytest有灵活的挑选测试用例执行的机制
指定一个模块,可以像这样只挑选一个模块执行
Pytest cases\登录\test_错误登录.py

指定目录
可以像这样只挑选一个目录执行
Pytest cases

也可以指定多个目录
Pytest cases1 cases2\登录
指定模块里面的函数或者类
指定一个类
pytest cases\login\Test_错误密码.py::Test_错误密码

也可以指定类里面的方法
pytest cases\login\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
class Test_错误密码2:
@pytest.mark.smoke
def test_c002001(self):
print('\n用例C002001')
assert 1==1
然后,可以这样运行指定标签的用例pytest cases -m smoke -s

也可以给整个类加上标签
@pytest.mark.Test_错误密码2
class Test_错误密码2:
@pytest.mark.smoke
def test_c002001(self):
print('\n用例C002001')
assert 1==1
def test_c002002(self):
print('\n用例C001002')
assert 2 == 2
def test_c002003(self):
print('\n用例C001003')
assert 3 == 2

也可以同时添加多个标签
@pytest.mark.Test_错误密码2
@pytest.mark.网页测试
class Test_错误密码2:
@pytest.mark.smoke
def test_c002001(self):
print('\n用例C002001')
assert 1==1
def test_c002002(self):
print('\n用例C001002')
assert 2 == 2
def test_c002003(self):
print('\n用例C001003')
assert 3 == 2


实际案例
Test_错误登录.py
import time
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
class Test_错误登录:
def test_UI_0001(self):
print('\n用例UI_001')
wd=webdriver.Chrome(service=Service(r"D:\个人知识总结\python project\pytest\chromedriver-win64\chromedriver-win64\chromedriver.exe"))
wd.implicitly_wait(10)
wd.get('http://127.0.0.1/mgr/sign.html')
# wd.find_element(By.ID,'username').send_keys()
wd.find_element(By.ID, 'password').send_keys('888')
wd.find_element(By.CSS_SELECTOR,"button[type='submit']").click()
time.sleep(2)
alertText=wd.switch_to.alert.text
print(alertText)
assert alertText == '请输入用户名'

浙公网安备 33010602011771号