pytest

一、Pytest特点

基于Python语言的自动化测试框架最知名的有如下

1、 unitest

2、 pytest

3、 robotframework

前两款框架主要聚焦在开发者的白盒单元测试

而robotframework主要聚焦在系统测试上。

Pytest可以用来做系统测试的自动化,它的特点有:

1、 用Python编写测试用例,简便易用

2、 可以用文件系统目录层次对应手工测试用例层次结构

3、 灵活的初始化清除机制

4、 可以灵活挑选测试用例执行

5、 利用第三方插件,可以生成不错的报表

Pytest的功能非常多,我们这里主要介绍常用的功能。

安装

直接执行如下命令即可安装pytest

1
pip install pytest

我们还需要产生测试报表,所以需要安装一个pytest-html,执行如下命令安装

1
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

1
2
3
4
5
6
7
8
9
10
11
12
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。

1
pytest -s

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

1
pytest -sv

执行pytest时,如果没有指定目标目录或者文件,它会自动搜索当前目录下所有符合条件的文件、类、函数。所以上面,就找到了3个测试方法,对应3个用例。我们目前项目根目录中只有一个cases目录用例存放测试用例,将来还会有其他目录。

产生报告

前面安装pytest,我们也安装了pytest-html插件,这个插件就是用来产生测试报告的。要产生报告,在命令行加上参数—html=report.html –self-contained-html,如下

1
pytest cases --html=report.html --self-contained-html

生成报告

三、初始化清除

对于自动化测试框架来说,初始化清除功能至关重要。

模块级别

模块级别的初始化、清除分别在整个模块的测试用例执行前后执行,并且只会执行1次。它主要是用来为该模块中所有的测试用例做公共的初始化和清除。

如下定义setup_module和teardown_module全局函数

Test_错误密码.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
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

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
37
38
39
40
41
42
43
44
45
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

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
37
38
39
40
41
42
43
44
45
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

1
2
3
4
5
6
7
8
import pytest
 
@pytest.fixture(scope='package',autouse=True)
def st_emptyEnv():
    print(f'\n### 初始化-目录甲')
    yield
 
    print(f'\n### 清除-目录甲')

Test_错误密码.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
37
38
39
40
41
42
43
44
45
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有灵活的挑选测试用例执行的机制

指定一个模块,可以像这样只挑选一个模块执行

1
Pytest cases\登录\test_错误登录.py

指定目录

可以像这样只挑选一个目录执行

1
Pytest cases

也可以指定多个目录

1
Pytest cases1 cases2\登录

指定模块里面的函数或者类

指定一个类

1
pytest cases\login\Test_错误密码.py::Test_错误密码

也可以指定类里面的方法

1
pytest cases\login\Test_错误密码.py::Test_错误密码::test_c001001

根据名字

可以使用命令行参数-k后面加名字来挑选要执行的测试项

比如像这样后面跟测试函数名字的一部分:

1
pytest -k c001001 -s

注意,-k后面的名字

可以是测试函数的名字,可以是类的名字,可以是模块文件名,可以是目录的名字

是大小写敏感的

不一定要完整,只要能有部分匹配上就行

可以用not表示选择名字中不包含,比如

1
pytest -k "not c001001" -s

可以用and表示选择名字同时包含多个关键字,比如

1
pytest -k "错 and 密码2" -s

也可以使用or表示选择名字

1
pytest -k "错 or 密码2" -s

根据用例标签执行用例

可以这样给某个方法打上标签webtest

1
2
3
4
5
6
class Test_错误密码2:
 
    @pytest.mark.smoke
    def test_c002001(self):
        print('\n用例C002001')
        assert 1==1

然后,可以这样运行指定标签的用例pytest cases -m smoke -s

也可以给整个类加上标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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

 也可以同时添加多个标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 == '请输入用户名'

  

 

  

  

 

  

 

  

 

  

  

  

 

  

 

  

 

 

  

  

  

  

  

 

  

  

 

posted @   leagueandlegends  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示