5

如何在不更改任何一个导入模块中的代码的情况下模拟两个深度导入的 Python 类?假设我正在导入一个 Web 实用程序库,该库导入一个 HTTPClient() - 我如何编写一个单元测试,模拟 HTTPClient 以返回一个值,而不更改文件 web_utils.py?我想在 DataHandler 中使用数据操作(而不是模拟出来),但我不希望 HTTPClient 实际连接到网络。

这甚至可能吗?鉴于 Python 有猴子补丁,它当然看起来应该是。还是有替代/更好的方法?我仍在弄清楚模拟过程,更不用说改变进口了。

# someLib/web_utils.py
from abc.client import SomeHTTPClient # the class to replace

def get_client():
    tc = SomeHTTPClient(endpoint='url') # fails when I replace the class
    return tc

class DataHandler(object):
    def post_data(someURL, someData):
        newData = massage(someData)
        client = get_client()
        some_response = client.request(someURL, 'POST', newData)
        return some_response

# code/myCode.py
from someLib.web_utils import DataHandler

dh = DataHandler()
reply = dh.post_data(url, data)

# tests/myTests.py
from django.test.testcases import TestCase
from mock import Mock

class Mocking_Test(TestCase):
    def test_mock(self):
        from someLib import web_utils
        fakeClient = Mock()
        fakeClient.request = web_utils.SomeHTTPClient.request # just to see if it works
        web_utils.SomeHTTPClient = fakeClient
        dh = DataHandler()
        reply = dh.post_data(url='somewhere', data='stuff')

更新 - 添加了该get_client()功能。我认为@spicavigo 的答案在正确的轨道上——它似乎确实正在取代SomeHTTPClient课程。但由于某种原因,该类没有实例化一个对象(错误是,“必须是类型,而不是 Mock”)。我也看不出它是如何成为一个Mock()已创建的对象,而不是一个类。所以我不确定如何使这部分工作。

4

3 回答 3

4

你能把它添加到 myTest.py 并尝试

from someLib import web_utils
web_utils.SomeHTTPClient = <YOUR MOCK CLASS>
于 2012-09-06T13:30:50.353 回答
2

这是最终奏效的方法:

class Mocking_Test(TestCase):
    def test_mock(self):
        def return_response(a, b, c, *parms, **args):
            print "in request().return_response"
            class makeResponse(object):
                status = 200
                reason = "making stuff up"
            return makeResponse()

        fakeClient = Mock()
        fakeClient.return_value = return_response

        #with patch('abc.client.SomeHTTPClient') as MockClient: # not working.
        with patch('abc.client.SomeHTTPClient.request', new_callable=fakeClient):
            dh = DataHandler()
            reply = dh.post_data(url='somewhere', data='stuff')

我在原来的帖子中走在正确的轨道上patch,我只是没有正确地进行修补。另外,我不是替换整个类,而是仅替换类request上的函数(这是所有需要模拟的内容,以避免实际发出 HTTP 请求)。

于 2012-09-06T18:15:14.540 回答
-1

我知道这不是你想要的,但是在创建你的模拟类之后,我会简单地做这样的事情,以使代码更干净(在 bash shell 中,否则如果你有 1 个文件,你可以做一个 find 和代替)

find . -name "*.py" -type f -exec sed -i "s/<old_class>/<mock_class>/g" '{}' \;

这应该用新类替换旧类的所有实例,我个人认为它会使代码更简洁。如果您只想要一个文件,而不是所有 .py 文件,只需将 -name 更改为“file.py”

于 2012-09-06T14:18:07.190 回答