pytest随手记-自定义mark、pytest.ini、doctest测试框架、html报告

mark标记

  1. 举例:某用例可以用@pytest.mark.xxx,则想要运行该条时需要加-m参数指定值为xxx,:如pycharm中写pytest.main(["-s", "test_server.py", "-m=xxx"]),cmd执行时pytest -v -m xxx,若不想执行则用 pytest -v -m "not xxx"
  2. 指定运行某函数也可在pycharm中用pytest.main(["-v", "test_server.py::TestClass::test_method"]),cmd下用pytest -v test_server.py::TestClass::test_method ,意为执行server.py文件下TestClass类中test_method方法,也可选择多个节点运行,中间用逗号隔开:
pytest.main(["-v", "test_server.py::TestClass", "test_server.py::test_send_http"])
  1. 用 -k 匹配用例名称执行,如 pytest -v -k http (指定匹配带有http的用例)、pytest -k "not send_http" -v(去掉带有send_http的用例)、pytest -k "http or quick" -v(同时带quick的用例)

基础用例a失败,则依赖a用例结果的b、c用例直接跳过且标记为xfail

举个例子

#encoding=utf-8
import pytest

canshu = [{"user": "amdin", "psw": ""}]

@pytest.fixture(scope="module")
def login(request):
    user = request.param["user"]
    psw = request.param["psw"]
    print("正在操作登录,账户:%s,密码:%s" %(user,psw))
    if psw:
        return True
    else:
        return False

@pytest.mark.parametrize("login", canshu, indirect=True)
class Test_xx():
    def test_01(self,login):
        '''用例1登录'''
        result = login
        print("用例1:%s" % result)
        assert result == True

    def test_02(self,login):
        result = login
        print("用例2,登录结果:%s" %result)
        if not result:
            pytest.xfail("登录不成功,标记为xfail")

        assert 1 == 1

    def test_03(self,login):
        result = login
        print("用例3,登录结果:%s" %result)
        if not result:
            pytest.xfail("登录不成功,标记为xfail")

        assert 1 == 1

if __name__ == "__main__":
    pytest.main(["-s", "pratice.py"])

结果如图


mark_xfail

可以看到有两个用例判断登录失败后置为xfail且被ignore

pytest.ini配置文件

pytest.ini pytest的主配置文件,可以改变pytest的默认行为,该文件放在python项目的根目录,基本格式如下:

# 保存为pytest.ini文件

[pytest]

addopts = -rsxX
xfail_strict = true

使用pytest --help指令可以查看pytest.ini的设置选项

