妹子让我看她写的pytest,结果...

2020-12-21 11:12:12 浏览数 (1)

陆陆续续断更好久好久了,这么久发生了很多事情,也思考了很多事情。突然发现拖延症已经严重影响到了我。

什么是拖延症,简单来说就是个人选择而已。每时每刻,面临辛苦的选项与逃避的选项时,倾向于选择更安逸的那一个。一而再再而三,就成了拖延。很早之前我就计划专注某个方面写一个系列,但是直到今日,仍没有结果,实在是有些惭愧,想从新逼迫自己进步,不知道能坚持多久。

言归正传,谈下pytest,很多人会有疑问,网上都那么多pytest文章了,为什么我还要专门写呢,其实很简单。原因有二,第一个就是老生常谈的话题了多数人写技术博客都是通过写来看自己掌握的进度,同时哪天有用到了,回过头看,也可以起到温故而知新的作用。第二,刚好有测试妹子给我提供了一些简单的pytest的小案例,我也正有此意,那这篇文章就这样来了。

先声明:我写的技术文主要还是以理解为主,不一定专业,如果看完还是不会,那一定是我写的不够好。不要因为我写的太过于乏味而打消自己学习的念头。

回到正文pytest,可能很多常写python的人第一次听到这个库,它究竟有什么用呢? pytest 是一个成熟的全功能的 Python 测试工具。这里提供一份Pytest 使用手册以及官方文档。

  • 手册:https://learning-pytest.readthedocs.io/zh/latest/
  • 官方文档:https://docs.pytest.org/en/latest/contents.html

pytest的特点

  • 简单灵活,容易上手,文档丰富;
  • 支持参数化,可以细粒度地控制要测试的测试用例;
  • 能够支持简单的单元测试和复杂的功能测试,还可以用来做selenium/appnium等自动化测试、接口自动化测试(pytest requests);
  • pytest具有很多第三方插件,并且可以自定义扩展,比较好用的如pytest-selenium(集成selenium)、pytest-html(完美html测试报告生成)、pytest-rerunfailures(失败case重复执行)、pytest-xdist(多CPU分发)等;
  • 测试用例的skipxfail处理;
  • 可以很好的和CI工具结合,例如jenkins等等;

安装

  • pip安装
代码语言:javascript复制
pip install -U pytest
  • 验证安装
代码语言:javascript复制
pytest --version # 会展示当前已安装版本
  • pytest中,assert 是编写测试的最基础工具。如:
代码语言:javascript复制
a = 1
b = 3
assert a == b

执行结果

代码语言:javascript复制
Traceback (most recent call last):
  File "/home/xsl/test.py", line 3, in <module>
    assert a == b
AssertionError

Pytest 查找测试策略

默认情况下,pytest 会递归查找当前目录下所有以 test 开始或结尾的 Python 脚本,并执行文件内的所有以 test开始或结束的函数和方法。

代码语言:javascript复制
# test_no_mark.py

def test_func1():
    assert 1 == 1

def test_func2():
    assert 1 != 1

所以,编写pytest测试样例非常简单,只需要按照下面的规则:

  • 测试文件以test_开头(以_test结尾也可以)
  • 测试类以Test开头,并且不能带有 init 方法(注意:定义class时,需要以T开头,不然pytest是不会去运行该class的)
  • 测试函数以test_开头

仅用四行代码创建一个简单的测试函数 文件名为test.py

代码语言:javascript复制
import pytest

def func(x):
    return x   1


def test_answer():
    assert func(3) == 5

if __name__ == '__main__':
    pytest.main("-s  test.py")

执行测试代码结果如下,pytest将显示错误信息,因为func(3)未返回5:

代码语言:javascript复制
============================= test session starts ==============================
platform linux -- Python 3.5.3, pytest-3.4.1, py-1.5.2, pluggy-0.6.0
rootdir: /home/xsl, inifile:
collected 1 item

test.py F

=================================== FAILURES ===================================
_________________________________ test_answer __________________________________

    def test_answer():
>       assert func(3) == 5
E       assert 4 == 5
E           where 4 = func(3)

test.py:15: AssertionError
=============================== warnings summary ===============================
None
  passing a string to pytest.main() is deprecated, pass a list of arguments instead.

-- Docs: http://doc.pytest.org/en/latest/warnings.html
===================== 1 failed, 1 warnings in 0.10 seconds =====================

  • 因为妹子用到的是@pytest.mark.parametrize,那我们就详细讲解下pytest.mark.parametrize

使用方法:@pytest.mark.parametrize('参数',list)

参数一是字符串,多个参数中间用逗号隔开 第二个参数是list,多组数据用元祖类型;list的每个元素都是一个元组,元组里的每个元素和按参数顺序一一对应

