Pytest使用鉤子方法生成自定義測(cè)試報(bào)告

使用Pytest測(cè)試框架生成測(cè)試報(bào)告最常用的便是使用pytest-htmlallure-pytest兩款插件了缀踪。
pytest-html簡(jiǎn)單(支持單html測(cè)試報(bào)告)闭翩,allure-pytest則漂亮而強(qiáng)大挣郭。
當(dāng)然想要使用自定義模板生成測(cè)試報(bào)告也非常簡(jiǎn)單,簡(jiǎn)單實(shí)現(xiàn)步驟如下:

  1. 介入Pytest運(yùn)行流程疗韵,運(yùn)行后自動(dòng)生成HTML測(cè)試報(bào)告:使用Hooks方法
  2. 拿到運(yùn)行結(jié)果統(tǒng)計(jì)數(shù)據(jù):鉤子方法pytest_terminal_summary的terminalreporter對(duì)象的stats屬性中
  3. 渲染HTML報(bào)告模板生成測(cè)試報(bào)告:使用Jinjia2

經(jīng)研究兑障,Hooks方法pytest_terminal_summary及運(yùn)行完畢生成命令行總結(jié)中包含的terminalreporter對(duì)象的stats屬性中包含我們需要的測(cè)試結(jié)果統(tǒng)計(jì)。

鉤子運(yùn)行流程可以參考:https://www.cnblogs.com/superhin/p/11733499.html

使用Pytest的對(duì)象自式锻簟(對(duì)象.__dict__dir(對(duì)象))我們可以很方便的查看對(duì)象有哪些屬性流译,在conftest.py文件中編寫鉤子函數(shù)如下:

# file: conftest.py
def pytest_terminal_summary(terminalreporter, exitstatus, config):
    from pprint import pprint
    pprint(terminalreporter.__dict__)  # Python自省,輸出terminalreporter對(duì)象的屬性字典

編寫一些實(shí)例用例者疤,運(yùn)行后屏幕輸出的terminalreporter對(duì)象相關(guān)屬性如下:

{'_already_displayed_warnings': None,
 '_collect_report_last_write': 1629647274.594309,
 '_keyboardinterrupt_memo': None,
 '_known_types': ['failed',
                  'passed',
                  'skipped',
                  'deselected',
                  'xfailed',
                  'xpassed',
                  'warnings',
                  'error'],
 '_main_color': 'red',
 '_numcollected': 7,
 '_progress_nodeids_reported': {'testcases/test_demo.py::test_01',
                                'testcases/test_demo.py::test_02',
                                'testcases/test_demo.py::test_03',
                                'testcases/test_demo.py::test_04',
                                'testcases/test_demo.py::test_05',
                                'testcases/test_demo.py::test_06',
                                'testcases/test_demo2.py::test_02'},
 '_screen_width': 155,
 '_session': <Session pythonProject1 exitstatus=<ExitCode.TESTS_FAILED: 1> testsfailed=2 testscollected=7>,
 '_sessionstarttime': 1629647274.580377,
 '_show_progress_info': 'progress',
 '_showfspath': None,
 '_tests_ran': True,
 '_tw': <_pytest._io.terminalwriter.TerminalWriter object at 0x10766ebb0>,
 'config': <_pytest.config.Config object at 0x106239610>,
 'currentfspath': None,
 'hasmarkup': True,
 'isatty': True,
 'reportchars': 'wfE',
 'startdir': local('/Users/superhin/項(xiàng)目/pythonProject1'),
 'startpath': PosixPath('/Users/superhin/項(xiàng)目/pythonProject1'),
 'stats': {'': [<TestReport 'testcases/test_demo.py::test_01' when='setup' outcome='passed'>,
                <TestReport 'testcases/test_demo.py::test_01' when='teardown' outcome='passed'>,
                <TestReport 'testcases/test_demo.py::test_02' when='setup' outcome='passed'>,
                <TestReport 'testcases/test_demo.py::test_02' when='teardown' outcome='passed'>,
                <TestReport 'testcases/test_demo.py::test_03' when='setup' outcome='passed'>,
                <TestReport 'testcases/test_demo.py::test_03' when='teardown' outcome='passed'>,
                <TestReport 'testcases/test_demo.py::test_04' when='teardown' outcome='passed'>,
                <TestReport 'testcases/test_demo.py::test_05' when='setup' outcome='passed'>,
                <TestReport 'testcases/test_demo.py::test_05' when='teardown' outcome='passed'>,
                <TestReport 'testcases/test_demo.py::test_06' when='setup' outcome='passed'>,
                <TestReport 'testcases/test_demo.py::test_06' when='teardown' outcome='passed'>,
                <TestReport 'testcases/test_demo2.py::test_02' when='setup' outcome='passed'>,
                <TestReport 'testcases/test_demo2.py::test_02' when='teardown' outcome='passed'>],
           'failed': [<TestReport 'testcases/test_demo.py::test_02' when='call' outcome='failed'>,
                      <TestReport 'testcases/test_demo.py::test_03' when='call' outcome='failed'>],
           'passed': [<TestReport 'testcases/test_demo.py::test_01' when='call' outcome='passed'>,
                      <TestReport 'testcases/test_demo2.py::test_02' when='call' outcome='passed'>],
           'skipped': [<TestReport 'testcases/test_demo.py::test_04' when='setup' outcome='skipped'>],
           'xfailed': [<TestReport 'testcases/test_demo.py::test_05' when='call' outcome='skipped'>],
           'xpassed': [<TestReport 'testcases/test_demo.py::test_06' when='call' outcome='passed'>]}}

