日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > python >内容正文

python

研效优化实践:Python单测——从入门到起飞

發(fā)布時(shí)間:2024/2/28 python 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 研效优化实践:Python单测——从入门到起飞 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

作者:uniquewang,騰訊安全平臺后臺開發(fā)工程師

福生于微,積微成著,一行代碼的精心調(diào)試,一條指令的細(xì)心驗(yàn)證,一個(gè)字節(jié)的研磨優(yōu)化,都是影響企業(yè)研發(fā)效能工程的細(xì)節(jié)因素。而單元測試,是指針對軟件中的最小可測試單元的檢查驗(yàn)證,一個(gè)單元測試往往就是一小段代碼。本文基于騰訊安全平臺部的研效優(yōu)化實(shí)踐,介紹和總結(jié)公司第三大后端開發(fā)語言 python 的單測編寫方法,面向單測 0 基礎(chǔ)同學(xué),歡迎共同交流探討。

前言

本文面向單測 0 基礎(chǔ)的同學(xué),介紹和總結(jié)python的單測編寫方法。首先會介紹主流的單測框架,重點(diǎn) pytest。第二部分介紹如何使用 Mock 來輔助實(shí)現(xiàn)一些復(fù)雜場景測試,第三部分單測覆蓋率統(tǒng)計(jì)。中間穿插借助 IDE 工具來提效的手段

一、python 單測框架

單測框架無外乎封裝了測試相關(guān)的核心能力來輔助我們快速進(jìn)行單測,例如 java 的junit,golang 的gocover,python 目前的主流單測框架有unittest,nose,pytest

unittest

unittest 是 python 官方標(biāo)準(zhǔn)庫中自帶的單元測試框架,又是也稱為 PyUnit。類似于 JUnit 是 java 語言的標(biāo)準(zhǔn)單元測試框架一樣。unittest 對于 python2.7+,python3 使用方法一致。

基本實(shí)例
#?test_str.py import?unittestclass?TestStringMethods(unittest.TestCase):def?setUp(self):#?單測啟動前的準(zhǔn)備工作,比如初始化一個(gè)mysql連接對象#?為了說明函數(shù)功能,測試的時(shí)候沒有CMysql模塊注釋掉或者換做print學(xué)習(xí)self.conn?=?CMysql()def?tearDown(self):#?單測結(jié)束的收尾工作,比如數(shù)據(jù)庫斷開連接回收資源self.conn.disconnect()def?test_upper(self):self.assertEqual('foo'.upper(),?'FOO')def?test_isupper(self):self.assertTrue('FOO'.isupper())self.assertFalse('Foo'.isupper())def?test_split(self):s?=?'hello?world'self.assertEqual(s.split(),?['hello',?'world'])#?check?that?s.split?fails?when?the?separator?is?not?a?stringwith?self.assertRaises(TypeError):s.split(2)if?__name__?==?'__main__':unittest.main()
編寫方法
  • test_str.py,測試文件名約定 test_xxx

  • import unittest ,python 自帶,無需額外 pip 安裝

  • class 名字 Test 打頭

  • 類繼承 unittest.TestCase

  • 每個(gè)單測方法命名 test_xxx

  • 每個(gè)測試的關(guān)鍵是:調(diào)用 assertEqual() 來檢查預(yù)期的輸出;調(diào)用 assertTrue() 或 assertFalse() 來驗(yàn)證一個(gè)條件;調(diào)用 assertRaises() 來驗(yàn)證拋出了一個(gè)特定的異常。使用這些方法而不是 assert 語句是為了讓測試運(yùn)行者能聚合所有的測試結(jié)果并產(chǎn)生結(jié)果報(bào)告。注意這些方法是 unnitest 模塊的方法,需要使用 self 調(diào)用。

  • setUp()方法,單測啟動前的準(zhǔn)備工作,比如初始化一個(gè) mysql 連接對象

  • tearDown()方法,單測結(jié)束的收尾工作,比如數(shù)據(jù)庫斷開連接回收資源。setUp 和 tearDown 非常類似于 java 里的切面編程

  • unittest.main() 提供了一個(gè)測試腳本的命令行接口

參數(shù)化

標(biāo)準(zhǔn)庫的 unittest 自身不支持參數(shù)化測試,需要通過第三方庫來支持:parameterized 和 ddt。官方文檔這里完全沒做介紹,暫不深入

