Python Mock入門(mén)

      網(wǎng)友投稿 854 2025-04-02

      今天我們介紹一下mock,Mock這個(gè)詞在英語(yǔ)中有模擬的這個(gè)意思,因此我們可以猜測(cè)出這個(gè)庫(kù)的主要功能是模擬一些東西。準(zhǔn)確的說(shuō),Mock是Python中一個(gè)用于支持的測(cè)試的庫(kù),它的主要功能是使用mock對(duì)象替代掉指定的Python對(duì)象,以達(dá)到模擬對(duì)象的行為。簡(jiǎn)單的說(shuō),mock庫(kù)用于如下的場(chǎng)景:

      假設(shè)你開(kāi)發(fā)的項(xiàng)目叫a,里面包含了一個(gè)模塊b,模塊b中的一個(gè)函數(shù)c(也就是a.b.c)在工作的時(shí)候需要調(diào)用發(fā)送請(qǐng)求給特定的服務(wù)器來(lái)得到一個(gè)JSON返回值,然后根據(jù)這個(gè)返回值來(lái)做處理。如果要為a.b.c函數(shù)寫(xiě)一個(gè)單元測(cè)試,該如何做?

      一個(gè)簡(jiǎn)單的辦法是搭建一個(gè)測(cè)試的服務(wù)器,在單元測(cè)試的時(shí)候,讓a.b.c函數(shù)和這個(gè)測(cè)試服務(wù)器交互。但是這種做法有兩個(gè)問(wèn)題:

      測(cè)試服務(wù)器可能很不好搭建,或者搭建效率很低。

      你搭建的測(cè)試服務(wù)器可能無(wú)法返回所有可能的值,或者需要大量的工作才能達(dá)到這個(gè)目的。

      那么如何在沒(méi)有測(cè)試服務(wù)器的情況下進(jìn)行上面這種情況的單元測(cè)試呢?Mock模塊就是答案。上面已經(jīng)說(shuō)過(guò)了,mock模塊可以替換Python對(duì)象。我們假設(shè)a.b.c的代碼如下:

      import requests

      def c(url):

      resp = requests.get(url)

      # further process with resp

      如果利用mock模塊,那么就可以達(dá)到這樣的效果:使用一個(gè)mock對(duì)象替換掉上面的requests.get函數(shù),然后執(zhí)行函數(shù)c時(shí),c調(diào)用requests.get的返回值就能夠由我們的mock對(duì)象來(lái)決定,而不需要服務(wù)器的參與。簡(jiǎn)單的說(shuō),就是我們用一個(gè)mock對(duì)象替換掉c函數(shù)和服務(wù)器交互的過(guò)程。你一定很好奇這個(gè)功能是如何實(shí)現(xiàn)的,這個(gè)是mock模塊內(nèi)部的實(shí)現(xiàn)機(jī)制,不在本文的討論范圍。本文主要討論如何用mock模塊來(lái)解決上面提到的這種單元測(cè)試場(chǎng)景。

      Mock的安裝和導(dǎo)入

      在Python 3.3以前的版本中,需要另外安裝mock模塊,可以使用pip命令來(lái)安裝:

      pip install mock

      然后在代碼中就可以直接import進(jìn)來(lái):

      import mock

      從Python 3.3開(kāi)始,mock模塊已經(jīng)被合并到標(biāo)準(zhǔn)庫(kù)中,被命名為unittest.mock,可以直接import進(jìn)來(lái)使用:

      from unittest import mock

      Mock對(duì)象

      基本用法

      Mock對(duì)象是mock模塊中最重要的概念。Mock對(duì)象就是mock模塊中的一個(gè)類(lèi)的實(shí)例,這個(gè)類(lèi)的實(shí)例可以用來(lái)替換其他的Python對(duì)象,來(lái)達(dá)到模擬的效果。Mock類(lèi)的定義如下:

      class Mock(spec=None,

      side_effect=None,

      return_value=DEFAULT,

      wraps=None, name=None,

      spec_set=None, **kwargs)

      這里給出這個(gè)定義只是要說(shuō)明下Mock對(duì)象其實(shí)就是個(gè)Python類(lèi)而已,當(dāng)然,它內(nèi)部的實(shí)現(xiàn)是很巧妙的,有興趣的可以去看mock模塊的代碼。

      Mock對(duì)象的一般用法是這樣的:

      找到你要替換的對(duì)象,這個(gè)對(duì)象可以是一個(gè)類(lèi),或者是一個(gè)函數(shù),或者是一個(gè)類(lèi)實(shí)例。

      然后實(shí)例化Mock類(lèi)得到一個(gè)mock對(duì)象,并且設(shè)置這個(gè)mock對(duì)象的行為,比如被調(diào)用的時(shí)候返回什么值,被訪問(wèn)成員的時(shí)候返回什么值等。

      使用這個(gè)mock對(duì)象替換掉我們想替換的對(duì)象,也就是步驟1中確定的對(duì)象。

      之后就可以開(kāi)始寫(xiě)測(cè)試代碼,這個(gè)時(shí)候我們可以保證我們替換掉的對(duì)象在測(cè)試用例執(zhí)行的過(guò)程中行為和我們預(yù)設(shè)的一樣。

      舉個(gè)例子來(lái)說(shuō):我們有一個(gè)簡(jiǎn)單的客戶(hù)端實(shí)現(xiàn),用來(lái)訪問(wèn)一個(gè)URL,當(dāng)訪問(wèn)正常時(shí),需要返回狀態(tài)碼200,不正常時(shí),需要返回狀態(tài)碼404。首先,我們的客戶(hù)端代碼實(shí)現(xiàn)如下:

      #!/usr/bin/env python

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

      import requests

      def send_request(url):

      r = requests.get(url)

      return r.status_code

      def visit_ustack():

      return send_request('http://www.ustack.com')

      外部模塊調(diào)用visit_ustack()來(lái)訪問(wèn)UnitedStack的官網(wǎng)。下面我們使用mock對(duì)象在單元測(cè)試中分別測(cè)試訪問(wèn)正常和訪問(wèn)不正常的情況。

      Python Mock入門(mén)

      #!/usr/bin/env python

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

      import unittest

      import mock

      import client

      class TestClient(unittest.TestCase):

      def test_success_request(self):

      success_send = mock.Mock(return_value='200')

      client.send_request = success_send

      self.assertEqual(client.visit_ustack(), '200')

      def test_fail_request(self):

      fail_send = mock.Mock(return_value='404')

      client.send_request = fail_send

      self.assertEqual(client.visit_ustack(), '404')

      找到要替換的對(duì)象:我們需要測(cè)試的是visit_ustack這個(gè)函數(shù),那么我們需要替換掉send_request這個(gè)函數(shù)。

      實(shí)例化Mock類(lèi)得到一個(gè)mock對(duì)象,并且設(shè)置這個(gè)mock對(duì)象的行為。在成功測(cè)試中,我們?cè)O(shè)置mock對(duì)象的返回值為字符串“200”,在失敗測(cè)試中,我們?cè)O(shè)置mock對(duì)象的返回值為字符串"404"。

      使用這個(gè)mock對(duì)象替換掉我們想替換的對(duì)象。我們替換掉了client.send_request

      寫(xiě)測(cè)試代碼。我們調(diào)用client.visit_ustack(),并且期望它的返回值和我們預(yù)設(shè)的一樣。

      上面這個(gè)就是使用mock對(duì)象的基本步驟了。在上面的例子中我們替換了自己寫(xiě)的模塊的對(duì)象,其實(shí)也可以替換標(biāo)準(zhǔn)庫(kù)和第三方模塊的對(duì)象,方法是一樣的:先import進(jìn)來(lái),然后替換掉指定的對(duì)象就可以了。

      稍微高級(jí)點(diǎn)的用法

      class Mock的參數(shù)

      上面講的是mock對(duì)象最基本的用法。下面來(lái)看看mock對(duì)象的稍微高級(jí)點(diǎn)的用法(并不是很高級(jí)啊,最完整最高級(jí)的直接去看mock的文檔即可,后面給出)。

      先來(lái)看看Mock這個(gè)類(lèi)的參數(shù),在上面看到的類(lèi)定義中,我們知道它有好幾個(gè)參數(shù),這里介紹最主要的幾個(gè):

      name: 這個(gè)是用來(lái)命名一個(gè)mock對(duì)象,只是起到標(biāo)識(shí)作用,當(dāng)你print一個(gè)mock對(duì)象的時(shí)候,可以看到它的name。

      return_value: 這個(gè)我們剛才使用過(guò)了,這個(gè)字段可以指定一個(gè)值(或者對(duì)象),當(dāng)mock對(duì)象被調(diào)用時(shí),如果side_effect函數(shù)返回的是DEFAULT,則對(duì)mock對(duì)象的調(diào)用會(huì)返回return_value指定的值。

      side_effect: 這個(gè)參數(shù)指向一個(gè)可調(diào)用對(duì)象,一般就是函數(shù)。當(dāng)mock對(duì)象被調(diào)用時(shí),如果該函數(shù)返回值不是DEFAULT時(shí),那么以該函數(shù)的返回值作為mock對(duì)象調(diào)用的返回值。

      其他的參數(shù)請(qǐng)參考官方文檔。

      mock對(duì)象的自動(dòng)創(chuàng)建

      當(dāng)訪問(wèn)一個(gè)mock對(duì)象中不存在的屬性時(shí),mock會(huì)自動(dòng)建立一個(gè)子mock對(duì)象,并且把正在訪問(wèn)的屬性指向它,這個(gè)功能對(duì)于實(shí)現(xiàn)多級(jí)屬性的mock很方便。

      client = Client()

      client.v2_client.get.return_value = '200'

      這個(gè)時(shí)候,你就得到了一個(gè)mock過(guò)的client實(shí)例,調(diào)用該實(shí)例的v2_client.get()方法會(huì)得到的返回值是"200"。

      從上面的例子中還可以看到,指定mock對(duì)象的return_value還可以使用屬性賦值的方法。

      對(duì)方法調(diào)用進(jìn)行檢查

      mock對(duì)象有一些方法可以用來(lái)檢查該對(duì)象是否被調(diào)用過(guò)、被調(diào)用時(shí)的參數(shù)如何、被調(diào)用了幾次等。實(shí)現(xiàn)這些功能可以調(diào)用mock對(duì)象的方法,具體的可以查看mock的文檔。這里我們舉個(gè)例子。

      還是使用上面的代碼,這次我們要檢查visit_ustack()函數(shù)調(diào)用send_request()函數(shù)時(shí),傳遞的參數(shù)類(lèi)型是否正確。我們可以像下面這樣使用mock對(duì)象。

      class TestClient(unittest.TestCase):

      def test_call_send_request_with_right_arguments(self):

      client.send_request = mock.Mock()

      client.visit_ustack()

      self.assertEqual(client.send_request.called, True)

      call_args = client.send_request.call_args

      self.assertIsInstance(call_args[0][0], str)

      Mock對(duì)象的called屬性表示該moc對(duì)象是否被調(diào)用過(guò)。

      Mock對(duì)象的call_args表示該mock對(duì)象被調(diào)用的tuple,tuple的每個(gè)成員都是一個(gè)mock.call對(duì)象。mock.call這個(gè)對(duì)象代表了一次對(duì)mock對(duì)象的調(diào)用,其內(nèi)容是一個(gè)tuple,含有兩個(gè)元素,第一個(gè)元素是調(diào)用mock對(duì)象時(shí)的位置參數(shù)(*args),第二個(gè)元素是調(diào)用mock對(duì)象時(shí)的關(guān)鍵字參數(shù)(**kwargs)。

      現(xiàn)在來(lái)分析下上面的用例,我們要檢查的項(xiàng)目有兩個(gè):

      visit_ustack()調(diào)用了send_request()

      調(diào)用的參數(shù)是一個(gè)字符串

      patch和patch.object

      在了解了mock對(duì)象之后,我們來(lái)看兩個(gè)方便測(cè)試的函數(shù):patch和patch.object。這兩個(gè)函數(shù)都會(huì)返回一個(gè)mock內(nèi)部的類(lèi)實(shí)例,這個(gè)類(lèi)是class _patch。返回的這個(gè)類(lèi)實(shí)例既可以作為函數(shù)的裝飾器,也可以作為類(lèi)的裝飾器,也可以作為上下文管理器。使用patch或者patch.object的目的是為了控制mock的范圍,意思就是在一個(gè)函數(shù)范圍內(nèi),或者一個(gè)類(lèi)的范圍內(nèi),或者with語(yǔ)句的范圍內(nèi)mock掉一個(gè)對(duì)象。我們看個(gè)代碼例子即可:

      class TestClient(unittest.TestCase):

      def test_success_request(self):

      status_code = '200'

      success_send = mock.Mock(return_value=status_code)

      with mock.patch('client.send_request', success_send):

      from client import visit_ustack

      self.assertEqual(visit_ustack(), status_code)

      def test_fail_request(self):

      status_code = '404'

      fail_send = mock.Mock(return_value=status_code)

      with mock.patch('client.send_request', fail_send):

      from client import visit_ustack

      self.assertEqual(visit_ustack(), status_code)

      這個(gè)測(cè)試類(lèi)和我們剛才寫(xiě)的第一個(gè)測(cè)試類(lèi)一樣,包含兩個(gè)測(cè)試,只不過(guò)這次不是顯示創(chuàng)建一個(gè)mock對(duì)象并且進(jìn)行替換,而是使用了patch函數(shù)(作為上下文管理器使用)。

      patch.object和patch的效果是一樣的,只不過(guò)用法有點(diǎn)不同。舉例來(lái)說(shuō),同樣是上面這個(gè)例子,換成patch.object的話是這樣的:

      def test_fail_request(self):

      status_code = '404'

      fail_send = mock.Mock(return_value=status_code)

      with mock.patch.object(client, 'send_request', fail_send):

      from client import visit_ustack

      self.assertEqual(visit_ustack(), status_code)

      就是替換掉一個(gè)對(duì)象的指定名稱(chēng)的屬性,用法和setattr類(lèi)似。

      如何學(xué)習(xí)使用mock?

      你肯定很奇怪,本文不就是教人使用mock的么?其實(shí)不是的,我發(fā)現(xiàn)自己在學(xué)習(xí)mock的過(guò)程中遇到的主要困難是不清楚mock能做什么,而不是mock對(duì)象到底有哪些函數(shù)。因此寫(xiě)這篇文章的主要目的是為了說(shuō)明mock能做什么。

      當(dāng)你知道了mock能做什么之后,要如何學(xué)習(xí)并掌握mock呢?最好的方式就是查看閱讀官方文檔,并在自己的單元測(cè)試中使用。

      最后,學(xué)習(xí)mock技能你應(yīng)該要能夠感受到一種控制的快感,就是你能享受控制外部服務(wù)的快樂(lè)。當(dāng)你感受到這種快感的時(shí)候,你的mock應(yīng)該就達(dá)到熟練使用的水平了。

      官方文檔

      Python 2.7

      mock還未加入標(biāo)準(zhǔn)庫(kù)。

      http://www.voidspace.org.uk/python/mock/index.html

      Python 3.4

      mock已經(jīng)加入了標(biāo)準(zhǔn)庫(kù)。

      https://docs.python.org/3.4/library/unittest.mock-examples.html

      https://docs.python.org/3.4/library/unittest.mock.html

      ‘掃一掃’

      Python 單元測(cè)試

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶(hù)投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶(hù)投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:excel中的滾動(dòng)條不見(jiàn)了怎么辦(excel上下滾動(dòng)條不見(jiàn)了)
      下一篇:電腦 與手機(jī) 查看的同意文件 顯示的數(shù)據(jù)不一樣(電腦設(shè)置密碼怎么設(shè)置)
      相關(guān)文章
      国产亚洲婷婷香蕉久久精品 | 亚洲区视频在线观看| 亚洲五月综合缴情在线观看| 亚洲欧洲精品成人久久曰影片| 老司机亚洲精品影院在线观看| 亚洲日韩中文字幕无码一区| 亚洲中文字幕无码久久| 亚洲日本VA午夜在线影院| 亚洲欧美日韩综合久久久久| 亚洲成a∨人片在无码2023| 亚洲AV无码专区在线观看成人| 色偷偷噜噜噜亚洲男人| 五月天婷亚洲天综合网精品偷| 国产成人亚洲精品播放器下载| 亚洲AⅤ无码一区二区三区在线 | 亚洲精品在线免费观看| 久久久久久亚洲精品成人| 亚洲激情在线视频| 亚洲美女视频一区二区三区| 亚洲欧洲日韩在线电影| 亚洲va精品中文字幕| 亚洲午夜精品久久久久久app| 亚洲精品无码av中文字幕| 久久久久久亚洲av无码蜜芽| 激情无码亚洲一区二区三区| 亚洲国产av无码精品| 国产亚洲精品看片在线观看| 亚洲国产一成人久久精品| 亚洲国产精品久久66| 亚洲日本国产乱码va在线观看| 亚洲六月丁香六月婷婷蜜芽| 亚洲一卡2卡三卡4卡无卡下载| 国产精品亚洲av色欲三区| 亚洲精品成a人在线观看| 亚洲综合熟女久久久30p| 亚洲AV日韩AV永久无码免下载| 亚洲国产视频网站| 亚洲色大情网站www| 亚洲精品国自产拍在线观看| 亚洲精品成人无码中文毛片不卡| 亚洲va在线va天堂va不卡下载|