如何在python中测试工厂?

Sam*_*pak 4 python unit-testing dependency-injection

单元测试第一类应仅针对其协作者的公共接口进行测试.在大多数情况下,这很容易实现用假物体替换协作者- 模拟器.正确使用依赖注入时,这应该很容易.

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

wheel

class Wheel:
    """Cars wheel"""

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

         self._radius = radius #This is private property
Run Code Online (Sandbox Code Playgroud)

engine

 class Engine:
     """Cars engine"""

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

         self._power = power #This is private property
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

现在让我们拥有 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)
Run Code Online (Sandbox Code Playgroud)

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

那么如何测试工厂?

Sam*_*pak 8

幸运的是,在python测试中,工作很容易归功于monkey_patching.您不仅可以替换对象的实例,还可以替换整个类.让我们看看例子

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")
Run Code Online (Sandbox Code Playgroud)

所以我们用Mock替换类及其构造函数,返回我们的假实例.这使我们能够检查,使用正确的参数调用构造函数,因此我们不需要依赖实际的类.我们真的能够在python中不仅使用假实例,而且还使用假类.

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

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

但这没有必要.