執(zhí)行結(jié)果
... ---------------------------------------------------------------------- Ran?3?tests?in?0.001sOK

nose

nose 是 Python 的一個(gè)第三方單元測試框架。這意味著,如果要使用 nose,需要先顯式安裝它

pip?install?nose

一個(gè)簡單的 nose 單元測試示例如下:

import?nosedef?test_example?():passif?__name__?==?'__main__':nose.runmodule()

需要注意的是,nose 已經(jīng)進(jìn)入維護(hù)模式,最近官方已經(jīng)沒有提交記錄,最近的 relase 是 jun 2,2015


這里由于使用經(jīng)驗(yàn)有限,也沒有深入去調(diào)研當(dāng)前的一個(gè)使用情況,就不做進(jìn)一步介紹,有興趣自行 google。

pytest

先放官方 slogan

pytest: helps you write better programs

The pytest framework makes it easy to write small tests, yet scales to support complex functional testing for applications and libraries.

pytest 得益于其簡單的實(shí)現(xiàn)方案、豐富的參數(shù)化功能、易用的前后置邏輯(固件)特性,以及通用的 mock 功能,目前在是非常火爆的 python 單測框架。

安裝

pytest 是第三方包,使用功能需要提前安裝,支持 python2.7 和 python3.5 及以上

pip?install?pytest
測試發(fā)現(xiàn)

pytest 在所選目錄中查找test_*.py或*_test.py文件。在選定的文件中,pytest 在類之外查找?guī)熬Y的測試函數(shù),并在帶前綴的測試類中查找?guī)熬Y的測試方法(無__init__()方法)。

基本實(shí)例

直接放官網(wǎng)的幾個(gè)例子感受一下

#?content?of?test_sample1.py def?inc(x):return?x?+?1def?test_answer():assert?inc(3)?==?5 #?content?of?test_class.py import?pytestdef?f():raise?SystemExit(1)class?TestClass:def?test_one(self):x?=?"this"assert?"h"?in?xdef?test_two(self):x?=?"hello"assert?hasattr(x,?"check")def?test_mytest(self):with?pytest.raises(SystemExit):f()
運(yùn)行
$?pytest

無參數(shù),運(yùn)行當(dāng)前目錄及子目錄下所有的測試文件,發(fā)現(xiàn)規(guī)則見上

$?pytest?test_sample1.py

運(yùn)行指定測試文件

$?pytest?test_class.py::TestClass [root?test]#?pytest?test_class.py::TestClass ============================================?test?session?starts?============================================= platform?linux?--?Python?3.6.8,?pytest-6.2.4,?py-1.10.0,?pluggy-0.13.1 rootdir:?/data/ftp/unique/mf_web_proj_v2/test collected?3?itemstest_class.py?.F.??????????????????????????????????????????????????????????????????????????????????????[100%]==================================================?FAILURES?================================================== _____________________________________________?TestClass.test_two?_____________________________________________self?=?<test_class.TestClass?object?at?0x7f55f3e0e518>def?test_two(self):x?=?"hello" >???????assert?hasattr(x,?"check") E???????AssertionError:?assert?False E????????+??where?False?=?hasattr('hello',?'check')test_class.py:13:?AssertionError ==========================================?short?test?summary?info?=========================================== FAILED?test_class.py::TestClass::test_two?-?AssertionError:?assert?False ========================================?1?failed,?2?passed?in?0.03s?=========================================

運(yùn)行指定指定測試類。根據(jù)運(yùn)行結(jié)果可以看出 test_two 測試方法失敗

$?pytest?test_class.py::TestClass::test_two

運(yùn)行指定測試類下的指定測試方法

跳過指定測試

通過@pytest.mark.skip注解跳過指定的測試,跳過測試的原因比如有當(dāng)前方法已經(jīng)發(fā)現(xiàn)有 bug 還在 fix,測試的數(shù)據(jù)數(shù)據(jù)庫未就緒,環(huán)境不滿足等等

#?content?of?test_skip.py import?pytest @pytest.mark.skip def?test_min():values?=?(2,?3,?1,?4,?6)assert?min(values)?==?1def?test_max():values?=?(2,?3,?1,?4,?6)assert?5?in?values
標(biāo)記函數(shù)

類似于上面的 skip,也可指定其他的標(biāo)簽,使用 @pytest.mark 在函數(shù)上進(jìn)行各種標(biāo)記。比如 fixed,finished 等等你想要的各種標(biāo)簽。然后在運(yùn)行的時(shí)候可以根據(jù)指定的標(biāo)簽跑某些標(biāo)簽的測試方法。

