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

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

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

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

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

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

pytest的特点

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

安装

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

执行结果

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

Pytest 查找测试策略

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

# 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

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:

============================= 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的每个元素都是一个元组,元组里的每个元素和按参数顺序一一对应

# 一个参数
@pytest.mark.parametrize('参数名',list) 进行参数化
# 两个参数
@pytest.mark.parametrize('参数名1,参数名2',[(参数1_data[0], 参数2_data[0]), (参数1_data[1], 参数2_data[1])])
  • 小试牛刀
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')

执行结果

...                                                                      [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

  • 传入多个参数
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')

执行结果如下:

============================= 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,那么执行结果就变成了

============================= 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开始或结束的函数和方法。所以当需要测试的文件特别多,就可以直接另写一个文件

import pytest

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

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

  • 运行./目录下所有(test_*.py*_test.py
pytest.main(['./'])
  • 运行./xdj 目录下的所有用例
pytest.main (['./xdj'])    
  • 运行指定模块
pytest.main (['./xdj/test01.py'])
  • 运行test01.py中的test_login方法
pytest.main (['./xdj/test01.py::test_login']) 
  • 运行test02.pytest2(类)的方法test_class
pytest.main (['./xdj/test02.py::test2::test_class'])
  • 匹配包含love的用例(匹配目录名、模块名、类名、用例名)
pytest.main (['-k','love'])
  • 匹配test.py模块下包含hello的用例
pytest.main(['-k','hello','./xdj/test.py'])
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容