Unit Testing the Universe – Part 5

by | 15 Jan 2018 | Coding Journey | 0 comments

Unit Testing and Test Driven Development with Pytest

Coding Examples – Outgoing Commands and Mocks

In this final piece of our series on basics of unit testing we get to test outgoing commands and learn how to use mocks to remove dependency on external objects.

Recent Comments

    Unit Testing the Universe – Part 5

    by | 15 Jan 2018 | Coding Journey | 0 comments

    Unit Testing and Test Driven Development with Pytest

    Coding Examples – Outgoing Commands and Mocks

    In this final piece of our series on basics of unit testing we get to test outgoing commands and learn how to use mocks to remove dependency on external objects.

    Model Further Structure of the Application

    Before we proceed further into coding the tests it is a good idea to have a look at how our application might be built further. This will allow us to figure out why it is important to remove references to external objects when we are unit testing and replace them with fake objects – Mocks

    Model of the Further structure of the application

    Now please consider the diagram above representing information flow inside of our application and think what happens when you send set_target_speed message out to the inverter object. We are getting into the whole vicious circle of further dependencies and the universe may reach its heat death before we get the answer back and notice that our outgoing command worked.

     

     

    Why to Use Mocks?

    • We do not want to test the logic of the external object. Since mock replaces it and does not have any complicated logic itself, the tests that we will make using it are much faster.
    • We can use mocks also to replace methods from libraries to which objects we do not have a direct access and cannot replace a reference to them easily. This involves a concept called patching which we do not discuss in the basics of unit testing but will in the advanced part later on. This allows us to replace for example built in python methods like open() or input() or get() calls to web APIs using requests library at run time.
    • Mocks provide a plethora of methods useful in testing. One of them is return value which gives you ability to return to your object under test precisely what you want. This enables you to test your object for various scenarios when the collaborating object returns different values.
    • All of those things let you chop off all the dependencies making tests fast and isolate your object properly.

    Seems like finally we have found a use for the unittest framework! For this moment in time all we are going to use from it is the mock module and from that the Mock class. In our new fixture we just instantiate the Mock object and return it for use in test functions later on.

    Test set_target_speed Method

    The last method to test is an outgoing command. We want to confirm if it gets sent out with a correct parameter to prove that we are introducing correct change in the external object. For this we will replace the reference to our inverter object in the Fridge class and stick a Mock object in its place. To carry out a test of an outgoing command we have to call it on the Fridge object and then use mock to assert about that call.

    Let’s look at the code for this test then.

    Testing Methodology

    We have created a new instance of the Fridge object inside the test function this time and used our mock_inverter fixture instead of a reference to the inverter. The rest of the parameters of our class get instantiated with the default values set in the class itself.

    We have then called the method on the object under test with an argument 1000.

    Mock object has a wonderful feature that it will register all methods called on it and store them. We can then access them like attributes and make assertions about them.

    Now using Mock’s method: assert_called_once_with we create an assertion about the call. The way we have to do this is to use Mock object and access the method that we want to assert about (like an attribute – using dot) and then call Mock’s assert_called_once_with method with parameters comma separated (if there are more than one).

    Mock then carries out the assertion if it was actually called with our set_target_speed method with an argument 1000.

    Initial test results

    We run our test script and make sure that the new test fails in the first try.

    ================================== FAILURES ========================================
    __________________________________ test_set_target_speed _________________________________
    mock_inverter = <Mock id='140530704193968'>
    def test_set_target_speed(mock_inverter):
    """Test outgoing command called with correct parameter."""
    fridge = Fridge(mock_inverter)
    > fridge.set_target_speed(1000)
    E AttributeError: 'Fridge' object has no attribute 'set_target_speed'
    test_fridge.py:69: AttributeError
    ============================ 1 failed, 4 passed in 0.04 seconds ============================

    And it does since there is no set_target_speed method yet.

    Add Set_Target_Speed to Pass the Test

    The next step for us is to add code in the Fridge class and make the test pass. See the code below for the details.

    set_target_speed requires reference to the external object (_inverter) and calls on it to carry out a change of speed. Since in the test we replaced that reference with a Mock what really will happen in the test now is that we will call the method on the Mock object which will remember this call and the name of the method as one of its attributes.

    Final test results (all)

    This time we run our test script with the verbose option enabled.

    (python362_env) tomasz_kluczkowski@xps15:~/Dev/unit_testing_examples/tests$ pytest test_fridge.py -v
    ====================== test session starts ======================
    platform linux -- Python 3.6.2, pytest-3.2.3, py-1.4.34, pluggy-0.4.0 -- /home/tomasz_kluczkowski/.virtualenvs/python362_env/bin/python
    cachedir: .cache
    rootdir: /home/tomasz_kluczkowski/Dev/unit_testing_examples/tests, inifile:
    collected 5 items
    test_fridge.py::test_get_temp PASSED
    test_fridge.py::test_get_fan_speed_setting PASSED
    test_fridge.py::test_set_temp PASSED
    test_fridge.py::test_raises_exception_when_temp_too_low PASSED
    test_fridge.py::test_set_target_speed PASSED
    ====================== 5 passed in 0.02 seconds ======================

    The verbose option produces a nice output for us listing every single test and its results.

    Conclusions

    Congratulations! If you read the whole “Unit Testing the Universe” series you should be able to start your own unit testing adventure quite easily. If you have doubts about any aspects covered, do not hesitate to contact me or write in the comments please.

    Stuff we have learned:

    • Thankfully not everything has to be tested directly. Through a smart approach of testing through the use of the interface we avoid binding ourselves to a current implementation or writing tests that are completely redundant.
    • The clear distinction of type and origin of message helps us to decide whether to test it or not.
    • You now know how to get started with Pytest and use a simple Mock object to replace references to your external objects.
    • We have also covered Test Driven Development and it should no longer be scary. With time and good problem description / specification you will be able to use TDD with ease.

    Thank you for reading and keep an eye on this blog as I am preparing the advanced unit testing post where I will explain patching of 3rd party libraries’ methods, test and fixture parametrisation, setup & tear-down code, returning values from Mock objects etc.

    Recent Comments

      0 Comments

      Submit a Comment

      Your email address will not be published. Required fields are marked *

      Created by: Tomasz Kluczkowski

      Copyright © 2017

      Created by: Tomasz Kluczkowski

      Copyright © 2017