【问题标题】:How to unit test factory in python?如何在python中对工厂进行单元测试?
【发布时间】:2012-09-02 16:00:19
【问题描述】:

当单元测试类应该只针对其合作者的公共接口进行测试。在大多数情况下,使用 假对象 - Mocks 替换协作者很容易实现。当正确使用依赖注入时,这应该很容易。

但是,当尝试测试工厂类时,事情会变得复杂。让我们看例子

模块wheel

class Wheel:
    """Cars wheel"""

     def __init__(self, radius):
         """Create wheel with given radius"""

         self._radius = radius #This is private property

模块engine

 class Engine:
     """Cars engine"""

     def __init(self, power):
     """Create engine with power in kWh"""

         self._power = power #This is private property

模块car

class Car:
    """Car with four wheels and one engine"""

    def __init__(self, engine, wheels):
        """Create car with given engine and list of wheels"""

        self._engine = engine
        self._wheels = wheels

现在让我们CarFactory

from wheel import Wheel
from engine import Engine
from car import Car

class CarFactory:
    """Factory that creates wheels, engine and put them into car"""

    def create_car():
        """Creates new car"""

        wheels = [Wheel(50), Wheel(50), Wheel(60), Wheel(60)]
        engine = Engine(500)
        return Car(engine, wheels)

现在我想为CarFactory 编写一个单元测试。我想测试一下,工厂正确地创建了对象。但是,我不应该测试对象的私有属性,因为它们将来可以更改,这会破坏我的测试。想象一下,Wheel._radius 替换为 Wheel._diameterEngine._power 替换为 Engine._horsepower

那么如何测试工厂呢?

【问题讨论】:

  • 你为什么要写Java并称它为Python?不需要 CarFactory 类:create_car 应该是一个独立的函数。而且我们在 Python 中没有私有变量。
  • @Daniel Roseman 当我说私有时,我的意思是私有作为内部(不是 api 的一部分)。我并不是说作为语言结构的私有。关于 CarFactory 是类,你能解释为什么它是错的吗?我是python新手,所以很抱歉我不够pythonic。
  • 如果您不封装任何数据,则无需创建类。类不仅仅是放置函数的地方:这就是模块的用途。将create_car 作为模块级独立函数。另请注意,无需在单独的模块中使用 WheelCarEngine
  • @Daniel Roseman:我认为您可能会将问题中使用的琐碎示例代码与问题本身混淆。恕我直言,OP 的方法本质上没有任何问题。对于非常简单的模块和类可能没有必要,但在应用于大型复杂问题时可能会很有用。

标签: python unit-testing dependency-injection


【解决方案1】:

幸运的是,由于monkey_patching,python 测试工厂很容易。您不仅可以替换对象的实例,还可以替换整个类。让我们看例子

import unittest
import carfactory
from mock import Mock

def constructorMock(name):
    """Create fake constructor that returns Mock object when invoked"""
    instance = Mock()
    instance._name_of_parent_class = name
    constructor = Mock(return_value=instance)
    return constructor

class CarFactoryTest(unittest.TestCase):

    def setUp():
        """Replace classes Wheel, Engine and Car with mock objects"""

        carfactory.Wheel = constructorMock("Wheel")
        carfactory.Engine = constructorMock("Engine")
        carfactory.Car = constructorMock("Car")

    def test_factory_creates_car():
        """Create car and check it has correct properties"""

        factory = carfactory.CarFactory()
        car_created = factory.create_car()

        # Check the wheels are created with correct radii
        carfactory.Wheel.assert_called_with(radius=50)
        carfactory.Wheel.assert_called_with(radius=50)
        carfactory.Wheel.assert_called_with(radius=60)
        carfactory.Wheel.assert_called_with(radius=60)

        # Check the engine is created with correct power
        carfactory.Engine.assert_called_once_with(power=500)

        # Check the car is created with correct engine and wheels
        wheel = carfactory.Wheel.return_value
        engine = carfactory.Engine.return_value
        carfactory.Car.assert_called_once_with(engine, [wheel, wheel, wheel, wheel])

        # Check the returned value is the car created
        self.assertEqual(car_created._name_of_parent_class, "Car")

所以我们用 Mock 替换类和它们的构造函数,它返回我们的假实例。这使我们能够检查,构造函数是用正确的参数调用的,所以我们不需要依赖真正的类。我们真的能够在 python 中不仅使用假实例,还可以使用假类。

另外,我不得不提一下,上面的代码并不理想。例如,假构造函数应该为每个请求真正创建新的 Mock,因此我们可以检查汽车是否使用正确的车轮(例如正确的顺序)。可以这样做,但代码会更长,我希望让示例尽可能简单。

在示例中,我使用了 Python 的 Mock 库 http://www.voidspace.org.uk/python/mock/

但这不是必须的。

【讨论】:

  • 可能值得一提的是Mock 不是一个标准库(以及从哪里获得它)。否则 +1。
猜你喜欢
  • 2011-06-23
  • 2011-10-18
  • 2019-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-03
  • 2016-05-15
  • 1970-01-01
相关资源
最近更新 更多