一篇教程精通pytest

Posted 上海一亩地

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一篇教程精通pytest相关的知识,希望对你有一定的参考价值。

黑盒与白盒

在测试行业中,所有的测试工作分成黑盒测试和白盒测试两类。其中黑盒测试是不涉及软件内部代码逻辑的测试。测试对象是软件功能、性能、兼容性、安全健壮性、可移植性、可靠性等等,衡量黑盒测试工作量的指标是检测的功能点数量,衡量黑盒测试工作效果的指标是bug发现数量(虽然不应该这样,有的软件bug就是少,但是没办法,老板们都这么干)。而白盒测试是针对代码的测试,测试对象是类、方法和静态函数。衡量白盒测试的指标是覆盖率,包括语句覆盖率、条件覆盖率、分支覆盖率等等,白盒测试只能用自动化的方式实现,手工?你试试要请多少人干活。
一个新软件完成,没有经过测试。一般一轮测试是人工手动的功能测试,将所有功能点测试一遍,然后统计重要的,易出错的功能点(bug常常扎堆出现)。将这些易出错的功能点的测试用例再拿来进行二轮甚至三轮测试。而对于重要的功能点,把这些功能点相关的用例全部用自动化去实现,这样每出现一个新版本就跑一次自动化看看有没有大问题,俗称冒烟测试,。最终将稳定的不经常改动的功能点进行自动化回归测试,回归测试中大量采用自动化,很少用手工测试。
综上:

  1. 一轮全手工,功能全覆盖,需要大量人力。
  2. 易出错功能的进行二轮。
  3. 重要的功能,用例自动化执行,以后版本迭代时进行冒烟测试。
  4. 稳定的功能,用例自动化执行,定期进行回归测试。

什么是自动化测试

自动化是一种测试方法,但不是测试类型。自动化就是代替人工。所以既有黑盒自动化测试也有白盒自动化测试。

  1. 黑盒自动化:自动化擅长的领域就那么三种,命令行测试(subprocess+paramiko)、API接口测试(requests+UIautomation)、web页面测试(selenium)。所以如果涉及桌面测试,就很难了,非常不稳定。
  2. 白盒自动化测试: 主要就是使用静态代码扫描工具(Sonarqube)+ 白盒框架(pytest)去测试一个个函数和类,以及统计覆盖路。

pytest特点

  1. pytest是单元测试框架,能批量执行其他函数,统计执行结果,生成测试报告。unittest也可以,但没有pytest强大。
  2. pytest能实现跳过用例或者rerun用例。
  3. pytest有非常美观的测试报告。
  4. pytest有丰富的插件支持。

pytest默认命名规则

  1. 待测模块名(py文件名)必须用test_开头,或者_test结尾。
  2. 待测类名必须以Test开头并且不能有init方法。
  3. 待测方法必须以test开头,不必有下划线
    为什么说是默认规则?这些命名规则是可以自定义在ini配置文件中的,可以改变。

环境安装

找一个Python3 环境,或者生成一个新的虚拟环境。生成并激活虚拟环境的命令是

python3 -m venv venv3
source venv3/bin/activate
# 关闭虚拟环境 直接敲  deactivate

将以下文本保存成requirements.txt

pytest
pytest-html   # 发report需要
pytest-xdist  # pytest均衡使用多个cpu内核
pytest-ordering  # 调整case顺序
pytest-rerunfailures # 重跑
allure-pytest  # 重复执行一个函数,将其视为多个case

然后执行安装:

pip install -r requirements.txt

pytest的运行方式

  1. main主函数运行
  2. 命令行运行
  3. pytest.ini配置文件运行
  4. nodeid的方式,即模块::类名::函数名

main函数方式:

新建login_test.py

# 主函数方式
import pytest


class TestLogin():
    def test_01_haha(self):
        print("hahahaha")

if __name__ =="__main__":
    pytest.main(["-vs"])

命令行方式

直接执行pytest -vs

-v表示详细模式,每个函数显示模块和所在类。
-s是显示打印信息。
-n支持多核多线程并发运行测试。多个待测函数可以同时跑。
–reruns=NUM 失败重跑数
-x 有用例失败就停止测试,后面测试不继续跑
–maxfail=NUM 最多出现n次失败后停止测试
-k 只执行函数名中包含特定字符串的用例
-m 只执行标记的用例
–html xxxx/xxxx.html 给出文件路径,生成测试报告

>pytest -vs
======================================================================================= test session starts ========================================================================================
platform win32 -- Python 3.8.5, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 -- c:\\python38\\python.exe
cachedir: .pytest_cache
metadata: 'Python': '3.8.5', 'Platform': 'Windows-10-10.0.19041-SP0', 'Packages': 'pytest': '6.2.4', 'py': '1.10.0', 'pluggy': '0.13.1', 'Plugins': 'allure-pytest': '2.9.43', 'forked': '1.3.0',
 'html': '3.1.1', 'metadata': '1.11.0', 'ordering': '0.6', 'rerunfailures': '10.1', 'xdist': '2.3.0'