#?test_with_mark.py@pytest.mark.finished def?test_func1():assert?1?==?1@pytest.mark.unfinished def?test_func2():assert?1?!=?1

測試時(shí)使用-m選擇標(biāo)記的測試函數(shù)

$?pytest?-m?finished?test_with_mark.py
參數(shù)化測試

通過參數(shù)化測試,我們可以向斷言中添加多個(gè)值。這個(gè)功能使用頻率非常高,我們可以模擬各種正常的、非法的入?yún)ⅰ.?dāng)然很多同學(xué)習(xí)慣直接在函數(shù)內(nèi)部構(gòu)造一個(gè)參數(shù)集合,通過 for 循環(huán)挨個(gè)測,通過 try/catch 的方式捕獲異常使得所有參數(shù)都跑一遍,,但要分析測試結(jié)果就需要做不少額外的工作。在 pytest 中,我們有更好的解決方法,就是參數(shù)化測試,即每組參數(shù)都獨(dú)立執(zhí)行一次測試。使用的工具就是 @pytest.mark.parametrize(argnames, argvalues)。在函數(shù)內(nèi)部的 for 循環(huán)模式,會當(dāng)做一次測試用例,而采用pytest.mark.parametrize方式會產(chǎn)生 N 個(gè)測試用例,N=len(argnames)。

#?test_parametrize_sigle.py #?單參數(shù) import?pytest @pytest.mark.parametrize('passwd',['123456','abcdefdfs','as52345fasdf4']) def?test_passwd_length(passwd):assert?len(passwd)?>=?8 #?test_parametrize_multiple.py #?多參數(shù) import?pytest @pytest.mark.parametrize('user,?passwd',[('jack',?'abcdefgh'),('tom',?'a123456a')]) def?test_passwd_md5(user,?passwd):db?=?{'jack':?'e8dc4081b13434b45189a720b77b6818','tom':?'1702a132e769a623c1adb78353fc9503'}import?hashlibassert?hashlib.md5(passwd.encode()).hexdigest()?==?db[user]

argnames可以是用逗號分隔的字符串,也可以是列表,比如上面的'user, passwd',或者['user','passwd']

argvalues類型是 List[Tuple]。

fixture
定義

fixture 翻譯過來是固定,固定狀態(tài);固定物;【機(jī)械工程】裝置器,工件夾具,直接看官方的解釋更好理解。

In testing, a fixture provides a defined, reliable and consistent context for the tests. This could include environment (for example a database configured with known parameters) or content (such as a dataset).

Fixtures define the steps and data that constitute the arrange phase of a test (see Anatomy of a test). In pytest, they are functions you define that serve this purpose. They can also be used to define a test’s act phase; this is a powerful technique for designing more complex tests.

谷歌翻譯

在測試中,fixture 為測試提供定義的、可靠的和一致的上下文。這可能包括環(huán)境(例如配置了已知參數(shù)的數(shù)據(jù)庫)或內(nèi)容(例如數(shù)據(jù)集)。

Fixtures 定義了構(gòu)成測試編排階段的步驟和數(shù)據(jù)(參見 Anatomy of a test) . 在 pytest 中,它們是您定義的用于此目的的函數(shù)。它們還可用于定義測試的 行為階段;這是設(shè)計(jì)更復(fù)雜測試的強(qiáng)大技術(shù)。

總結(jié)下就是使用fixture可以為你的測試用例定義一些可復(fù)用的、一致的功能支持,其中最常見的可能就是數(shù)據(jù)庫的初始連接和最后關(guān)閉操作,測試數(shù)據(jù)集的統(tǒng)一提供接口。功能類似于上面unittest框架的 setup()和 teardown()。同時(shí)也是 pytest 更加出眾的地方,包括:

  • 有獨(dú)立的命名,并通過聲明它們從測試函數(shù)、模塊、類或整個(gè)項(xiàng)目中的使用來激活。

  • 按模塊化的方式實(shí)現(xiàn),每個(gè) fixture 都可以互相調(diào)用。

  • fixture 的范圍從簡單的單元測試到復(fù)雜的功能測試,可以對 fixture 配置參數(shù),或者跨函數(shù) function,類 class,模塊 module 或整個(gè)測試 session 范圍。

