2012-09-02 103 views
3

单元测试第一类应该仅针对其协作者的公共接口进行测试。在大多数情况下,使用虚假物品代替合作伙伴可以轻松实现 - Mocks。正确使用依赖注入时,这应该很容易。如何在Python中对工厂进行单元测试?

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

模块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._radiusWheel._diameterEngine._power所取代,被Engine._horsepower所取代。

那么如何测试工厂呢?

+0

你为什么写Java并将其称为Python?不需要CarFactory类:'create_car'应该是一个独立的函数。我们在Python中没有私有变量。 –

+0

@丹尼尔罗斯曼当我说私人时,我的意思是私人的内部(而不是api的一部分)。我不是指私人语言结构。而关于CarFactory的课程,你能解释它为什么错了吗?我是新来的python,所以道歉我不pythonic足够。 –

+0

如果不封装任何数据,则不需要创建类。类不是放置函数的简单场所:这就是模块的用途。将'create_car'作为模块级别的独立函数。另外请注意,不需要将'Wheel','Car'和'Engine'放在单独的模块中。 –

回答

6

幸运的是,在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") 

所以我们用Mock替换类和它们的构造函数,返回我们的假实例。这使我们能够检查,构造函数被调用了正确的参数,所以我们不需要依赖实际的类。我们真的能够在Python中使用不仅伪造的实例,而且伪造的类。

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

在这个例子中我使用模拟库蟒蛇http://www.voidspace.org.uk/python/mock/

但它是没有必要的。

+0

值得一提的是,'Mock'不是一个标准库(以及从哪里得到它)。否则为+1。 – martineau