Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unit test factory in python?

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?

like image 332
Samuel Hapak Avatar asked Sep 02 '12 16:09

Samuel Hapak


People also ask

How do you write a unit test in Python example?

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.

What is factory method in Python?

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.

Where do I put unit tests in Python?

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.


1 Answers

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.

like image 140
Samuel Hapak Avatar answered Oct 02 '22 17:10

Samuel Hapak