rootdir: E:\\python-code\\pytest_learn
plugins: allure-pytest-2.9.43, forked-1.3.0, html-3.1.1, metadata-1.11.0, ordering-0.6, rerunfailures-10.1, xdist-2.3.0
collected 1 item                                                                                                                                                                                    

case/login_test.py::TestLogin::test_01_haha hahahaha
PASSED

======================================================================================== 1 passed in 0.02s =========================================================================================

可以发现main方法中的内容其实就是命令行中的内容。

-n并行运行

import pytest
import time

class TestLogin():
    def test_01_haha(self):
        time.sleep(3)
        print("hahahaha")

    def test_02_hoho(self):
        time.sleep(3)
        print("hohohoho")

if __name__ =="__main__":
    #pytest.main(["-vs"])
    pytest.main(["-vs","-n 2"])
>pytest -vs -n 2
===================================== test session starts ======================================
platform win32 -- Python 3.8.5, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 -- c:\\python38\\python.exe
cachedir: .pytest_cache
metadata: 'Python': '3.8.5', 'Platform': 'Windows-10-10.0.19041-SP0', 'Packages': 'pytest': '6.
2.4', 'py': '1.10.0', 'pluggy': '0.13.1', 'Plugins': 'allure-pytest': '2.9.43', 'forked': '1.3.
0', 'html': '3.1.1', 'metadata': '1.11.0', 'ordering': '0.6', 'rerunfailures': '10.1', 'xdist': '
2.3.0'
rootdir: E:\\python-code\\pytest_learn
plugins: allure-pytest-2.9.43, forked-1.3.0, html-3.1.1, metadata-1.11.0, ordering-0.6, rerunfail
ures-10.1, xdist-2.3.0
[gw0] win32 Python 3.8.5 cwd: E:\\python-code\\pytest_learn
[gw1] win32 Python 3.8.5 cwd: E:\\python-code\\pytest_learn
[gw0] Python 3.8.5 (tags/v3.8.5:580fbb0, Jul 20 2020, 15:57:54) [MSC v.1924 64 bit (AMD64)]
[gw1] Python 3.8.5 (tags/v3.8.5:580fbb0, Jul 20 2020, 15:57:54) [MSC v.1924 64 bit (AMD64)]
gw0 [2] / gw1 [2]
scheduling tests via LoadScheduling

case/login_test.py::TestLogin::test_02_hoho
case/login_test.py::TestLogin::test_01_haha
[gw0] PASSED case/login_test.py::TestLogin::test_01_haha
[gw1] PASSED case/login_test.py::TestLogin::test_02_hoho

====================================== 2 passed in 3.63s =======================================


node id的方式:

pytest指定某个模块下某个类下的某个方法。中间用双冒号:: 隔开

import pytest


class TestLogin():
    def test_01_haha(self):
        print("hahahaha")

if __name__ =="__main__":
    # pytest.main(["-vs"])
    # pytest.main(["-vs","./login_test.py"]) # 这里可以指定文件或文件夹,main方法可以放在系统的任何地方。
    pytest.main(["-vs", "login_test.py::TestLogin::test_01_haha"])

pytest.ini配置文件运行方式

通过预先编写配置文件再执行的方式才是最常用的执行方式,企业自动化的测试调度也需要代码化。
pytest.ini是pytest的基础配置文件

  1. 位置:放在项目根目录
  2. 编码:必须使用ANSI编码,可以用notepad++更改文件编码。
  3. 作用:改变pytest的默认行为。

案例:

[pytest]
# 加配置
addopts = -vs

# 测试用例文件夹
testpaths = ./case

# 配置测试搜索的文件名模式
python_files = test_*.py

# 测试搜索类名
python_classes = Test*

# 测试搜索函数名
python_functions = test

# 只执行带标签的用例,如果跑全部,就不加这个
markers = smoke
# 执行多个标签可以加and 和 or。 markers = "smoke or product"

如何给用例添加标记呢?
使用@pytest.mark.标记的

import pytest
import time

class TestLogin():
    @pytest.mark.run(order=3)
    @pytest.mark.smoke
    def test_01_haha(self):
        time.sleep(3)
        print("hahahaha")

失败用例重跑

加–reruns可以在用例执行失败后再多跑n次。

import pytest
import time

class TestLogin():
    def test_01_haha(self):
        time.sleep(3)
        print("hahahaha")

    def test_02_hoho(self):
        time.sleep(3)
        print("hohohoho")

    def test_03_fail(self):
        time.sleep(3)
        assert 1==2


if __name__ =="__main__":
    #pytest.main(["-vs"])
    pytest.main(["-vs","-n 3","--reruns=2"])