使用
#?官方Quick?example import?pytestclass?Fruit:def?__init__(self,?name):self.name?=?nameself.cubed?=?Falsedef?cube(self):self.cubed?=?Trueclass?FruitSalad:def?__init__(self,?*fruit_bowl):self.fruit?=?fruit_bowlself._cube_fruit()def?_cube_fruit(self):for?fruit?in?self.fruit:fruit.cube()#?Arrange @pytest.fixture???#1 def?fruit_bowl():return?[Fruit("apple"),?Fruit("banana")]def?test_fruit_salad(fruit_bowl):?#2#?Actfruit_salad?=?FruitSalad(*fruit_bowl)#?Assertassert?all(fruit.cubed?for?fruit?in?fruit_salad.fruit)

使用很簡單:

  • 1 通過@pytest.fixture裝飾器裝飾一個(gè)函數(shù)

  • 2 直接將 fixture 作為參數(shù)傳給測試用例,這樣就可以做到測試用例只關(guān)心當(dāng)前的測試邏輯,數(shù)據(jù)準(zhǔn)備等交給 fixture 來搞定

#!/usr/bin/env?python3 #?-*-?coding:?utf-8?-*- import?sqlite3 import?pytest@pytest.fixture def?connection():connection?=?sqlite3.connect(':memory:')?#?1yield?connection?#?2connection.close()?#?3@pytest.fixture(autouse=True)?#?4 def?insert_one_item(connection):session?=?connection.cursor()session.execute('''CREATE?TABLE?numbers(id?int?,?existing?boolean)''')session.execute('INSERT?INTO?numbers?VALUES?(101,?1)')connection.commit()def?test_exist_num(connection):session?=?connection.cursor()session.execute('''SELECT?COUNT(1)?FROM?numbers?WHERE?id=101''')results?=?session.fetchall()assert?len(results)?>?0

這是一個(gè)通過 fixture 來管理 db 連接的例子,

  • 1 前置準(zhǔn)備

  • 2 yield 關(guān)鍵詞將 fixture 分為兩部分,yield 之前的代碼屬于預(yù)處理,會在測試前執(zhí)行;yield 之后的代碼屬于后處理,將在測試完成后執(zhí)行。如果沒有返回給yield即可

  • 3 結(jié)束收尾

  • 4 @pytest.fixture(autouse=True) autouse 關(guān)鍵字告訴框架在跑用例之前自動運(yùn)行該 fixture

作用域

通過 scope 參數(shù)聲明作用域,可選項(xiàng)有:

  • function: 函數(shù)級,每個(gè)測試函數(shù)都會執(zhí)行一次固件;

  • class: 類級別,每個(gè)測試類執(zhí)行一次,所有方法都可以使用;

  • module: 模塊級,每個(gè)模塊執(zhí)行一次,模塊內(nèi)函數(shù)和方法都可使用;

  • session: 會話級,一次測試只執(zhí)行一次,所有被找到的函數(shù)和方法都可用。

@pytest.fixture(scope='function') def?func_scope():pass

默認(rèn)的作用域?yàn)?function

管理
  • 可以單獨(dú)在每個(gè)測試 py 文件中放置需要的 fixture

  • 對于復(fù)雜項(xiàng)目,可以在不同的目錄層級定義 conftest.py,其作用域?yàn)槠渌诘哪夸浐妥幽夸洝@缟厦娴膄ixture/connection,就應(yīng)該放在公用的 conftest.py 中,統(tǒng)一管理測試數(shù)據(jù)的連接。這樣就很好的做到的復(fù)用。

借助 IDE 提效

