aba*_*rik 0 python unit-testing mocking python-mock
所以,考虑我有一个简单的库,我正在尝试编写单元测试.该库与数据库通信,然后使用该数据调用SOAP API.我有三个模块,每个模块都有一个测试文件.
目录结构:
./mypkg
../__init__.py
../main.py
../db.py
../api.py
./tests
../test_main
../test_db
../test_api
Run Code Online (Sandbox Code Playgroud)
码:
#db.py
import mysqlclient
class Db(object):
def __init__(self):
self._client = mysqlclient.Client()
@property
def data(self):
return self._client.some_query()
#api.py
import soapclient
class Api(object):
def __init__(self):
self._client = soapclient.Client()
@property
def call(self):
return self._client.some_external_call()
#main.py
from db import Db
from api import Api
class MyLib(object):
def __init__(self):
self.db = Db()
self.api = Api()
def caller(self):
return self.api.call(self.db.data)
Run Code Online (Sandbox Code Playgroud)
单元测试:
#test_db.py
import mock
from mypkg.db import Db
@mock.patch('mypkg.db.mysqlclient')
def test_db(mysqlclient_mock):
mysqlclient_mock.Client.return_value.some_query = {'data':'data'}
db = Db()
assert db.data == {'data':'data'}
#test_api.py
import mock
from mypkg.api import Api
@mock.patch('mypkg.db.soapclient')
def test_db(soap_mock):
soap_mock.Client.return_value.some_external_call = 'foo'
api = Api()
assert api.call == 'foo'
Run Code Online (Sandbox Code Playgroud)
在上面的例子中,mypkg.main.MyLib调用mypkg.db.Db()(使用第三方mysqlclient)然后mypkg.api.Api()(使用第三方soapclient)
我使用mock.patch补丁的第三方库嘲笑我的DB和API调用test_db和test_api分开.
现在我的问题是,是否建议在test_mainOR中再次修补这些外部调用只是补丁db.Db和api.Api?(这个例子非常简单,但是在较大的库中,在再次修补外部调用或甚至使用修补内部库的测试助手函数时,代码变得很麻烦).
选项1:main再次修补外部库
#test_main.py
import mock
from mypkg.main import MyLib
@mock.patch('mypkg.db.mysqlclient')
@mock.patch('mypkg.api.soapclient')
def test_main(soap_mock, mysqlcient_mock):
ml = MyLib()
soap_mock.Client.return_value.some_external_call = 'foo'
assert ml.caller() == 'foo'
Run Code Online (Sandbox Code Playgroud)
选项2:修补内部库
#test_main.py
import mock
from mypkg.main import MyLib
@mock.patch('mypkg.db.Db')
@mock.patch('mypkg.api.Api')
def test_main(api_mock, db_mock):
ml = MyLib()
api_mock.return_value = 'foo'
assert ml.caller() == 'foo'
Run Code Online (Sandbox Code Playgroud)
mock.patch创建一个模拟版本的东西,它被导入,而不是它的存在.这意味着传递给的字符串mock.patch必须是被测模块中导入模块的路径.以下是修补程序装饰器的外观test_main.py:
@mock.patch('mypkg.main.Db')
@mock.patch('mypkg.main.Api')
Run Code Online (Sandbox Code Playgroud)
此外,修补模块(api_mock和db_mock)上的句柄引用类,而不是这些类的实例.当你写的时候api_mock.return_value = 'foo',你告诉api_mock在调用它时返回'foo',而不是当它的实例有一个调用它的方法时.以下是main.py以及它们如何涉及到的对象api_mock,并db_mock在您的测试:
Api is a class : api_mock
Api() is an instance : api_mock.return_value
Api().call is an instance method : api_mock.return_value.call
Api().call() is a return value : api_mock.return_value.call.return_value
Db is a class : db_mock
Db() is an instance : db_mock.return_value
Db().data is an attribute : db_mock.return_value.data
Run Code Online (Sandbox Code Playgroud)
因此test_main.py应如下所示:
import mock
from mypkg.main import MyLib
@mock.patch('mypkg.main.Db')
@mock.patch('mypkg.main.Api')
def test_main(api_mock, db_mock):
ml = MyLib()
api_mock.return_value.call.return_value = 'foo'
db_mock.return_value.data = 'some data' # we need this to test that the call to api_mock had the correct arguments.
assert ml.caller() == 'foo'
api_mock.return_value.call.assert_called_once_with('some data')
Run Code Online (Sandbox Code Playgroud)
选项1中的第一个补丁非常适合单元测试db.py,因为它为db模块提供了mysqlclient的模拟版本.同样,@mock.patch('mypkg.api.soapclient')属于test_api.py.
我想不出选项2可以帮助你进行单元测试的方法.
编辑:我错误地将类称为模块.db.py和api.py是模块
| 归档时间: |
|
| 查看次数: |
856 次 |
| 最近记录: |