原文:微服務(wù)下的契約測試—Python版 - 知乎 (zhihu.com)
契約測試--pact框架使用 - 簡書 (jianshu.com)
我們用實例更好的理解一下契約測試
- 1、有一個獲取用戶信息的接口 /user,參數(shù)name,調(diào)用地址:http://0.0.0.0:5000/user?name=zhoujielun,返回對應(yīng)的個人信息普通的接口測試,從客戶端出發(fā),通過設(shè)計不同的參數(shù)組合盡可能的覆蓋更多的邏輯分支,比如name=zhoujielun,name=123等等,為了說明契約測試,我們盡量簡單說明使用unittest單測框架來做接口測試,我們關(guān)注接口的返回代碼如 user_unittest.py所示
def test_lizeyang(self):
"""name=lizeyang的測試用例"""
expect = { "age": 21, "home": "china"}
res = requests.get("http://127.0.0.1:5000/user?name=lizeyang").json()
self.assertEqual(res, expect)
def test_zhoujielun(self):
"""name=zhoujielun的測試用例"""
expect = { "age": 21, "home": "america"}
res = requests.get("http://127.0.0.1:5000/user?name=zhoujielun").json()
self.assertEqual(res, expect)
self.assertEqual(res, expect)
運行python user_unittest.py,結(jié)果如下:
======================================================================
FAIL: test_lizeyang (__main__.UserTesting)
name=lizeyang的測試用例
----------------------------------------------------------------------
Traceback (most recent call last):
File "user_unittest.py", line 19, in test_lizeyang
self.assertEqual(res, expect)
AssertionError: {u'errmsg': u'user not exist.'} != {'home': 'china', 'age': 21}
- {u'errmsg': u'user not exist.'}
+ {'age': 21, 'home': 'china'}
----------------------------------------------------------------------
Ran 2 tests in 0.015s
FAILED (failures=1)
可以看到,name=lizeyang的測試用例是失敗的,預(yù)期結(jié)果和實際調(diào)用結(jié)果不相符
上面是一個簡單的接口測試用例,對于普通服務(wù)的接口測試來說,這種測試方法是可行的,沒有問題但是在微服務(wù)架構(gòu)下,會遇到一個問題:某服務(wù)X改了接口返回的數(shù)據(jù)或者結(jié)構(gòu),比如原先的返回code字段變更成了status或者某個home字段的值,按照上面的測試方法,我們獨立對X測試的時候,優(yōu)先考慮的是X服務(wù)的可用性和正確性,很難去實際度量X的上游調(diào)用方他的代碼在實際調(diào)用X接口的時候是否正常,服務(wù)他的預(yù)期,基于此,微服務(wù)的一個組件,他的上游組件數(shù)量是N多。
生產(chǎn)者按照自己的意愿去生產(chǎn)接口,導致這些接口無法滿足消費者的需求,從而引發(fā)各種開發(fā)、測試問題,顯然不是我們想要的結(jié)果。契約測試的出現(xiàn)就是為了解決這種需求與實際不對等的問題,本質(zhì)上其實是一個基于mock服務(wù)的接口測試
通俗的契約測試核心思想:對于任意兩個存在調(diào)用關(guān)系的服務(wù)組件A和B,A需要調(diào)用B的接口api_01,從契約的角度出發(fā),我們需要從A這個消費者的角度制造一個契約(或者叫合同吧),合同里面約定了接口、接口入?yún)⒁约霸谌雲(yún)⒌那疤峤涌趹?yīng)該給出什么樣的返回。然后B組件的接口api_01在開發(fā)的時候需要按照契約的要求去開發(fā),開發(fā)完成之后由B端人員發(fā)起契約測試或者CI集成測試
契約測試 = 測試左移 + mock + 接口測試
#!/usr/bin/python
# -*- encoding:utf-8 -*-
import atexit
import requests
import unittest
from pact import EachLike, SomethingLike, Term
from pact.consumer import Consumer
from pact.provider import Provider
# 定義一個pact,消費者是ModuleA,生產(chǎn)者是ModuleB,契約文件存放在pacts文件夾下
pact = Consumer('ModuleA').has_pact_with(Provider('ModuleB'), pact_dir='./pacts’)
# 啟動服務(wù)
pact.start_service()
atexit.register(pact.stop_service)
class UserTesting(unittest.TestCase):
def runTest(self):
self.test_lizeyang()
def test_lizeyang(self):
# 預(yù)期結(jié)果
expected = {"age": 222, "home": "china"}
# 契約的實際內(nèi)容
(pact
.given('test lizeyang this user.')
.upon_receiving('a request for the user `lizeyang`')
.with_request('get', '/user', query={"name": "lizeyang"})
.will_respond_with(200, body=expected))
# 調(diào)用pact自帶的mock服務(wù),注冊接口
with pact:
res = requests.get("http://localhost:1234/user?name=lizeyang").json()
self.assertEqual(res, expected)
if __name__ == "__main__":
ut = UserTesting()
ut.test_lizeyang()
運行python user_pact_test.py之后,
INFO WEBrick 1.3.1
INFO ruby 2.2.2 (2015-04-13) [x86_64-darwin13]
INFO WEBrick::HTTPServer#start: pid=63533 port=1234
{u'home': u'china', u'age': 222}
INFO going to shutdown ...
INFO WEBrick::HTTPServer#start done.
這時,在contracts/pacts文件夾下會自動生產(chǎn)一個契約文件modulea-moduleb.json,內(nèi)容如下。
{
"consumer": {
"name": "ModuleA"
},
"provider": {
"name": "ModuleB"
},
"interactions": [
{
"description": "a request for the user `lizeyang`”, # 描述
"providerState": "test lizeyang this user.”,
"request": {
"method": "get",
"path": "/user",
"query": "name=lizeyang"
},
"response": {
"status": 200,
"headers": {
},
"body": {
"home": "china",
"age": 222
}
}
}
],
"metadata": {
"pactSpecification": {
"version": "2.0.0"
}
}
}
這個契約文件定義了調(diào)用生產(chǎn)者ModuleB時候的測試用例和返回報文的格式 B端拿到這份契約之后,需要按照個契約的內(nèi)容完成接口開發(fā)以及自測 自測方法:
pact-verifier --provider-base-url=http://127.0.0.1:5000 --pact-url=./contracts/pacts/test_sender-service_001.json

可以看到自測接口與實際的返回是不一致的,開發(fā)需要按照契約修改自己的代碼來通過契約測試即可。