代码语言:javascript复制
# 一个参数
@pytest.mark.parametrize('参数名',list) 进行参数化
# 两个参数
@pytest.mark.parametrize('参数名1,参数名2',[(参数1_data[0], 参数2_data[0]), (参数1_data[1], 参数2_data[1])])
  • 小试牛刀
代码语言:javascript复制
import pytest

data =[[1,1], [2,1 1], [6,2*3],]
@pytest.mark.parametrize('a, b', data)
def test_assert(a, b):
    assert a == b

if __name__ == '__main__':
    pytest.main('-q test.py')

执行结果

代码语言:javascript复制
...                                                                      [100%]
=============================== warnings summary ===============================
None
  passing a string to pytest.main() is deprecated, pass a list of arguments instead.

-- Docs: http://doc.pytest.org/en/latest/warnings.html
3 passed, 1 warnings in 0.01 seconds
  • 传入多个参数
代码语言:javascript复制
import pytest

@pytest.mark.parametrize("user_nm, pass_wd",
                         [("xu", "123456"), ("ni", "123456"),
                          ("xing", "123456"), ("chen", "123456")])
def test_login(user_nm, pass_wd):
    print(user_nm   " : "   pass_wd)
    assert pass_wd == '123456'

if __name__ == '__main__':
    pytest.main('-s test.py')

执行结果如下:

代码语言:javascript复制
============================= test session starts ==============================
platform linux -- Python 3.5.3, pytest-3.4.1, py-1.5.2, pluggy-0.6.0
rootdir: /home/xsl/gogs/dps/etl, inifile:
collected 4 items

test.py xu : 123456
.ni : 123456
.xing : 123456
.chen : 123456
.

=============================== warnings summary ===============================
None
  passing a string to pytest.main() is deprecated, pass a list of arguments instead.

-- Docs: http://doc.pytest.org/en/latest/warnings.html
===================== 4 passed, 1 warnings in 0.01 seconds =====================

细心的朋友发现了,每次我执行的时候会用pytest.main('-s 文件名') 其实这里的-s是可以根据不同的需求进行替换的,这里我们替换成-v,那么执行结果就变成了

代码语言:javascript复制
============================= test session starts ==============================
platform linux -- Python 3.5.3, pytest-3.4.1, py-1.5.2, pluggy-0.6.0 -- /usr/bin/python3.5
cachedir: .pytest_cache
rootdir: /home/xsl, inifile:
collecting ... collected 4 items

test.py::test_login[xu-123456] PASSED                                    [ 25%]
test.py::test_login[ni-123456] PASSED                                    [ 50%]
test.py::test_login[xing-123456] PASSED                                  [ 75%]
test.py::test_login[chen-123456] PASSED                                  [100%]

=============================== warnings summary ===============================
None
  passing a string to pytest.main() is deprecated, pass a list of arguments instead.

-- Docs: http://doc.pytest.org/en/latest/warnings.html
===================== 4 passed, 1 warnings in 0.01 seconds =====================

  • -s: 显示程序中的print/logging输出
  • -v: 丰富信息模式, 输出更详细的用例执行信息
  • -q: 安静模式, 不输出环境信息
  • -k:关键字匹配,用and区分:匹配范围(文件名、类名、函数名)
  • -x:出现一条测试用例失败就退出测试。在调试阶段非常有用,当测试用例失败时,应该先调试通过,而不是继续执行测试用例。

这样就可以写很多个测试文件,然后在另外的文件中使用pytest.main('-s 文件名') 去执行,又因为前文讲了,pytest会递归查找当前目录下所有以 test 开始或结尾的 Python 脚本,并执行文件内的所有以 test开始或结束的函数和方法。所以当需要测试的文件特别多,就可以直接另写一个文件

代码语言:javascript复制
import pytest

if __name__ == '__main__':
    pytest.main('-s test.py')

其实根据自己不同的需求来确定pytest.main('')括号内的内容,比如

  • 运行./目录下所有(test_*.py*_test.py
代码语言:javascript复制
pytest.main(['./'])
  • 运行./xdj 目录下的所有用例
代码语言:javascript复制
pytest.main (['./xdj'])    
  • 运行指定模块
代码语言:javascript复制
pytest.main (['./xdj/test01.py'])
  • 运行test01.py中的test_login方法
代码语言:javascript复制
pytest.main (['./xdj/test01.py::test_login']) 
  • 运行test02.pytest2(类)的方法test_class
代码语言:javascript复制
pytest.main (['./xdj/test02.py::test2::test_class'])
  • 匹配包含love的用例(匹配目录名、模块名、类名、用例名)
代码语言:javascript复制
pytest.main (['-k','love'])
  • 匹配test.py模块下包含hello的用例
代码语言:javascript复制
pytest.main(['-k','hello','./xdj/test.py'])

0 人点赞