mock服務從入門到實踐


1.如何在接口開發階段編寫測試腳本

利用fiddle的autoresponeder或以代碼(mock)實現模擬接口返回數據

2.mock服務介紹以及實現原理

mock測試就是在測試過程中,對於某些不容易構造或者不容易獲取的對象,用一個虛擬的對象來創建以便測試的測試方法。

對象

這個虛擬的對象就是mock對象。mock對象就是真實對象在調試期間的代替品。Mock對象是mock模塊中最重要的概念。Mock對象就是mock模塊中的一個類的實例,這個類的實例可以用來替換其他的Python對象,來達到模擬的效果。Mock類的定義如下:
class Mock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, **kwargs)

對象使用范疇

真實對象具有不可確定的行為,產生不可預測的效果,(如:股票行情,天氣預報)真實對象很難被創建的 真實對象的某些行為很難被觸發真實對象實際上還不存在的(和其他開發小組或者和新的硬件打交道)等等。

Mock對象的一般用法是這樣的:

  1. 找到你要替換的對象,這個對象可以是一個類,或者是一個函數,或者是一個類實例。

  2. 然后實例化Mock類得到一個mock對象,並且設置這個mock對象的行為,比如被調用的時候返回什么值,被訪問成員的時候返回什么值等。

  3. 使用這個mock對象替換掉我們想替換的對象,也就是步驟1中確定的對象。

  4. 之后就可以開始寫測試代碼,這個時候我們可以保證我們替換掉的對象在測試用例執行的過程中行為和我們預設的一樣。

舉個例子來說:我們有一個簡單的客戶端實現,用來訪問一個URL,當訪問正常時,需要返回狀態碼200,不正常時,需要返回狀態碼404。首先,我們的客戶端代碼實現如下:

# 文件名:client
import requests


def send_requestr(url):
    r = requests.get(url)
    return r.status_code

def visit_ustack():
    return send_requestr("http://www.ustack.com")

外部模塊調用visit_ustack()來訪問UnitedStack的官網。下面我們使用mock對象在單元測試中分別測試訪問正常和訪問不正常的情況。

import unittest
from unittest import mock
from . 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')

關鍵步驟

  1. 找到要替換的對象:我們需要測試的是visit_ustack這個函數,那么我們需要替換掉send_request這個函數。

  2. 實例化Mock類得到一個mock對象,並且設置這個mock對象的行為。在成功測試中,我們設置mock對象的返回值為字符串“200”,在失敗測試中,我們設置mock對象的返回值為字符串"404"。

  3. 使用這個mock對象替換掉我們想替換的對象。我們替換掉了client.send_request

  4. 寫測試代碼。我們調用client.visit_ustack(),並且期望它的返回值和我們預設的一樣。

上面這個就是使用mock對象的基本步驟了。在上面的例子中我們替換了自己寫的模塊的對象,其實也可以替換標准庫和第三方模塊的對象,方法是一樣的:先import進來,然后替換掉指定的對象就可以了。

使用一個接口來描述這個對象。在產品代碼中實現這個接口,在測試代碼中實現這個接口,在被測試代碼中只是通過接口來引用對象,所以它不知道這個引用的對象是真實對象,還是mock對象。

對象實例

一個鬧鍾根據時間來進行提醒服務,如果過了下午5點鍾就播放音頻文件提醒大家下班了,如果我們要利用真實的對象來測試的話就只能苦苦等到下午五點,然后把耳朵放在音箱旁,我們應該利用mock對象來進行測試,這樣我們就可以模擬控制時間了,而不用苦苦等待時鍾轉到下午5點鍾了。

mock的重要性

mock就是對於某些不容易構造或者不容易獲取的對象,用一個虛擬的對象來創建的方法。項目開發和測試過程中,遇到以下的情況時,就需要模擬結果返回。
1.當另一方接口或服務還未完成,阻礙項目進度時,可以通過mock的方式,實現並行開發。
2.另一方接口不穩定,而這邊需要一個穩定的結果才能繼續往下走流程時,也可以使用mock。有時候自動化測試需要一個持續穩定的環境,也可以對不是很重要的服務進行mock處理。
3.需要模擬異常情況,但是這種異常不容易觸發時,可以使用mock實現。

3.安裝mock

pip  install mock

4.在case中通過底層函數實現mock

import unittest
import json
from demo import RunMain   #導入方法
from mock import mock   #導入mock
import HTMLTestRunner
class TestMethod(unittest.TestCase):
    def setUp(self):
      self.run = RunMain()   #每次都實例化
    def test_01(self):
      url = 'http://10.1.30.118:3200/provider/user/user/login?loginName=15519560190&loginModel=0&password=12345678&loginType=01'
      data = {
    'loginName':'15519560190',
    'loginModel':'01',
    'password':'12345678',
    'loginModel':'0',
    'timeStamp':'1582616038764'
    }
      mock_data = mock.Mock(return_value=data) #把data作為mock模擬的數據
      self.run.run_main = mock_data                #把模擬的數據傳給run_main
      res = self.run.run_main(url,'POST',data)   #返回的res就是data
      print(res)
      print(type(res))           #返回的數據是字典類型,這時候就不能用之前的json.loads()方法把返回結果轉為字典了
      self.assertEqual(res['loginModel'],'0')
      print('第一個case')

