16.2. Unit Testing

../../_images/fun-geekandpoke-development-driven-tests.jpg

Figure 16.1. Development driven tests

16.2.1. Glossary

Stub

A method stub or simply stub in software development is a piece of code used to stand in for some other programming functionality. A stub may simulate the behavior of existing code (such as a procedure on a remote machine) or be a temporary substitute for yet-to-be-developed code. Stubs are therefore most useful in porting, distributed computing as well as general software development and testing.

Mock

In object-oriented programming, mock objects are simulated objects that mimic the behavior of real objects in controlled ways. In a unit test, mock objects can simulate the behavior of complex, real objects and are therefore useful when a real object is impractical or impossible to incorporate into a unit test.

Unittest

In computer programming, unit testing is a software testing method by which individual units of source code, sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures, are tested to determine whether they are fit for use.

16.2.2. Running tests

16.2.3. Running tests with your IDE

  • View menu -> Run... -> Unittest in myfunction

16.2.4. From code

if __name__ == "__main__":
    import unittest
    unittest.main()

16.2.5. From command line

Display only errors. With -v display progress:

$ python -m unittest myfile.py
$ python -m unittest -v myfile.py

16.2.6. Example 1

16.2.7. Example 2

16.2.8. Example 3

16.2.9. Example 4

16.2.10. Example 5

16.2.11. Mock

  • Mock and MagicMock objects create all attributes and methods as you access them and store details of how they have been used.

from unittest.mock import MagicMock

thing = ProductionClass()
thing.method = MagicMock(return_value=3)

thing.method(3, 4, 5, key='value')
# 3

thing.method.assert_called_with(3, 4, 5, key='value')

16.2.12. Side effect

  • Raising an exception when a mock is called

from unittest.mock import Mock

mock = Mock(side_effect=KeyError('foo'))

mock()
# Traceback (most recent call last):
# KeyError: 'foo'

16.2.13. patch

  • The object you specify will be replaced with a mock (or other object) during the test and restored when the test ends

from unittest.mock import patch

@patch('module.ClassName2')
@patch('module.ClassName1')
def test(MockClass1, MockClass2):
    module.ClassName1()
    module.ClassName2()
    assert MockClass1 is module.ClassName1
    assert MockClass2 is module.ClassName2
    assert MockClass1.called
    assert MockClass2.called


test()
from unittest.mock import patch

class MyClass:
    def method(self)
        pass

with patch.object(MyClass, 'method', return_value=None) as mock_method:
    thing = MyClass()
    thing.method(1, 2, 3)

mock_method.assert_called_once_with(1, 2, 3)

16.2.14. Stub

  • writing classes or functions but not yet implementing them

  • After you have planned a module or class, for example by drawing it's UML diagram, you begin implementing it.

  • As you may have to implement a lot of methods and classes, you begin with stubs.

  • This simply means that you only write the definition of a function down and leave the actual code for later.

class Foo:
     def bar(self):
         raise NotImplementedError

     def tank(self):
         raise NotImplementedError

16.2.15. Assignments

Code 16.3. Solution
"""
* Assignment: DevOps Unittest Rectangle
* Complexity: medium
* Lines of code: 100 lines
* Time: 21 min

English:
    1. Write unittest for `Rectangle`
    2. Run doctests - all must succeed

Polish:
    1. Napisz testy jednostkowe dla `Rectangle`
    2. Uruchom doctesty - wszystkie muszą się powieść
"""

import unittest


class Rectangle:

    def __init__(self, a, b):
        self.side_a = a
        self.side_b = b

        if a <= 0 or b <= 0:
            raise ValueError('Side length must be positive')

    def area(self) -> int:
        return self.side_a * self.side_b

    def circumference(self) -> int:
        return (self.side_a + self.side_b) * 2

    def __str__(self):
        return f'Rectangle({self.a}, {self.b})'


Code 16.4. Solution
"""
* Assignment: DevOps Unittest Dragon
* Complexity: medium
* Lines of code: 100 lines
* Time: 21 min

English:
    1. Write unittest for the dragon from :ref:`Dragon Alpha`
    2. Run doctests - all must succeed

Polish:
    1. Napisz testy jednostkowe dla Smoka z :ref:`Dragon Alpha`
    2. Uruchom doctesty - wszystkie muszą się powieść
"""

from random import randint
from unittest import TestCase


class Config:
    RESOLUTION_X_MIN = 0
    RESOLUTION_X_MAX = 1024
    RESOLUTION_Y_MIN = 0
    RESOLUTION_Y_MAX = 768

class Status:
    ALIVE = 'alive'
    DEAD = 'dead'

class Dragon:
    DAMAGE_MIN = 5
    DAMAGE_MAX = 20

    class IsDead(Exception):
        pass

    def __init__(self, name, x=0, y=0):
        self.status = Status.ALIVE
        self.name = name
        self.position_x = x
        self.position_y = y

    def get_position(self):
        return self.position_x, self.position_y

    def set_position(self, x, y):
        if x > Config.RESOLUTION_X_MAX:
            x = Config.RESOLUTION_X_MAX

        if x < Config.RESOLUTION_X_MIN:
            x = Config.RESOLUTION_X_MIN

        if y > Config.RESOLUTION_Y_MAX:
            y = Config.RESOLUTION_Y_MAX

        if y < Config.RESOLUTION_Y_MIN:
            y = Config.RESOLUTION_Y_MIN

        self.position_x = x
        self.position_y = y

    def move(self, down=0, left=0, up=0, right=0):
        x, y = self.get_position()
        x += right - left
        y += down - up
        self.set_position(x, y)

    def make_damage(self):
        if self.is_dead():
            raise Dragon.IsDead
        return randint(self.DAMAGE_MIN, self.DAMAGE_MAX)

    def is_dead(self):
        if self.status == Status.DEAD:
            return True
        else:
            return False