如何模拟作为库一部分的超类? [英] How do a mock a superclass that is part of a library?

查看:47
本文介绍了如何模拟作为库一部分的超类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 Python 中有一些类似如下的代码:

I have some code like the following in Python:

library.py:

class HasSideEffects(object):

    def __init__(self, kittens):
        print 'kittens cry'  # <- side effect

    def launch_nukes(self):
        print 'launching nukes'  # <- side effect

my_code.py:

from library import HasSideEffects


class UtilitySubclass(HasSideEffects):

    def party(self):
        self.launch_nukes()
        return 'confetti'  # I want to test that my confetti cannon will work


if __name__ == '__main__':
    x = UtilitySubclass(1)  # oh no! crying kittens :(
    x.party()  # oh no! nukes!

我想进行单元测试,当我调用 party 时,我会得到 confetti.但是,我想避免作为我子类化的库类的一部分的所有副作用.更具体地说,我想在单元测试中避免这些副作用,但在生产代码中需要这些副作用.

I want to unit test that when I call party I will get confetti. However, I want to avoid all the side effects that are part of the library class I am subclassing. More specifically, I want to avoid these side effects in the unit tests, but the side effects are needed in the production code.

我无法更改 library.py 中的代码,因为它是一个库,而且我没有编写代码.显然,我不想为了方便单元测试而维护一个库的分叉版本.

I cannot change the code in library.py because it's a library, and I did not write the code. Obviously I wouldn't want to maintained a forked version of a library just to facilitate a unit test.

我一直在尝试模拟 HasSideEffects 超类.我需要在超类中模拟 __init__launch_nukes 以便它们不再产生副作用.我在模拟 __init__ 方法时遇到了一些麻烦,显然 Python mock 库不支持它?

I've been trying to mock the HasSideEffects superclass. I need to mock __init__ and launch_nukes in the superclass so they no longer perform side effects. I've been having some trouble mocking the __init__ method, apparently the Python mock library does not support it?

在避免副作用的同时,测试 party 返回 confetti 的最佳方法是什么?

What's the best way to test that party return confetti, while avoiding side effects?

推荐答案

也许你已经很接近解决方案了:

Maybe you was quite close to a solution:

我一直在尝试模拟 HasSideEffects 超类.我需要在超类中模拟 init 和 launch_nukes,以便它们不再产生副作用.我在模拟 init 方法时遇到了一些麻烦,显然 Python 模拟库不支持它?

I've been trying to mock the HasSideEffects superclass. I need to mock init and launch_nukes in the superclass so they no longer perform side effects. I've been having some trouble mocking the init method, apparently the Python mock library does not support it?

unittest.mock 和旧的 mock 正是为了完成这种工作而设计的.当您需要从库或资源中删除依赖项时,模拟它并替换为 patch 非常强大,尤其是当您应该使用无法更改的遗留代码时.

unittest.mock and legacy mock are designed exactly to do this kind of work. When you need to remove dependencies from library or resources mock it and replace by patch is powerful especially when you should play with legacy code that you cannot change.

在您的情况下,您需要修补任一 __init__ 方法:要做到这一点,您必须考虑两件事

In your case you need to patch either __init__ method: to do it you must take in account two things

  1. 如果类是子类并且在构建子类对象时需要修补它,则必须直接修补 __init__ 而不是类定义.
  2. __init__ 方法必须返回 None 和你的模拟.
  1. If the class is subclassed and you need patch it when build a subclass object you must patch __init__ directly instead of class definition.
  2. __init__ method MUST return None and your mock too.

现在回到您的简单示例:我对其进行了一些更改以使测试更加明确.

Now come back to your simple example: I changed it a little bit to make tests more explicit.

library.py

class HasSideEffects(object):

    def __init__(self, kittens):
        raise Exception('kittens cry')  # <- side effect


    def launch_nukes(self):
        raise Exception('launching nukes')  # <- side effect

my_code.py

from library import HasSideEffects

class UtilitySubclass(HasSideEffects):

    def party(self):
        self.launch_nukes()
        return 'confetti'  # I want to test that my confetti cannon will work

test_my_code.py

import unittest
from unittest.mock import patch, ANY
from my_code import UtilitySubclass


class MyTestCase(unittest.TestCase):
    def test_step_by_step(self):
        self.assertRaises(Exception, UtilitySubclass) #Normal implementation raise Exception
        #Pay attention to return_value MUST be None for all __init__ methods
        with patch("library.HasSideEffects.__init__", autospec=True, return_value=None) as mock_init:
            self.assertRaises(TypeError, UtilitySubclass) #Wrong argument: autospec=True let as to catch it
            us = UtilitySubclass("my kittens") #Ok now it works
            #Sanity check: __init__ call?
            mock_init.assert_called_with(ANY, "my kittens") #Use autospec=True inject self as first argument -> use Any to discard it
            #But launch_nukes() was still the original one and it will raise
            self.assertRaises(Exception, us.party)
            with patch("library.HasSideEffects.launch_nukes") as mock_launch_nukes:
                self.assertEqual("confetti",us.party())
                # Sanity check: launch_nukes() call?
                mock_launch_nukes.assert_called_with()

    @patch("library.HasSideEffects.launch_nukes")
    @patch("library.HasSideEffects.__init__", autospec=True, return_value=None)
    def test_all_in_one_by_decorator(self, mock_init, mock_launch_nukes):
        self.assertEqual("confetti",UtilitySubclass("again my kittens").party())
        mock_init.assert_called_with(ANY, "again my kittens")
        mock_launch_nukes.assert_called_with()


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

请注意,装饰器版本简洁明了.

Note as the decorator version is neat and simple.

这篇关于如何模拟作为库一部分的超类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