<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          用 coverage 模塊提高 Python 開發(fā)效率

          共 5994字,需瀏覽 12分鐘

           ·

          2020-11-29 02:29

          前幾天,聽了公司某位大佬關(guān)于編程心得的體會(huì),其中講到了“測(cè)試驅(qū)動(dòng)開發(fā)”,感覺自己的測(cè)試技能薄弱,因此,寫下這篇文章,希望對(duì)測(cè)試能有個(gè)入門。這段時(shí)間,筆者也體會(huì)到了測(cè)試的價(jià)值,一句話,學(xué)會(huì)測(cè)試,能夠讓你的開發(fā)更加高效。

          本文將介紹以下兩個(gè)方面的內(nèi)容:

          • Test with Coverage

          • Mock

          Test with Coverage

          測(cè)試覆蓋率通常被用來衡量測(cè)試的充分性和完整性。從廣義的角度講,主要分為兩大類:面向項(xiàng)目的需求覆蓋率和更偏向技術(shù)的代碼覆蓋率。對(duì)于開發(fā)人員來說,我們更注重代碼覆蓋率。

          代碼覆蓋率指的是至少執(zhí)行了一次的條目數(shù)占整個(gè)條目數(shù)的百分比。如果條目數(shù)是語句,對(duì)應(yīng)的就是代碼行覆蓋率;如果條目數(shù)是函數(shù),對(duì)應(yīng)的就是函數(shù)覆蓋率;如果條目數(shù)是路徑,對(duì)應(yīng)的就是路徑覆蓋率,等等。統(tǒng)計(jì)代碼覆蓋率的根本目的是找出潛在的遺漏測(cè)試用例,并有針對(duì)性的進(jìn)行補(bǔ)充,同時(shí)還可以識(shí)別出代碼中那些由于需求變更等原因造成的廢棄代碼。通常我們希望代碼覆蓋率越高越好,代碼覆蓋率越高越能說明你的測(cè)試用例設(shè)計(jì)是充分且完備的,但測(cè)試的成本會(huì)隨著代碼覆蓋率的提高而增加。

          在Python中,coverage模塊幫助我們實(shí)現(xiàn)了代碼行覆蓋率,我們可以方便地使用它來完整測(cè)試的代碼行覆蓋率。

          我們通過一個(gè)例子來介紹coverage模塊的使用。

          首先,我們有腳本func_add.py,實(shí)現(xiàn)了add函數(shù),代碼如下:

          #?-*-?coding:?utf-8?-*-

          def?add(a,?b):
          ????if?isinstance(a,?str)?and?isinstance(b,?str):
          ????????return?a?+?'+'?+?b
          ????elif?isinstance(a,?list)?and?isinstance(b,?list):
          ????????return?a?+?b
          ????elif?isinstance(a,?(int,?float))?and?isinstance(b,?(int,?float)):
          ????????return?a?+?b
          ????else:
          ????????return?None

          在add函數(shù)中,分四種情況實(shí)現(xiàn)了加法,分別是字符串,列表,屬性值,以及其它情況。

          接著,我們用unittest模塊來進(jìn)行單元測(cè)試,代碼腳本(test_func_add.py)如下:

          import?unittest
          from?func_add?import?add


          class?Test_Add(unittest.TestCase):

          ????def?setUp(self):
          ????????pass

          ????def?test_add_case1(self):
          ????????a?=?"Hello"
          ????????b?=?"World"
          ????????res?=?add(a,?b)
          ????????print(res)
          ????????self.assertEqual(res,?"Hello+World")

          ????def?test_add_case2(self):
          ????????a?=?1
          ????????b?=?2
          ????????res?=?add(a,?b)
          ????????print(res)
          ????????self.assertEqual(res,?3)

          ????def?test_add_case3(self):
          ????????a?=?[1,?2]
          ????????b?=?[3]
          ????????res?=?add(a,?b)
          ????????print(res)
          ????????self.assertEqual(res,?[1,?2,?3])

          ????def?test_add_case4(self):
          ????????a?=?2
          ????????b?=?"3"
          ????????res?=?add(a,?b)
          ????????print(None)
          ????????self.assertEqual(res,?None)


          if?__name__?==?'__main__':

          ????#?部分用例測(cè)試
          ????#?構(gòu)造一個(gè)容器用來存放我們的測(cè)試用例
          ????suite?=?unittest.TestSuite()
          ????#?添加類中的測(cè)試用例
          ????suite.addTest(Test_Add('test_add_case1'))
          ????suite.addTest(Test_Add('test_add_case2'))
          ????#?suite.addTest(Test_Add('test_add_case3'))
          ????#?suite.addTest(Test_Add('test_add_case4'))
          ????run?=?unittest.TextTestRunner()
          ????run.run(suite)

          在這個(gè)測(cè)試中,我們只測(cè)試了前兩個(gè)用例,也就是對(duì)字符串和數(shù)值型的加法進(jìn)行測(cè)試。

          在命令行中輸入coverage run test_func_add.py命令運(yùn)行該測(cè)試腳本,輸出結(jié)果如下:

          Hello+World
          .3
          .
          ----------------------------------------------------------------------
          Ran?2?tests?in?0.000s

          OK

          再輸入命令coverage html就能生成代碼行覆蓋率的報(bào)告,會(huì)生成htmlcov文件夾,打開其中的index.html文件,就能看到本次執(zhí)行的覆蓋率情況,如下圖:

          測(cè)試覆蓋率結(jié)果總覽


          我們點(diǎn)擊func_add.py查看add函數(shù)測(cè)試的情況,如下圖:


          func_add.py腳本的測(cè)試覆蓋率情況


          可以看到,單元測(cè)試腳本test_func_add.py的前兩個(gè)測(cè)試用例只覆蓋到了add函數(shù)中左邊綠色的部分,而沒有測(cè)試到紅色的部分,代碼行覆蓋率為75%。


          此,還有兩種情況沒有覆蓋到,說明我們的單元測(cè)試中的測(cè)試用例還不夠充分。


          test_func_add.py中,我們把main函數(shù)中的注釋去掉,把后兩個(gè)測(cè)試用例也添加進(jìn)來,這時(shí)候我們?cè)龠\(yùn)行上面的coverage模塊的命令,重新生成htmlcov后,func_add.py的代碼行覆蓋率如下圖:

          增加測(cè)試用例后,func_add.py腳本的測(cè)試覆蓋率情況


          可以看到,增加測(cè)試用例后,我們調(diào)用的add函數(shù)代碼行覆蓋率為100%,所有的代碼都覆蓋到了。

          Mock

          Mock這個(gè)詞在英語中有模擬的這個(gè)意思,因此我們可以猜測(cè)出這個(gè)庫的主要功能是模擬一些東西。準(zhǔn)確的說,Mock是Python中一個(gè)用于支持單元測(cè)試的庫,它的主要功能是使用mock對(duì)象替代掉指定的Python對(duì)象,以達(dá)到模擬對(duì)象的行為。在Python3中,mock是輔助單元測(cè)試的一個(gè)模塊。它允許您用模擬對(duì)象替換您的系統(tǒng)的部分,并對(duì)它們已使用的方式進(jìn)行斷言。

          在實(shí)際生產(chǎn)中的項(xiàng)目是非常復(fù)雜的,對(duì)其進(jìn)行單元測(cè)試的時(shí)候,會(huì)遇到以下問題:

          • 接口的依賴

          • 外部接口調(diào)用

          • 測(cè)試環(huán)境非常復(fù)雜

          單元測(cè)試應(yīng)該只針對(duì)當(dāng)前單元進(jìn)行測(cè)試, 所有的內(nèi)部或外部的依賴應(yīng)該是穩(wěn)定的, 已經(jīng)在別處進(jìn)行測(cè)試過的。使用mock 就可以對(duì)外部依賴組件實(shí)現(xiàn)進(jìn)行模擬并且替換掉, 從而使得單元測(cè)試將焦點(diǎn)只放在當(dāng)前的單元功能。

          我們通過一個(gè)簡(jiǎn)單的例子來說明mock模塊的使用。

          首先,我們有腳本mock_multipy.py,主要實(shí)現(xiàn)的功能是Operator類中的multipy函數(shù),在這里我們可以假設(shè)該函數(shù)并沒有實(shí)現(xiàn)好,只是存在這樣一個(gè)函數(shù),代碼如下:

          #?-*-?coding:?utf-8?-*-
          #?mock_multipy.py

          class?Operator():

          ????def?multipy(self,?a,?b):
          ????????pass

          盡管我們沒有實(shí)現(xiàn)multipy函數(shù),但是我們還是想對(duì)這個(gè)函數(shù)的功能進(jìn)行測(cè)試,這時(shí)候我們可以借助mock模塊中的Mock類來實(shí)現(xiàn)。測(cè)試的腳本(mock_example.py)代碼如下:

          #?-*-?coding:?utf-8?-*-

          from?unittest?import?mock
          import?unittest

          from?mock_multipy?import?Operator

          #?test?Operator?class
          class?TestCount(unittest.TestCase):

          ????def?test_add(self):
          ????????op?=?Operator()
          ????????#?利用Mock類,我們假設(shè)返回的結(jié)果為15
          ????????op.multipy?=?mock.Mock(return_value=15)
          ????????#?調(diào)用multipy函數(shù),輸入?yún)?shù)為4,5,實(shí)際并未調(diào)用
          ????????result?=?op.multipy(4,?5)
          ????????#?聲明返回結(jié)果是否為15
          ????????self.assertEqual(result,?15)


          if?__name__?==?'__main__':
          ????unittest.main()

          讓我們對(duì)上述的代碼做一些說明。

          op.multipy?=?mock.Mock(return_value=15)

          通過Mock類來模擬調(diào)用Operator類中的multipy()函數(shù),return_value 定義了multipy()方法的返回值。

          result?=?op.multipy(4,?5)

          result值調(diào)用multipy()函數(shù),輸入?yún)?shù)為4,5,但實(shí)際并未調(diào)用,最后通過assertEqual()方法斷言,返回的結(jié)果是否是預(yù)期的結(jié)果為15。輸出的結(jié)果如下:

          Ran?1?test?in?0.002s

          OK

          通過Mock類,我們即使在multipy函數(shù)并未實(shí)現(xiàn)的情況下,仍然能夠通過想象函數(shù)執(zhí)行的結(jié)果來進(jìn)行測(cè)試,這樣如果有后續(xù)的函數(shù)依賴multipy函數(shù),也并不影響后續(xù)代碼的測(cè)試。

          利用Mock模塊中的patch函數(shù),我們可以將上述測(cè)試的腳本代碼簡(jiǎn)化如下:

          #?-*-?coding:?utf-8?-*-
          import?unittest

          from?unittest.mock?import?patch
          from?mock_multipy?import?Operator

          #?test?Operator?class
          class?TestCount(unittest.TestCase):

          ????@patch("mock_multipy.Operator.multipy")
          ????def?test_case1(self,?tmp):
          ????????tmp.return_value?=?15
          ????????result?=?Operator().multipy(4,?5)
          ????????self.assertEqual(15,?result)

          if?__name__?==?'__main__':
          ????unittest.main()

          patch()裝飾器可以很容易地模擬類或?qū)ο笤谀K測(cè)試。在測(cè)試過程中,您指定的對(duì)象將被替換為一個(gè)模擬(或其他對(duì)象),并在測(cè)試結(jié)束時(shí)還原。

          那如果我們后面又實(shí)現(xiàn)了multipy函數(shù),是否仍然能夠測(cè)試呢?

          修改mock_multipy.py腳本,代碼如下:

          #?-*-?coding:?utf-8?-*-
          #?mock_multipy.py

          class?Operator():

          ????def?multipy(self,?a,?b):
          ????????return?a?*?b

          這時(shí)候,我們?cè)龠\(yùn)行mock_example.py腳本,測(cè)試仍然通過,這是因?yàn)閙ultipy函數(shù)返回的結(jié)果仍然是我們mock后返回的值,而并未調(diào)用真正的Operator類中的multipy函數(shù)。

          我們修改mock_example.py腳本如下:

          #?-*-?coding:?utf-8?-*-

          from?unittest?import?mock
          import?unittest

          from?mock_multipy?import?Operator

          #?test?Operator?class
          class?TestCount(unittest.TestCase):

          ????def?test_add(self):
          ????????op?=?Operator()
          ????????#?利用Mock類,添加side_effect參數(shù)
          ????????op.multipy?=?mock.Mock(return_value=15,?side_effect=op.multipy)
          ????????#?調(diào)用multipy函數(shù),輸入?yún)?shù)為4,5,實(shí)際已調(diào)用
          ????????result?=?op.multipy(4,?5)
          ????????#?聲明返回結(jié)果是否為15
          ????????self.assertEqual(result,?15)


          if?__name__?==?'__main__':
          ????unittest.main()

          side_effect參數(shù)和return_value參數(shù)是相反的。它給mock分配了可替換的結(jié)果,覆蓋了return_value。簡(jiǎn)單的說,一個(gè)模擬工廠調(diào)用將返回side_effect值,而不是return_value。所以,設(shè)置side_effect參數(shù)為Operator類中的multipy函數(shù),那么return_value的作用失效。

          運(yùn)行修改后的測(cè)試腳本,測(cè)試結(jié)果如下:

          Ran?1?test?in?0.004s

          FAILED?(failures=1)


          15?!=?20

          Expected?:20
          Actual???:15

          可以發(fā)現(xiàn),multipy函數(shù)返回的值為20,不等于我們期望的值15,這是side_effect函數(shù)的作用結(jié)果使然,返回的結(jié)果調(diào)用了Operator類中的multipy函數(shù),所以返回值為20。

          self.assertEqual(result, 15)中將15改成20,運(yùn)行測(cè)試結(jié)果如下:

          Ran?1?test?in?0.002s

          OK

          本次分享到此結(jié)束,感謝大家的閱讀~

          作者:jclian,喜歡算法,熱愛分享,希望能結(jié)交更多志同道合的朋友,一起在學(xué)習(xí)Python的道路上走得更遠(yuǎn)!

          推薦閱讀



          5分鐘完全掌握PyPy


          用 PyPy 讓你的 Python 代碼運(yùn)行得更快!


          有人在代碼里下毒!慎用 pip install 命令

          點(diǎn)擊下方閱讀原文加入社區(qū)會(huì)員



          點(diǎn)贊鼓勵(lì)一下

          瀏覽 24
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲精品久久久久 | 人人鲁人人操 | 色色爽| 亚洲男女网站 | 日韩欧美性爱 |