可以看到stats屬性為字典格式福澡,其中包含了setup/teardown狀態(tài),以及各種狀態(tài)(passed驹马,failed革砸,skipped,xfailed糯累,xpassed)等用例結(jié)果(TestReport)列表算利。
我進(jìn)一步打印一個(gè)TestReport對(duì)向

# file: conftest.py
def pytest_terminal_summary(terminalreporter, exitstatus, config):
    from pprint import pprint
    # pprint(terminalreporter.__dict__)  # Python自省,輸出terminalreporter對(duì)象的屬性字典
    pprint(terminalreporter.stats['passed'][0].__dict__)  # 第一個(gè)通過用例的TestReport對(duì)象屬性

打印結(jié)果如下:

{'duration': 0.00029346700000010273,
 'extra': [],
 'keywords': {'pythonProject1': 1, 'test_01': 1, 'testcases/test_demo.py': 1},
 'location': ('testcases/test_demo.py', 11, 'test_01'),
 'longrepr': None,
 'nodeid': 'testcases/test_demo.py::test_01',
 'outcome': 'passed',
 'sections': [('Captured stdout call', 'test01\n')],
 'user_properties': [],
 'when': 'call'}

我們自己定義報(bào)告模板寇蚊,使用Jinjia2笔时,將stats屬性中的統(tǒng)計(jì)數(shù)據(jù)渲染生成自定義報(bào)告,完整代碼如下:

Jinja2官方使用文檔參考:http://doc.yonyoucloud.com/doc/jinja2-docs-cn/index.html

# file: conftest.py
from jinja2 import Template

html_tpl = '''<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{title}}</title>
    <style>
      table {border-spacing: 0;}
      th {background-color: #ccc}
      td {padding: 5px; border: 1px solid #ccc;}
    </style>
</head>
<body>
    <h2>{{title}}</h2>
    <table>
        <tr> <th>函數(shù)名</th> <th>用例id</th> <th>狀態(tài)</td> <th>用例輸出</th> <th>執(zhí)行時(shí)間</th> </tr>
        {% for item in results %}
        <tr>
            <td>{{item.location[2]}}</td>
            <td>{{item.nodeid}}</td>
            <td>{{item.outcome.strip()}}</td>
            <td>{% if item.sections %}{{item.sections[0][1].strip()}}{% endif %}</td>
            <td>{{item.duration}}</td>
        </tr>
        {% endfor %}
    </table>
</body>
</html>'''

def pytest_terminal_summary(terminalreporter, exitstatus, config):
    results = []
    for status in ['passed', 'failed', 'skipped', 'xfailed', 'xpassed']:
        if status in terminalreporter.stats:
            results.extend(terminalreporter.stats[status])

    html = Template(html_tpl).render(title='測(cè)試報(bào)告', results=results)
    with open('report.html', 'w', encoding='utf-8') as f:
        f.write(html)

在命令行運(yùn)行pytest生成的測(cè)試報(bào)告report.html示例如下


Pytest自定義測(cè)試報(bào)告
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末仗岸,一起剝皮案震驚了整個(gè)濱河市允耿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌扒怖,老刑警劉巖较锡,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異盗痒,居然都是意外死亡蚂蕴,警方通過查閱死者的電腦和手機(jī)低散,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來骡楼,“玉大人熔号,你說我怎么就攤上這事∧裾” “怎么了引镊?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)篮条。 經(jīng)常有香客問我弟头,道長(zhǎng),這世上最難降的妖魔是什么涉茧? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任赴恨,我火速辦了婚禮,結(jié)果婚禮上伴栓,老公的妹妹穿的比我還像新娘伦连。我一直安慰自己,他們只是感情好挣饥,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布除师。 她就那樣靜靜地躺著,像睡著了一般扔枫。 火紅的嫁衣襯著肌膚如雪汛聚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天短荐,我揣著相機(jī)與錄音倚舀,去河邊找鬼。 笑死忍宋,一個(gè)胖子當(dāng)著我的面吹牛痕貌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播糠排,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼舵稠,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了入宦?” 一聲冷哼從身側(cè)響起哺徊,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎乾闰,沒想到半個(gè)月后落追,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涯肩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年轿钠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了巢钓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡疗垛,死狀恐怖症汹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情继谚,我是刑警寧澤烈菌,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布阵幸,位于F島的核電站花履,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏挚赊。R本人自食惡果不足惜诡壁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荠割。 院中可真熱鬧妹卿,春花似錦、人聲如沸蔑鹦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嚎朽。三九已至铺纽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哟忍,已是汗流浹背狡门。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锅很,地道東北人其馏。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像爆安,于是被迫代替她去往敵國和親叛复。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容