Breaking dependencies#

Often it is not that easy to add tests. Some dependencies in our code prevent us from getting classes into test or the tests are slow as they depend on some network or datebase setup. With breaking dependencies we look at how we can circumvent dependencies so that we can get efficient tests into place before we make changes.

Extract and Overwrite#

Before we have some method that uses a global variable. With extract and overwrite we extract reads to global variables, or calls to e.g. data bases, into their own method calls.

import random

speed = random.randint(1, 100)

class DominoDay(object):
    def __init__(self):
        pass

    def turn(self):
        if speed >= 70:
            return True
        else:
            return False

After: we extracted the read of the global variable speed.

import random

speed = random.randint(1, 100)

class DominoDay(object):
    def __init__(self):
        pass

    def get_speed(self):
        return speed

    def turn(self):
        if self.get_speed() >= 70:
            return True
        else:
            return False

Testing: now we can overwrite the method for testing such that we can break the dependency on the speed variable.

import unittest
import random

speed = random.randint(1, 100)

class DominoDay(object):
    def __init__(self):
        pass

    def get_speed(self):
        return speed

    def turn(self):
        if self.get_speed() >= 70:
            return True
        else:
            return False

class TestDominoDay(DominoDay):
    def get_speed(self):
        return 90


class Tests(unittest.TestCase):
    def test_turn(self):
        c = TestDominoDay()
        self.assertEqual(c.turn(), True)

unittest.main()

Subclass and Overwrite#

We some basics of subclass and overwrite in the extract and overwrite method.

The point of subclass and overwrite is that we create for testing a subclass of our class that we want to tests where we disable the functionality that causes us headaches for testing. This way we get the class into testing without having to rewrite the original class.

class SelfDestructor(object):
    def __init__(self):
        pass

    def explode(self):
        print("BOOM")

class Car(object):
    def __init__(self):
        self.buttons_work = False

    def button_check(self):
        SelfDestructor().explode()
        # ...
        self.buttons_work = True

    def car_check(self):
        self.button_check()
        # self.tire_check()
        # ...

After: the class Car is unchanged, but we prevented the SelfDestructor from being called in the SafeCar subclass.

class SelfDestructor(object):
    def __init__(self):
        pass

    def explode(self):
        print("BOOM")

class Car(object):
    def __init__(self):
        self.buttons_work = False

    def button_check(self):
        SelfDestructor().explode()
        # ...
        self.buttons_work = True

    def car_check(self):
        self.button_check()
        # self.tire_check()
        # ...

class SafeCar(Car):
    def button_check(self):
        self.buttons_work = True

Now we can use the subclass for testing:

import unittest

class SelfDestructor(object):
    def __init__(self):
        pass

    def explode(self):
        print("BOOM")

class Car(object):
    def __init__(self):
        self.buttons_work = False

    def button_check(self):
        SelfDestructor().explode()
        # ...
        self.buttons_work = True

    def car_check(self):
        self.button_check()
        # self.tire_check()
        # ...

class SafeCar(Car):
    def button_check(self):
        self.buttons_work = True

class Tests(unittest.TestCase):
    def test_button_check(self):
        c = SafeCar()
        c.car_check()
        self.assertEqual(c.buttons_work, True)

unittest.main()
SafeCar().car_check()
Car().car_check()

Parameterize Method/Constructor#

Sometimes we create objects inside of our constructor or methods. But these objects could cause us troubles if their behaviour is non-deterministic or maybe just too slow for testing.

import random

class WeekendFun(object):
    def __init__(self):
        self.utilities = ["knife", "baseball bat"]
        # or network/database/...

    def have_fun_with(self):
        # or write to network/database/...
        return random.choice(self.utilities)

class WeekendSupervisor(object):
    def __init__(self):
        self.activities = WeekendFun()

    def allow(self):
        return self.activities.have_fun_with()

The solution is to pass the objects into our constructors/method such that we can control what objects are used.

import random

class WeekendFun(object):
    def __init__(self):
        self.utilities = ["knife", "baseball bat"]
        # or network/database/...

    def have_fun_with(self):
        # or write to network/database/...
        return random.choice(self.utilities)

class WeekendSupervisor(object):
    def __init__(self, activities=WeekendFun()):
        self.activities = activities

    def allow(self):
        return self.activities.have_fun_with()

In this way we can use special objects for testing:

import unittest, random

class WeekendFun(object):
    def __init__(self):
        self.utilities = ["knife", "baseball bat"]
        # or network/database/...

    def have_fun_with(self):
        # or write to network/database/...
        return random.choice(self.utilities)


class SafeWeekend(WeekendFun):
    def __init__(self):
        pass

    def have_fun_with(self):
        return "boardgame"

class WeekendSupervisor(object):
    def __init__(self, activities=WeekendFun()):
        self.activities = activities

    def allow(self):
        return self.activities.have_fun_with()

class Tests(unittest.TestCase):
    def test_get_price(self):
        safe = SafeWeekend()
        s = WeekendSupervisor(safe)
        self.assertEqual(s.allow(), 
                         SafeWeekend().have_fun_with())

unittest.main()