C:\\Python38\\python.exe E:/python-code/pytest_learn/case/login_test.py
============================= test session starts =============================
platform win32 -- Python 3.8.5, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 -- C:\\Python38\\python.exe
cachedir: .pytest_cache
metadata: 'Python': '3.8.5', 'Platform': 'Windows-10-10.0.19041-SP0', 'Packages': 'pytest': '6.2.4', 'py': '1.10.0', 'pluggy': '0.13.1', 'Plugins': 'allure-pytest': '2.9.43', 'forked': '1.3.0', 'html': '3.1.1', 'metadata': '1.11.0', 'ordering': '0.6', 'rerunfailures': '10.1', 'xdist': '2.3.0'
rootdir: E:\\python-code\\pytest_learn\\case
plugins: allure-pytest-2.9.43, forked-1.3.0, html-3.1.1, metadata-1.11.0, ordering-0.6, rerunfailures-10.1, xdist-2.3.0
gw0 I / gw1 I / gw2 I
[gw0] win32 Python 3.8.5 cwd: E:\\python-code\\pytest_learn\\case
[gw1] win32 Python 3.8.5 cwd: E:\\python-code\\pytest_learn\\case
[gw2] win32 Python 3.8.5 cwd: E:\\python-code\\pytest_learn\\case
[gw0] Python 3.8.5 (tags/v3.8.5:580fbb0, Jul 20 2020, 15:57:54) [MSC v.1924 64 bit (AMD64)]
[gw1] Python 3.8.5 (tags/v3.8.5:580fbb0, Jul 20 2020, 15:57:54) [MSC v.1924 64 bit (AMD64)]
[gw2] Python 3.8.5 (tags/v3.8.5:580fbb0, Jul 20 2020, 15:57:54) [MSC v.1924 64 bit (AMD64)]
gw0 [3] / gw1 [3] / gw2 [3]

scheduling tests via LoadScheduling

login_test.py::TestLogin::test_03_fail 
login_test.py::TestLogin::test_01_haha 
login_test.py::TestLogin::test_02_hoho 
[gw1] PASSED login_test.py::TestLogin::test_02_hoho 
[gw0] PASSED login_test.py::TestLogin::test_01_haha 
[gw2] RERUN login_test.py::TestLogin::test_03_fail 
login_test.py::TestLogin::test_03_fail 
[gw2] RERUN login_test.py::TestLogin::test_03_fail 
login_test.py::TestLogin::test_03_fail 
[gw2] FAILED login_test.py::TestLogin::test_03_fail 

================================== FAILURES ===================================
___________________________ TestLogin.test_03_fail ____________________________
[gw2] win32 -- Python 3.8.5 C:\\Python38\\python.exe

self = <case.login_test.TestLogin object at 0x000002B1BCE46070>

    def test_03_fail(self):
        time.sleep(3)
>       assert 1==2
E       assert 1 == 2
E         +1
E         -2

login_test.py:15: AssertionError
=========================== short test summary info ===========================
FAILED login_test.py::TestLogin::test_03_fail - assert 1 == 2
==================== 1 failed, 2 passed, 2 rerun in 9.89s =====================

Process finished with exit code 0

–maxfail和-k参数自己试试

pytest调整用例执行顺序

在待测用例的上方加装饰器@pytest.mark.run(order=n)能调整case执行顺序。

import pytest
import time

class TestLogin():
    @pytest.mark.run(order=3)
    def test_01_haha(self):
        time.sleep(3)
        print("hahahaha")

    @pytest.mark.run(order=1)
    def test_02_hoho(self):
        time.sleep(3)
        print("hohohoho")

    @pytest.mark.run(order=2)
    def test_03_fail(self):
        time.sleep(3)
        assert 1==2


if __name__ =="__main__":
    pytest.main(["-vs"])

跳过执行测试用例

无条件跳过
用@pytest.mark.skip(reason=“无条件跳过”),reason这里加一段话解释为什么跳过,也可以不加。

    @pytest.mark.run(order=1)
    @pytest.mark.skip(reason="无条件跳过")
    def test_02_hoho(self):
        time.sleep(3)
        print("hohohoho")

有条件跳过用

    @pytest.mark.skipif(age>18,reason="成年")
    def test_02_hoho(self):
        time.sleep(3)
        print("hohohoho")

生成测试报告

[pytest]
# 加配置
addopts = -vs --html ./report/report.html

# 测试用例文件夹
testpaths = ./case

# 配置测试搜索的文件名模式
python_files = test_*.py

# 测试搜索类名
python_classes = Test*

# 测试搜索函数名
python_functions = test

# 只执行带标签的用例,如果跑全部,就不加这个
markers = smoke
# 执行多个标签可以加and 和 or。 markers = "smoke or product"

或者 命令执行pytest --html ./report/report.html

只写了一半,未完待续。

以上是关于一篇教程精通pytest的主要内容,如果未能解决你的问题,请参考以下文章

单元测试界的高富帅,Pytest框架,手把手教学,从入门到精通

单元测试界的高富帅,Pytest框架,手把手教学,从入门到精通

超详细从入门到精通,pytest自动化测试框架实战教程-allure测试报告

Pytest 简明教程

华为出品Python入门教程:从零基础入门到精通,这一篇就够了

黑客入门教程(非常详细)从零基础入门到精通,看完这一篇就够了。