如何修补一个对象,以便除了一个方法之外的所有方法都被模拟?

时间:2016-03-03 18:44:50

标签: python python-unittest python-mock

我有一个入口点函数调用它main对象我想保持不变形,因为它在对象上调用了其他几个方法:

class Thing(object):

    def main(self):
        self.alpha()
        self.bravo()

    def alpha(self):
        self.charlie()

    def bravo(self):
        raise TypeError("Requires Internet connection!")

    def charlie(self):
        raise Exception("Bad stuff happens here!")

手动模拟非常简单:

thing = Thing() 
thing.alpha = MagicMock()
thing.bravo = MagicMock()

我可以测试以确保alpha和bravo都被调用一次,我可以在alpha和bravo中设置副作用以确保它们被处理等等。

我担心的是,如果代码定义发生变化,并且有人向charlie添加了main次调用。它没有被嘲笑,所以现在会感觉到副作用(而且它们就像写入文件,连接到数据库,从Internet上获取东西,因此这个简单的异常不会提醒我现在的测试坏)。

我的计划是验证我的模拟对象不会调用其他方法而不是我应该说的方法(或引发测试异常)。但是,如果我这样做:

MockThing = create_autospec(Thing)
thing = Thing()
thing.main() 

print thing.method_calls
# [calls.main()] 

然后main也被模拟,所以它不会调用其他方法。我如何模拟每个方法但主要的方法? (我希望method_calls为[calls.alpha(), calls.bravo()])。

编辑:黑客答案

嗯,我有一个非常hacky的解决方案,但我希望有一个更好的答案。基本上我从原始类(Python bind an unbound method

重新绑定方法
MockThing = create_autospec(Thing)
thing = MockThing()
thing.main = Thing.ingest.__get__(thing, Thing)
thing.main()

print thing.method_calls
# [calls.alpha(), calls.bravo()]

但是必须有一个比使用函数描述符更简单的解决方案!

4 个答案:

答案 0 :(得分:6)

当我做这些奇怪的事情时,比如调用一个类的实际方法,我会模拟我用来调用方法的静态引用:

    public static void WritePGM(string fileName, double[,] bitmap)
    {
        var width = bitmap.GetLength(0);
        var height = bitmap.GetLength(1);
        var header = "P5\n" + width + " " + height + "\n255\n";
        var writer = new BinaryWriter(new FileStream(fileName, FileMode.Create));
        writer.Write(System.Text.Encoding.ASCII.GetBytes(header));
        for (var j = 0; j < height; j++)
        {
            for (var i = width-1; i >= 0; i--)
            {
                var c = (byte)(System.Math.Max(System.Math.Min(1.0, bitmap[i, j]), 0.0) * 255.0);
                writer.Write(c);
            }
        }
        writer.Close();
    }

无论如何,在编写测试之后,最好通过使用一些协作者将您应该模拟的内容与要测试的内容分开:使用这些测试来引导生产代码重构并最终重构您的测试以消除这些类型的脏测试

答案 1 :(得分:2)

我遇到了同样的问题,但我找到了一种方法,我很满意。以下示例使用上面列出的Thing类:

import mock

mock_thing = mock.create_autospec(Thing)
mock_thing.main = lambda x: Thing.main(mock_thing, x)

这将导致mock_thing调用一个实际的&#39; main&#39;函数属于mock_thing,但mock_thing.alpha()和mock_thing.beta()都将被称为模拟! (将x替换为您传入函数的任何参数。)

希望这适合你!

答案 2 :(得分:0)

假设您有一个 Foo 类,带有一个 frobnicate 实例方法。

您可以使用 functools.partial 创建 Foo.frobnicate 函数的部分版本,并将您的模拟对象绑定到它的第一个参数。 这种方式允许您忽略参数的数量,并支持关键字参数(而 lambda 需要您知道并显式传递参数,并且不支持 kwargs)。

使用这种方法,最好定义一个创建模拟的 make_mock_foo 函数,以便通过 functools.partial 的绑定保存在一个地方。

import functools
from unittest import mock


class Foo:
    def frobnicate(self) -> None:
        pass


def make_mock_foo() -> Foo:
    mock_foo = mock.MagicMock(spec=Foo)
    mock_foo.frobnicate = functools.partial(Foo.frobnicate, mock_foo)
    return mock_foo

答案 3 :(得分:-1)

看起来你要么让单元测试进入你的集成/功能测试,要么你担心测试除了某个单元之外的其他东西。

如果您是单元测试Thing,那么您应该嘲笑您未测试的部分。但是,如果您正在测试Thing与其他内容(例如数据库)的集成方式,那么您应该测试实际的Thing,而不是模拟的。{/ p>

此外,这是依赖注入很有意义的地方,因为你会做这样的事情:

class Thing:
    def __init__(self, db, dangerous_thing):
        self.db = db
        self.dangerous_thing = dangerous_thing

    #....

    def charlie(self):
        foxtrot = self.dangerous_thing.do_it()

现在,您可以在测试dangerous_thing时传递Thing的模拟内容,这样您就不必担心真的正在执行{{1} }}