杀死一个模拟对象:一个Python故事

use*_*446 8 python unit-testing mocking

我在使用Python模拟时遇到了麻烦,并且一直在疯狂.由于担心因为没有足够的研究而投票失败,我已经暂时搁置了这个问题.我在上周累计24小时试图找出如何完成这项工作而不能.

我已经阅读了很多例子并从中创建了这个例子.我知道模拟对象应该易于使用,但这花了太长时间.现在我没时间了.

我想在这里做两件简单的事情:

1.在另一个函数中覆盖request.ok状态代码
2.导致抛出urllib2.HTTPError异常

为方便起见,我已将这两项任务提炼为最简单的示例:

#ExampleModule.py

import requests
import urllib2

def hello_world():    
    try:
        print "BEGIN TRY"
        r = requests.request('GET', "http://127.0.0.1:80")
        print r.ok

        if r.ok:
            print "PATCH 1 FAILED"
        else:
            print "PATCH 1 SUCCESSFUL"

    except urllib2.HTTPError:
        print "PATCH 2 SUCCESSFUL"
        print "EXCEPTION 2 HIT\n"
    else:
        print "PATCH 2 FAILED\n"
Run Code Online (Sandbox Code Playgroud)

#in TestModule.py

import mock
import ExampleModule     

def test_function_try():
    with mock.patch('ExampleModule.hello_world') as patched_request:
        patched_request.requests.request.ok = False
        result = ExampleModule.hello_world()
        print result

def test_function_exception():
    with mock.patch('ExampleModule.hello_world') as patched_exception:
        patched_exception.urllib2.side_effect = HTTPError
        result = ExampleModule.hello_world()
        print result

test_function_try()
test_function_exception()
Run Code Online (Sandbox Code Playgroud)

正常调用hello_world()输出:

BEGIN TRY
True
<Response [200]>
Run Code Online (Sandbox Code Playgroud)

对test_function_try()的正常调用输出:

<MagicMock name='hello_world()' id='70272816'>
#From the "print result" inside test_function_try()
Run Code Online (Sandbox Code Playgroud)

对test_function_exception()的正常调用输出:

<MagicMock name='hello_world()' id='62320016'>
#From the "print result" inside test_function_exception()
Run Code Online (Sandbox Code Playgroud)

显然,我实际上并没有从hello_world()返回任何内容,因此看起来修补对象是hello_world()函数,而不是请求或urllib2修补模块.

应该注意的是,当我尝试使用'ExampleModule.hello_world.requests'或'ExampleModule.hello_world.urllib2'进行修补时,我收到错误消息,说它们在hello_world()中找不到

问题摘要

  1. test_function_try()和test_function_exception()这两个函数有什么问题?需要修改什么,以便我可以在hello_world()内手动分配request.ok的值,并手动引发异常HTTPError,以便我可以测试该块中的代码...用于解释"何时"的加分点异常被抛出:一旦输入try:,或者在调用请求时,或者其他时间?
  2. 我一直关注的问题是:我在ExampleModule.py中的print语句会显示我的修补和模拟测试是否正常工作,或者我是否必须使用断言方法来获取真相?我不确定当人们提到"使用断言来查明实际修补的对象是否被调用等"时,断言是否是必需的.或者如果这是为了方便/惯例/实用性.

UPDATE

将补丁目标更改为requests.request()函数后,根据@Chepner的建议,我收到以下输出:

BEGIN TRY
False
PATCH 1 SUCCESSFUL
PATCH 2 FAILED

BEGIN TRY
Traceback (most recent call last):
File "C:\LOCAL\ECLIPSE PROJECTS\MockingTest\TestModule.py", line 44, in <module>
    test_function_exception()
File "C:\LOCAL\ECLIPSE PROJECTS\MockingTest\TestModule.py", line 19, in test_function_exception
    ExampleModule.hello_world()
File "C:\LOCAL\ECLIPSE PROJECTS\MockingTest\ExampleModule.py", line 12, in hello_world
    r = requests.request('GET', "http://127.0.0.1:8080")
File "C:\Python27\lib\site-packages\mock\mock.py", line 1062, in __call__
    return _mock_self._mock_call(*args, **kwargs)
File "C:\Python27\lib\site-packages\mock\mock.py", line 1118, in _mock_call
    raise effect
TypeError: __init__() takes exactly 6 arguments (1 given)
Run Code Online (Sandbox Code Playgroud)

che*_*ner 5

您不能在函数中模拟局部变量.你想要模拟的东西requests.request本身,所以当hello_world调用它时,返回一个模拟对象,而不是实际发出HTTP请求.

import mock
import urllib2
import ExampleModule     

def test_function_try():
    with mock.patch('ExampleModule.requests.request') as patched_request:
        patched_request.return_value.ok = False
        ExampleModule.hello_world()

def test_function_exception():
    with mock.patch('ExampleModule.requests.request') as patched_request:
        patched_request.side_effect = urllib2.HTTPError
        ExampleModule.hello_world()

test_function_try()
test_function_exception()
Run Code Online (Sandbox Code Playgroud)