When unit testing class one should test only against public interface of its collaborators. In most cases, this is easily achieved replacing collaborators with fake objects - Mocks. When using dependency injection properly, this should be easy most times.
However, things get complicated when trying to test factory class. Let us see example
Module wheel
class Wheel:
"""Cars wheel"""
def __init__(self, radius):
"""Create wheel with given radius"""
self._radius = radius #This is private property
Module engine
class Engine:
"""Cars engine"""
def __init(self, power):
"""Create engine with power in kWh"""
self._power = power #This is private property
Module 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
Now let us have 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)
Now I want to write an unit test for the CarFactory
. I want to test, the factory creates objects correctly. However, I should not test the private properties of objects, because they can be changed in future and that would break my tests. Imagine, Wheel._radius
replaced by Wheel._diameter
or Engine._power
replaced by Engine._horsepower
.
So how to test the factory?
For example, we named the file for unit-testing as Basic_Test.py . So the command to run python unittest will be: $python3. 6 -m unittest Basic_Test. Testing If you want to see the verbose, then the command will be; $python3.
Factory Method is a Creational Design Pattern that allows an interface or a class to create an object, but lets subclasses decide which class or object to instantiate. Using the Factory method, we have the best ways to create an object.
Tests are defined as functions prefixed with test_ and contain one or more statements that assert code produces an expected result or raises a particular error. Tests are put in files of the form test_*. py or *_test.py , and are usually placed in a directory called tests/ in a package's root.
Fortunately in python testing factories is easy thanks to monkey_patching. You can replace not only instances of objects, but also whole classes. Let us see example
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")
So we replace the classes and their constructors with Mock, that returns our fake instance. That enable us to check, the constructor was called with correct parametres and so we do not need to rely on real classes. We are really able in python to use not only fake instances, but also fake classes.
Also, I have to mention, the code above is not ideal one. For example, fake constructor should really create new Mock for each request, so we can check the car is called with correct wheels (correct order for example). This could be done, but the code would be longer and I wanted to keep the example as simple as possible.
In the example I have used Mock library for python http://www.voidspace.org.uk/python/mock/
But it is not necessary.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With