[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:

  markers (linelist)       markers for test functions
  empty_parameter_set_mark (string) default marker for empty parametersets
  norecursedirs (args)     directory patterns to avoid for recursion
  testpaths (args)         directories to search for tests when no files or dire

  console_output_style (string) console output: classic or with additional progr

  usefixtures (args)       list of default fixtures to be used with this project

  python_files (args)      glob-style file patterns for Python test module disco

  python_classes (args)    prefixes or glob names for Python test class discover

  python_functions (args)  prefixes or glob names for Python test function and m

  xfail_strict (bool)      default for the strict parameter of 
  addopts (args)           extra command line options
  minversion (string)      minimally required pytest version

具体参考https://www.cnblogs.com/yoyoketang/p/9550648.html这篇

doctest测试框架

doctest从字面意思上看,那就是文档测试。doctest是python里面自带的一个模块,它实际上是单元测试的一种。官方解释:doctest 模块会搜索那些看起来像交互式会话的 Python 代码片段,然后尝试执行并验证结果。个人感觉就是单元测试。
doctest测试用例可以放在两个地方

  • 函数或者方法下的注释里面
    例如:
def multiply(a, b):
    """
    fuction: 两个数相乘
    >>> multiply(4, 3)
    12
    >>> multiply('a', 3)
    'aaa'
    """
    return a * b
if __name__ == '__main__':
    import doctest
    doctest.testmod(verbose=True)
------------------------输出------------------------
Trying:
    multiply(4, 3)
Expecting:
    12
ok
Trying:
    multiply('a', 3)
Expecting:
    'aaa'
ok
1 items had no tests:
    __main__
1 items passed all tests:
   2 tests in __main__.multiply
2 tests in 2 items.
2 passed and 0 failed.
Test passed.
  • 模块的开头
'''
fuction: 两个数相乘
>>> multiply(4, 8)
12
>>> multiply('a', 5)
'aaa'
'''

def multiply(a, b):
    """
    fuction: 两个数相乘
    """
    return a * b
if __name__ == '__main__':
    import doctest
    doctest.testmod(verbose=True)
-------------------------输出---------------------
Failure
<Click to see difference>
**********************************************************************
File "F:/testpython/pratice.py", line 4, in pratice
Failed example:
    multiply(4, 8)
Expected:
    12
Got:
    32
Failure
<Click to see difference>
**********************************************************************
File "F:/testpython/pratice.py", line 6, in pratice
Failed example:
    multiply('a', 5)
Expected:
    'aaa'
Got:
    'aaaaa'

pytest框架是可以兼容doctest用例,执行的时候加个参数 --doctest-modules ,这样它就能自动搜索到doctest的用例
$ pytest -v --doctest-modules xxx.py
doctest独立文件
doctest内容也可以和代码抽离开,单独用一个.txt文件保存,在当前xxx.py同一目录新建一个xxx.txt文件,写入测试的文档,要先导入该功能,导入代码前面也要加>>>,如

>>> from xxx import multiply
>>> multiply(4, 3)
12
>>> multiply('a', 3)
'aaa'

cmd执行“python -m doctest -v xxx.txt”测试结果

pytest-html

  • 安装pytest-html
    pip install pytest-html
  • 执行,到pytest用例目录下
    pytest --html=report.html
    想要指定生成路径的话:pytest --html=./report/report.html
  • 报告独立显示
    pytest --html=report.html --self-contained-html
    -失败截图可以写到conftest,py文件里,这样用例运行时,只要检测到用例实例,就调用截图的方法,并且把截图存到html报告上
# conftest.py文件
# coding:utf-8

from selenium import webdriver
import pytest


driver = None

@pytest.mark.hookwrapper
def pytest_runtest_makereport(item):
    """
    当测试失败的时候,自动截图,展示到html报告中
    ** 作者:上海-悠悠 QQ交流群:588402570**
    :param item:
    """
    pytest_html = item.config.pluginmanager.getplugin('html')
    outcome = yield
    report = outcome.get_result()
    extra = getattr(report, 'extra', [])

    if report.when == 'call' or report.when == "setup":
        xfail = hasattr(report, 'wasxfail')
        if (report.skipped and xfail) or (report.failed and not xfail):
            file_name = report.nodeid.replace("::", "_")+".png"
            screen_img = _capture_screenshot()
            if file_name:
                html = '<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:600px;height:300px;" ' \
                       'onclick="window.open(this.src)" align="right"/></div>' % screen_img
                extra.append(pytest_html.extras.html(html))
        report.extra = extra

def _capture_screenshot():
    '''
    ** 作者:上海-悠悠 QQ交流群:588402570**
    截图保存为base64,展示到html中
    :return:
    '''
    return driver.get_screenshot_as_base64()


@pytest.fixture(scope='session', autouse=True)
def browser(request):
    global driver
    if driver is None:
        driver = webdriver.Firefox()

    def end():
        driver.quit()
    request.addfinalizer(end)
    return driver

用例文件,分为两个

# test_01.py文件

from selenium import webdriver
import time

#** 作者:上海-悠悠 QQ交流群:588402570**

def test_yoyo_01(browser):

    browser.get("https://www.cnblogs.com/yoyoketang/")
    time.sleep(2)
    t = browser.title
    assert t == "上海-悠悠"
# test_02.py文件

from selenium import webdriver
import time

# ** 作者:上海-悠悠 QQ交流群:588402570**

def test_yoyo_01(browser):

    browser.get("https://www.cnblogs.com/yoyoketang/")
    time.sleep(2)
    t = browser.title
    assert "上海-悠悠" in t

在cmd中运行 pytest --html=report.html --self-contained-html,生成报告

含失败截图

失败重试
需要 pip install pytest-rerunfailures 安装插件(这个插件目前这样安装会报错,可以在豆瓣等网站上下载好包在本地安装,也可以通过pycharm安装)
重跑命令:py.test --reruns 1 --html=report.html --self-contained-html,--reruns 1代表重跑一次

修改报告

添加描述(Description)列,添加可排序时间(Time)列,并删除链接(Link)列:(说实话conftest.py中的钩子函数和除fixture功能外的其他用法都还一头雾水)

from datetime import datetime
from py.xml import html
import pytest

@pytest.mark.optionalhook
def pytest_html_results_table_header(cells):
    cells.insert(2, html.th('Description'))
    cells.insert(1, html.th('Time', class_='sortable time', col='time'))
    cells.pop()

@pytest.mark.optionalhook
def pytest_html_results_table_row(report, cells):
    cells.insert(2, html.td(report.description))
    cells.insert(1, html.td(datetime.utcnow(), class_='col-time'))
    cells.pop()

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

推荐阅读更多精彩内容