已 PyCharm 為例介紹,vscode 等 ide 應(yīng)該大同小異

  • Settings/Preferences | Tools | Python Integrated Tools選擇單測框架


  • 創(chuàng)建目標(biāo)測試代碼文件 Car.py

  • #?content?of?Car.py class?Car:def?__init__(self,?speed=0):self.speed?=?speedself.odometer?=?0self.time?=?0def?say_state(self):print("I'm?going?{}?kph!".format(self.speed))def?accelerate(self):self.speed?+=?5def?brake(self):self.speed?-=?5def?step(self):self.odometer?+=?self.speedself.time?+=?1def?average_speed(self):if?self.time?!=?0:return?self.odometer?/?self.timeelse:pass
  • 測試 break 方法,右鍵或者 Ctrl+Shift+T 選中break(),選擇Go To | Test。當(dāng)然也可以直接直接右鍵一次性為多個(gè)方法創(chuàng)建對應(yīng)測試用例

  • 點(diǎn)擊Create New Test...,創(chuàng)建測試文件

    2.png

    完善測試代碼邏輯

    3.png

    點(diǎn)擊運(yùn)行按鈕,可以選擇運(yùn)行測試或者調(diào)試測試

    4.png

    運(yùn)行結(jié)果,4 個(gè)測試用例,有 2 個(gè)失敗。

    二、Mock

    上面的介紹的 pytest 框架可以輔助我們解決掉日常工作 70%的單測問題,但是對于一些不容易構(gòu)造/獲取的對象,需要依賴外部其他接口,特定運(yùn)行環(huán)境等場景,需要借助 Mock 工具來幫我們構(gòu)建全面的單測用例,而不至于卡在某一環(huán)境無法繼續(xù)推進(jìn)。

    舉一個(gè)實(shí)際工作中分布式任務(wù)下發(fā)的場景,master 節(jié)點(diǎn)需要通過調(diào)用資源管理服務(wù)下的 worker 節(jié)點(diǎn) cpu 占用率、內(nèi)存占用率等多項(xiàng)資源接口,來評估任務(wù)下發(fā)哪些節(jié)點(diǎn)。但是現(xiàn)在這些資源接口部署在 idc 環(huán)境,或者接口由其他同學(xué)在負(fù)責(zé)開發(fā)中,這時(shí)我們需要測試調(diào)度功能是否正常工作。

    根據(jù)本人之前的經(jīng)歷,一個(gè)簡單的辦法是搭建一個(gè)測試的服務(wù)器,然后全部模擬實(shí)現(xiàn)一遍這些接口。之前這樣做確實(shí)也挺爽,但是后邊就麻煩了,調(diào)用的接口越來越來,每次都要全部實(shí)現(xiàn)一遍。最重要的時(shí)候測試的時(shí)候這個(gè)服務(wù)還不能掛,不然就跑步起來了。:)

    其實(shí)還有一個(gè)更佳方案就是就是我們用一個(gè) mock 對象替換掉和遠(yuǎn)端 rpc 服務(wù)的交互過程,給定任何我們期望返回的指標(biāo)值。

    unittest

    python2.7 需要手動安裝 mock 模塊

    pip?install?mock

    python3.3 開始,mock 模塊已經(jīng)被合并到標(biāo)準(zhǔn)庫中,命名為 unittest.mock,可以直接 imort 使用

    from?unittest?import?mock

    mock 支持對象、方法、異常的寫法,直接上代碼說話,還是上面提到的分布式任務(wù)下發(fā)的場景。

    ResourceManager負(fù)責(zé)資源管理,get_cpu_cost通過網(wǎng)絡(luò)請求拉取節(jié)點(diǎn)利用率。

    WorkerManager負(fù)責(zé)管理所有 worker 節(jié)點(diǎn),get_all_works返回所有 worker 節(jié)點(diǎn)。

    Schedule負(fù)責(zé)任務(wù)調(diào)度,核心sch方法挑選一個(gè)負(fù)載最低的節(jié)點(diǎn)下發(fā)任務(wù)

    #?content?in?res.py class?ResourceManager:#?資源管理類def?get_cpu_cost(self,ip):#?已經(jīng)開發(fā)完的邏輯import?requestsimport?jsonresponse?=?requests.get("http://xx.oa.com/%s"%ip)rs?=?json.loads(response.json())return?rs["num"]class?WorkerManager:def?get_all_works(self):#?已經(jīng)開發(fā)完的邏輯import?pymysql#?打開數(shù)據(jù)庫連接db?=?pymysql.connect("localhost",?"xx",?"xx",?"TESTDB")cursor?=?db.cursor()cursor.execute("SELECT?ip?from?works?where?alive=1")ips?=?cursor.fetchall()db.close()return?[ip[0]?for?ip?in?ips] #?content?in?schedule.py from?src.demo.res?import?WorkerManager,?ResourceManagerclass?Schedule:def?__init__(self):self.work_mgr?=?WorkerManager()self.res_mgr?=?ResourceManager()def?sch(self):ips?=?self.work_mgr.get_all_works()cpu_cost?=?[self.res_mgr.get_cpu_cost(ip)?for?ip?in?ips]min_cost_index?=?cpu_cost.index(min(cpu_cost))?#?獲取負(fù)載最低的ipreturn?ips[min_cost_index]

    現(xiàn)在我們要針對Schedule類中的方法進(jìn)行測試。此時(shí)獲取節(jié)點(diǎn)的方法要從 db 拉取,獲取資源負(fù)責(zé)的方法要從遠(yuǎn)端網(wǎng)絡(luò)請求拉取。兩部分目前都很難搭建環(huán)境,我們希望 mock 這兩部分,來測試我們調(diào)度的邏輯是否滿足挑選負(fù)載最低的節(jié)點(diǎn)調(diào)度

    • mock 對象的寫法

    #?content?in?test_mock.py import?ipaddress import?unittest from?unittest?import?mock,?TestCasefrom?src.demo.schedule?import?ResourceManager,?WorkerManager?#1 from?src.demo.schedule?import?Scheduledef?mock_cpu_cost(ip):#?ip轉(zhuǎn)整數(shù)return?int(ipaddress.IPv4Address(ip))class?TestSchedule(TestCase):@mock.patch.object(ResourceManager,?'get_cpu_cost')?#2@mock.patch.object(WorkerManager,?'get_all_works')def?test_sch(self,?all_workers,?cpu_cost):?#?3workers?=?['1.1.1.1',?'2.2.2.2']cpu_cost.side_effect?=?mock_cpu_cost?#4all_workers.return_value?=?workers?#?5sch?=?Schedule()res?=?sch.sch()self.assertEqual(res,?workers[0])?#6if?__name__?==?'__main__':unittest.main()

    簡要說下關(guān)鍵幾步的寫法:

  • 這里import我們要 mock 的類,下面兩種 import 方法都可以

  • ?from?src.demo.res?import?ResourceManager,WorkerManager ?from?src.demo.schedule?import?ResourceManager,?WorkerManager
  • 使用@mock.patch.object注解寫法,參數(shù)ResourceManager是要 mock 的類對象(類名),'get_cpu_cost'是要 mock 的方法名

  • 我們一次性 mock 兩個(gè)對象,@mock.patch.object的順序從下到上來從前到后對應(yīng)參數(shù)名。all_workers和cpu_cost是兩個(gè)臨時(shí)名字,你可以自行定義。

  • 重點(diǎn):cpu_cost.side_effect = mock_cpu_cost用自定義方法替換目標(biāo)對象方法。

  • 重點(diǎn):all_workers.return_value = workers用自定義返回值來替換目標(biāo)對象返回值,直白說就是接下來調(diào)用get_all_works都會返回['1.1.1.1', '2.2.2.2']

  • 標(biāo)準(zhǔn) unittest 寫法來判斷言

    • mock 方法的寫法

    class?TestSchedule(TestCase):@mock.patch('src.demo.res.ResourceManager.get_cpu_cost')@mock.patch('src.demo.res.WorkerManager.get_all_works')def?test_sch(self,?all_workers,?cpu_cost):?#?3#?其他部分完全一致

    唯一不同點(diǎn)@mock.patch('src.demo.res.ResourceManager.get_cpu_cost'),參數(shù)寫方法全路徑。

    當(dāng)然 return_value 和 side_effect 也可一次定義

    class?TestSchedule(TestCase):#?mock對象的寫法@mock.patch.object(ResourceManager,?'get_cpu_cost')@mock.patch.object(WorkerManager,?'get_all_works',return_value=['1.1.1.1',?'2.2.2.2'])#?mock方法的寫法#?@mock.patch('src.demo.res.ResourceManager.get_cpu_cost')#?@mock.patch('src.demo.res.WorkerManager.get_all_works')def?test_sch(self,?_,?cpu_cost):cpu_cost.side_effect?=?mock_cpu_cost#?all_workers.return_value?=?workerssch?=?Schedule()res?=?sch.sch()self.assertEqual(res,?'1.1.1.1')

    pytest

    pytest 框架沒有提供 mock 模塊,使用需要在安裝一個(gè)包 pytest-mock

    pip?install?pytest-mock

    使用方法及寫法幾乎與unittest.mock完全一致

    class?TestSchedule:def?test_sch(self,?mocker):?#?1workers?=?['1.1.1.1',?'2.2.2.2']mocker.patch.object(ResourceManager,?'get_cpu_cost',?side_effect=mock_cpu_cost)?#?2mocker.patch.object(WorkerManager,?'get_all_works',?return_value=workers)sch?=?Schedule()res?=?sch.sch()assert?res?==?workers[0]
  • 無需手動 import,test 方法參數(shù)使用mocker,pytest-mock 會自動注入。名字不能換,只能使用`mocker

  • 寫法和 unittest.mock 完全一致。

  • 目前沒有找到原生優(yōu)雅寫注解的辦法,只能吧 mock 邏輯放到 test 方法中,后邊封裝后再補(bǔ)充

  • 如果掃一眼源碼可以看到 mock 是 pytest_mock.plugin 模塊下的一個(gè) fixture

    def?_mocker(pytestconfig:?Any)?->?Generator[MockerFixture,?None,?None]:"""Return?an?object?that?has?the?same?interface?to?the?`mock`?module,?buttakes?care?of?automatically?undoing?all?patches?after?each?test?method."""result?=?MockerFixture(pytestconfig)yield?resultresult.stopall() mocker?=?pytest.fixture()(_mocker)??#?default?scope?is?function class_mocker?=?pytest.fixture(scope="class")(_mocker) module_mocker?=?pytest.fixture(scope="module")(_mocker) package_mocker?=?pytest.fixture(scope="package")(_mocker) session_mocker?=?pytest.fixture(scope="session")(_mocker)

    三、覆蓋率

    覆蓋率是用來衡量單元測試對功能代碼的測試情況,通過統(tǒng)計(jì)單元測試中對功能代碼中行、分支、類等模擬場景數(shù)量,來量化說明測試的充分度

    同 Java 的 JaCoCo、Golang 的 GoCover 等一樣,Python 也有自己的單元測試覆蓋率統(tǒng)計(jì)工具,Coverage 就是使用最廣的一種。

    安裝

    pip?install?coverage

    使用

    For pytest

    coverage?run?-m?pytest?arg1?arg2?arg3

    For unittest

    coverage?run?-m?unittest?discover
    上報(bào)結(jié)果
    $?coverage?report?-m Name??????????????????????Stmts???Miss??Cover???Missing ------------------------------------------------------- my_program.py????????????????20??????4????80%???33-35,?39 my_other_module.py???????????56??????6????89%???17-23 ------------------------------------------------------- TOTAL????????????????????????76?????10????87%
    更多展示

    生成 html 文件及 css 等樣式,豐富展示

    coverage?html
    借助 IDE 提效

    右鍵呼出跑整個(gè)測試文件


    小箭頭跑單個(gè)測試用例


    右側(cè)或者左側(cè)項(xiàng)目樹可以看到整個(gè)覆蓋情況


    向上小箭頭可以導(dǎo)出覆蓋情況報(bào)告,Save會直接打開瀏覽器給出結(jié)果,很方便。點(diǎn)擊具體文件還有詳細(xì)說明。




    接入公司覆蓋率平臺

    如果所在公司有覆蓋率檢測平臺,接入原理很簡單。通過發(fā)布流水線集成項(xiàng)目代碼,拉取到構(gòu)建機(jī),將上面在本地跑的 coverage 放到構(gòu)建機(jī)上執(zhí)行,將結(jié)果上報(bào)到遠(yuǎn)端平臺。

    后記

    在騰訊安全平臺部實(shí)際研發(fā)與測試工作中,單元測試是保證代碼質(zhì)量的有效手段,也是效能優(yōu)化實(shí)踐的重要一環(huán)。本文是筆者在學(xué)習(xí) python 單測整個(gè)過程的總結(jié),介紹了 python 的幾種主流單測框架,Mock 的使用以及使用 coverage 來計(jì)算單測覆蓋率。推薦使用 pytest 來進(jìn)行日常測試框架,支持的插件足夠豐富,希望可以對有需要接入 python 單測的同學(xué)有些幫助。安平研效團(tuán)隊(duì)仍在持續(xù)探索優(yōu)化中,若大家在工作中遇到相關(guān)問題,歡迎一起交流探討,共同把研效測試工作做好、做強(qiáng)。

    github 倉庫地址:

    https://github.com/nudt681/python_unittest_guide

    參考文章

    pytest: helps you write better programs

    Pytest 使用手冊— learning-pytest 1.0 文檔

    Python 中 Mock 到底該怎么玩?一篇文章告訴你(超全) - 知乎

    Step 3. Test your first Python application | PyCharm - JetBrains

    總結(jié)

    以上是生活随笔為你收集整理的研效优化实践:Python单测——从入门到起飞的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。