運行結果

 

 5.重構封裝mock服務

# -*- coding: utf-8 -*-
#mock_demp.py

from mock import mock
#重構mock  封裝
def mock_test(mock_method,request_data,url,method,respone_data):    #參數就是mock要模擬的方法,請求數據,url,方法,返回數據
    mock_method = mock.Mock(return_value=respone_data)    #模擬的是一個方法,把返回數據作為mock數據
    res = mock_method(url,method,request_data)        #方法的參數,執行res
    print(res)
    return res
# -*- coding: utf-8 -*-
import unittest
import json
from demo import RunMain   #導入方法
from mock import mock
from mock_demo import mock_test  #導入mock_test
import HTMLTestRunner
class TestMethod(unittest.TestCase):
    def setUp(self):
      self.run = RunMain()   #每次都實例化
    def test_01(self):
      url = 'http://10.1.30.118:3200/provider/user/user/login?loginName=15519560010&loginModel=0&password=12345678&loginType=01'
      data = {
    'loginName':'15519560010',
    'loginModel':'01',
    'password':'12345678',
    'loginModel':'0',
    'timeStamp':'1582616038764'
    }
      #mock_data = mock.Mock(return_value=data)
      #self.run.run_main = mock_data
      #res = self.run.run_main(url,'POST',data)
      mock_test(self.run.run_main,data,url,'POST',data) #調用mock——test方法(模擬的方法,請求數據,url,方法,響應數據)
      #print(res)
      #print(type(res))
      #self.assertEqual(res['loginModel'],'0')
      print('第一個case')
      

 

執行結果

 

 

擴展

1、class Mock的參數

先來看看Mock這個類的參數,在上面看到的類定義中,我們知道它有好幾個參數,這里介紹最主要的幾個:

  • name: 這個是用來命名一個mock對象,只是起到標識作用,當你print一個mock對象的時候,可以看到它的name。
  • return_value: 這個我們剛才使用過了,這個字段可以指定一個值(或者對象),當mock對象被調用時,如果side_effect函數返回的是DEFAULT,則對mock對象的調用會返回return_value指定的值。
  • side_effect: 這個參數指向一個可調用對象,一般就是函數。當mock對象被調用時,如果該函數返回值不是DEFAULT時,那么以該函數的返回值作為mock對象調用的返回值

2、mock對象的自動創建

當訪問一個mock對象中不存在的屬性時,mock會自動建立一個子mock對象,並且把正在訪問的屬性指向它,這個功能對於實現多級屬性的mock很方便。

client = mock.Mock()
client.v2_client.get.return_value = '200'

這個時候,你就得到了一個mock過的client實例,調用該實例的v2_client.get()方法會得到的返回值是"200"。

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

3、對方法調用進行檢查

mock對象有一些方法可以用來檢查該對象是否被調用過、被調用時的參數如何、被調用了幾次等。實現這些功能可以調用mock對象的方法,具體的可以查看mock的文檔。這里我們舉個例子。

還是使用上面的代碼,這次我們要檢查visit_ustack()函數調用send_request()函數時,傳遞的參數類型是否正確。我們可以像下面這樣使用mock對象。

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對象的called屬性表示該mock對象是否被調用過。

Mock對象的call_args表示該mock對象被調用的tuple,tuple的每個成員都是一個mock.call對象。mock.call這個對象代表了一次對mock對象的調用,其內容是一個tuple,含有兩個元素,第一個元素是調用mock對象時的位置參數(*args),第二個元素是調用mock對象時的關鍵字參數(**kwargs)。

現在來分析下上面的用例,我們要檢查的項目有兩個:

  1. visit_ustack()調用了send_request()

  2. 調用的參數是一個字符串

4、patch和patch.object

在了解了mock對象之后,我們來看兩個方便測試的函數: patchpatch.object。這兩個函數都會返回一個mock內部的類實例,這個類是 class _patch。返回的這個類實例既可以作為函數的裝飾器,也可以作為類的裝飾器,也可以作為上下文管理器。使用 patch或者 patch.object的目的是為了控制mock的范圍,意思就是在一個函數范圍內,或者一個類的范圍內,或者 with語句的范圍內mock掉一個對象。我們看個代碼例子即可:
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)

這個測試類和我們剛才寫的第一個測試類一樣,包含兩個測試,只不過這次不是顯示創建一個mock對象並且進行替換,而是使用了patch函數(作為上下文管理器使用)。

patch.objectpatch的效果是一樣的,只不過用法有點不同。舉例來說,同樣是上面這個例子,換成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)

就是替換掉一個對象的指定名稱的屬性,用法和setattr類似。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM