Intro to Unit Testing and Test Driven Development in Python using Pytest – Part 2

by | 16 Nov 2017 | Coding Journey | 0 comments

Unit Testing and Test Driven Development with Pytest

Avoiding Redundancy and Dependency in Testing

Before we begin diving deeper into the matters please make sure you have familiarised yourself with the first article in “Unit Testing the Universe” series to understand concepts that we will be talking about fully.

In this article we will determine the reasons for testing and skipping tests of our code. Initially we all have the tendency to think that if there is a piece of logic in our object we should test it 100%.

As it turns out, thanks god, we do not have to test all of our code explicitly. This does not mean it does not get tested – on the contrary, it will get fully tested but indirectly. Let’s see how its done in a smart way.

Intro to Unit Testing and Test Driven Development in Python using Pytest – Part 2

by | 16 Nov 2017 | Coding Journey | 0 comments

Unit Testing and Test Driven Development with Pytest

Avoiding Redundancy and Dependency in Testing

Before we begin diving deeper into the matters please make sure you have familiarised yourself with the first article in “Unit Testing the Universe” series to understand concepts that we will be talking about fully.

In this article we will determine the reasons for testing and skipping tests of our code. Initially we all have the tendency to think that if there is a piece of logic in our object we should test it 100%.

As it turns out, thanks god, we do not have to test all of our code explicitly. This does not mean it does not get tested – on the contrary, it will get fully tested but indirectly. Let’s see how its done in a smart way.

What Do We Want to Test?

I think the easiest way to imagine what we want to test is through an example. Let’s say this time you came to buy some shoes in your favourite shop (I know it’s all about buying stuff here). They supposedly have this newest cushioning system and all bells and whistles that all cool kids want to wear them.

I do not think that you would grab the proverbial “pair of pliers and a blow torch” and start checking each component of the shoe in the shop to make sure it has internally what they promise? So what do you want to test when buying shoes? Do they look nice? Do they feel comfy? Can you  run 10k in them easily? Do they grip the surface well?

Testing the shoe in a shop

If you think about the common aspect of all those question’s you realise that they all assert something about a component of a shoe, be it the sole or the interior or outside of it but do not ask how each of the components is made up internally.

What you are interested in therefore is: the interface of each component.

And that is exactly how we want to approach unit testing of the components (objects) of our apps. We look at the interface assuming that the object under test is a black box to us and its internal structure is not known (and we are deliberately not interested in that structure). The only thing we actually care about is its behaviour which is represented by the interface. With this in mind we can now talk about what the interface really is.

Discovering the Interface

Our application consists of multitude of objects cooperating with each other. This cooperation is carried out through sending messages between the objects. Let’s have a look at the way we can distinguish different messages by where they come from or go to (message origin) and by what they are trying to achieve (message type).

Message Types

The initial division of messages we discuss is by type. We have only two of them: a query and a command.

What is the interface ?

The query is a simple message which asks object for a value to return. We call those methods getters as this is all they do – get a value of something. This type of message does not change anything in an object’s state.

On the contrary the command is designed to change a value and that’s why we call it often a setter method but it will not return anything.

You will sometimes have to create methods for your objects which are both a query and a command at the same time but each part of them is unit tested differently.

Message Origins

Apart from different message types we have different origins of them. This time we can distinguish three sources of a message: incoming, private (sent to self) and outgoing. Both incoming and outgoing messages are part of the interface but the private methods are not, they are the implementation of our object instead.

Incoming messages

How to approach unit testing incoming messages

Since an incoming query literally asks our object under test for a value stored inside of it the test that we have to perform to make sure it works correctly is to assert if the returned value matches our expectation.

For an incoming command we have to assert that the value inside of our object under test has changed after the incoming command was called to what was expected.

Outgoing mesages

Way to test outgoing messages

Why don’t we want to test the outgoing queries? If you look at the picture above you will notice that the message goes to object B. The yellow arrow comes out of our current object under test A into object B and our outgoing query becomes an incoming one for object B.

Since we agreed on unit testing incoming queries, we do not want to test the outgoing ones as all those tests are redundant (no public side effects are generated by the queries).

With the outgoing command the situation is not so simple. We have to make sure that the command is being called by our object under test and with correct parameters as it generates public side effects in object B. This is to make sure that the changes that object A will introduce in object B are the changes that we actually want to make!

But we want to stick to the unit testing philosophy of isolation. Therefore we do not want to access the external objects directly – instead we prefer to use Mocks – objects which replace the real ones and are used to simulate their behaviour.

This is to avoid dependency which slows us down. If you think about it – once you enter the external object you may need to navigate through its entire logic and communicate with further objects to maybe sometime finally returning to the originator of the call – object A. We want our unit tests to be super fast so we skip on calling the real external objects.

Private methods (messages sent to self)

Private methods do not get tested

When it comes to the private methods – they are the implementation of our object. We already mentioned that we want to avoid direct testing of their code as it adds to the workload and has no meaningful result since we can test the implementation indirectly through the tests of the interface.

Also when you think about it if you have tests written for your private methods what will happen if you decide to improve or change the implementation? All those tests will fail and panic will ensue! But if you test indirectly, through the interface, you can change the entire internal structure of your object and no one will ever notice – as long as you keep the interface the same.

One important caveat here – if you just designed a new private algorithm that the entire universe depends upon and it is so complicated that no one is sure if it does what it should – by all means test it. All these rules above are just guidelines for you. You can break them anytime you want but know why they are there and stick to them whenever you can.

And if in the future your tests of private methods stop you from improving the code of your object – just annihilate them with passion.

Summary

Let’s wrap this all up with a simple table.

Message Type Message Origin
Incoming Private Outgoing
Query Test returned value Do not test Do not test
Command Test change of value Do not test Test if sent out with correct parameters (use Mock to simulate external object)

All that info can be summed up now like this:

  • Incoming queries – assert about returned value
  • Incoming commands – assert about a change of value
  • Outgoing commands – test if they were sent out with correct parameters, use mocks to replace external objects
  • Outgoing queries don’t get tested
  • Private methods don’t get tested

Pretty nice, right? If we are lucky half of the code does not need direct, explicit testing!

Happy that half the code does not need testing

I hope to see you in part 3 of the series where we will start building a class for testing and introduce Pytest – the test framework we will be using.

0 Comments

Submit a Comment

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

Created by: Tomasz Kluczkowski

Copyright © Tomasz Kluczkowski

Created by: Tomasz Kluczkowski

Copyright © 2017