【问题标题】:Mocking an entire class模拟整个班级
【发布时间】:2015-11-11 21:39:28
【问题描述】:

长话短说,当它只是被模拟对象替换的那个方法时,我完全能够模拟类方法,但是当我试图用模拟替换整个类时我无法模拟那个方法对象

@mock.patch.object 成功地模拟了 scan 方法,但 @mock.patch 没有这样做。我已经按照示例 https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch 但显然我做错了什么。

在这两种情况下,我都在同一命名空间中模拟词典模块(它是由 import lexiconsentence_parser 中导入的),但 mock_lexicon is lexicon.lexicon 检查失败

#!python
import sys;
sys.path.append('D:\python\lexicon');
import lexicon;

import sentence_parser;
import unittest2 as unittest;
import mock;

class ParserTestCases(unittest.TestCase) :

    def setUp(self) :
        self.Parser = sentence_parser.Parser();

    @mock.patch('lexicon.lexicon')
    def test_categorizedWordsAreAssigned_v1(self, mock_lexicon) :

        print "mock is lexicon:";
        print mock_lexicon is lexicon.lexicon + "\n";

        instance = mock_lexicon.return_value;
        instance.scan.return_value = "anything";    

        self.Parser.categorize_words_in_sentence("sentence");
        instance.scan.assert_called_once_with("sentence");

    @mock.patch.object(lexicon.lexicon, 'scan')
    def test_categorizedWordsAreAssigned_v2(self, mock_scan) :

        mock_scan.return_value = "anything";    

        self.Parser.categorize_words_in_sentence("sentence");
        mock_scan.assert_called_once_with("sentence");

if (__name__ == '__main__') :
    unittest.main()

输出:

mock is lexicon:
False

======================================================================
FAIL: test_categorizedWordsAreAssigned_v1 (__main__.ParserTestCases)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:\python\get_img\getImage_env\lib\site-packages\mock\mock.py", line 1305, in patched
    return func(*args, **keywargs)
  File "./test_sentence_parser.py", line 26, in test_categorizedWordsAreAssigned_v1
    instance.scan.assert_called_once_with("sentence");
  File "D:\python\get_img\getImage_env\lib\site-packages\mock\mock.py", line 947, in assert_called_once_with
    raise AssertionError(msg)
AssertionError: Expected 'scan' to be called once. Called 0 times.

----------------------------------------------------------------------
Ran 2 tests in 0.009s

FAILED (failures=1)

编辑:

为了澄清,Parser 定义如下

#!python

import sys;
sys.path.append('D:\python\lexicon');
import lexicon;

class Parser(object) :

    my_lexicon = lexicon.lexicon()

    def __init__(self) :
        self.categorized_words = ['test'];

    def categorize_words_in_sentence(self, sentence) :
        self.categorized_words = self.my_lexicon.scan(sentence);


if (__name__ == '__main__') :
    instance = Parser();
    instance.categorize_words_in_sentence("bear");
    print instance.categorized_words;

【问题讨论】:

  • 三个问题: 1) 我在github.com/bitprophet/lexicon/tree/master/lexicon 上查看了lexicon 模块,我发现课程是Lexicon 而不是lexicon; 2) 我的猜测是你有另一个lexicon 模块,而不仅仅是D:\python\lexicon 中的那个; 3)为什么在行尾需要;
  • 1) lexicon 是我自己的模块,恰好与您链接的模块同名; 2)我在D:\python\lexicon 中只有两个文件,一个是lexicon.py,第二个是test_lexicon.py,包含单元测试; 3) ; 只是我在其他语言中习惯的东西,但这在这里并不重要

标签: python python-2.7 unit-testing mocking


【解决方案1】:

这里真正相关的是categorize_words_in_sentenceParser 的方法如何使用lexicon。但首先我们应该消除噪音:

print mock_lexicon is lexicon.lexicon + "\n"

是什么会导致我们走向错误的方向:尝试将其替换为

self.assertIs(mock_lexicon, lexicon.lexicon)

你会明白你正在打印False,因为mock_lexicon不是lexicon.lexicon + "\n",而只是lexicon.lexicon

现在我不能告诉你为什么第一个测试不起作用,因为答案在 categorize_words_in_sentence 方法中或更可能在 sentence_parser 模块中,我猜你可以有类似的东西

from lexicon import lexicon

在这两种情况下,请查看 Where to Patch 文档,该文档可以启发您了解可能的原因以及您真正需要修补的情况。

第二个版本之所以有效,是因为您正在修补对象而不是引用(应该不同)。

最后更简洁通用的版本可以是:

@mock.patch('lexicon.lexicon.scan', return_value="anything")
def test_categorizedWordsAreAssigned_v3(self, mock_scan) :
    self.Parser.categorize_words_in_sentence("sentence")
    mock_scan.assert_called_once_with("sentence")

还有一件事:删除 unittest2 至少您没有使用 python 2.4 并且您对向后移植的单元测试功能感兴趣。

[编辑]

现在我可以停下来猜测并指出为什么第一个版本不起作用并且永远不会起作用:

class Parser(object) :
    my_lexicon = lexicon.lexicon()

Parser.my_lexicon 属性在加载时间进行评估。这意味着当您导入sentence_parser 时,会创建一个lexicon 并关联到Parser.my_lexicon 的引用。当您修补 lexicon.lexicon 时,您保持此引用不变,并且您的解析器对象仍使用导入时创建的原始引用。

你可以做的是修补Parser类中的引用

@patch("sentence_parser.Parser.my_lexicon")

如果你想给你的模拟同样的lexicon的签名,你可以使用create_autospect

@patch("sentence_parser.Parser.my_lexicon", create_autospec("lexicon.lexicon", instance=True))

【讨论】:

  • 我在原帖中已经说过,词典是由import lexicon; 导入的。我已将完整的 Parser 代码添加到原始帖子中。我看过哪里补丁。我知道它只能模拟 scan 方法,但它让我很恼火的是,当没有理由不模拟整个类时它不起作用
  • 已更新...Parser 的课程代码在这里真正相关。
  • 谢谢!它终于奏效了。我需要阅读 python 中的参考资料,我希望在 Where to Patch 文档中会提到这样的麻烦。
  • 我不认为 Where to Patch 可以涵盖这些情况:这就是 Python 的工作方式,您应该尝试了解导入时会发生什么,并了解什么是执行什么不执行。我个人避免在模块/类的静态部分执行任何类型的函数/创建。
猜你喜欢
  • 2019-05-02
  • 1970-01-01
  • 1970-01-01
  • 2012-05-28
  • 1970-01-01
  • 2020-07-17
  • 1970-01-01
  • 1970-01-01
  • 2015-08-29
相关资源
最近更新 更多