In the previous article, we had a chance to explore some of the main concepts behind Test Driven Development and use them in Python. This approach of development utilizes unit tests as its driving force. Over the years, it has proved itself as one of the best technique for increasing the quality of the software. Apart from that, this way of development is reducing the number of bugs in your code and gives you the ability to eliminate manual testing, which is expensive and restricting.
Having a bug in production can be very expensive. Certain statistics say that finding a bug during development is 10 times cheaper than finding the same bug during QA and 100 times cheaper than finding it in the production. This way TDD made itself first line of defense against costly production bugs.
TDD comes from extreme programming (XP) world, but it was gladly adopted by Agile practitioners. In its essence, it is consisting of small cycles of development. Every cycle is having three steps: writing a unit test for functionality that doesn’t yet exist (initially fails), implementing the minimum amount of code to pass that test, refactoring. Then we repeat the whole process for a next functionality. Check out how it is done in the previous article. One of the cool things is that this way refactoring has a safety net, and we can do it stress-free.
I like to use something I like to call the TDD mantra to remember and these three crucial steps of TDD. The mantra goes like this: Red – Green – Refactor. During the Red phase, we write a test that will fail. Green phase refers to the moment we implement our functionality and test passes. And finally we refactor our code – we don’t really have a color for that one. So there you go, TDD Mantra – Red, Green, Refactor.
In general, by using Test Driven Development we avoid creating over complicated designs and overengineered systems. This is probably the biggest benefit of TDD. That is why a lot of people refers to this approach as Test Driven Design. Basically, it forces you to design classes properly and to follow principles like SOLID, YAGNI and DRY.
At the end of the previous article, we mentioned that we need to know how to mock objects in order to fully master TDD. So what would this mean? Well, in Object-Oriented programming mock objects are defined as simulated objects. They mimic the behavior of real objects in controlled ways. Why would anyone do that? Well, the whole point of unit testing is isolating certain functionality (unit) and test just that. We are trying to remove all other dependencies.
Sometimes this can be a database or file system, so using mock object we don’t step into the world of integration tests. Apart from that, this would mean that we need to take care of the data in the database before and after every test, and that is something we want to avoid. Other times dependency can be just some other class or function. This certainly is not helping us with our goal – the isolation of unit. That is why we define mock objects.
A mock object should be used in the situations when:
- The real object is slow
- The real object rarely and is difficult to produce artificially
- The real object produces non-deterministic results
- The real object does not yet exist (often the case in TDD )
Mock Objects Python Basics
For the purpose of this article, we use the unittest module. To be more specific, we use the Mock class. Essentially, this class is the core class using which we can create stubs in our test methods. After performing an action on these objects, you can check various details which can help you determine is your functionality behaving properly. The cool trick is that with the objects of Mock class you can mock any function, variable or object. Check out this test:
Note that you can define and access any ‘field’ of a Mock object which returns another object of a Mock class. Apart from that, you can observe this object as function as well. This means that you can prepare this mock object in any way you want. For example, you can assign some value to the fields of this object. It can be achieved either by assigning this value directly or using configure_mock:
Of course, you can configure mock methods as well. For example, if you want your mock object to have a method that returns certain value, you can do it like this:
You see that we used
This side_effect option is a
Using Mock class we can verify that some method has been called as well:
For that matter, we can check if some function has been called once or multiple times:
This is very useful in situations in which we inject one object into another and then we need to verify that some functions of the first object have been called. Finally, whenever we want to reset some of these internal counters we can use reset_mock. Note that this way we are only resting interactions, but not the configuration itself:
Ok, these are some simple behaviors that we can do with Mock class. However, we still haven’t answered a question of how we can mock classes that are we wrote or classes that come from some third-party library. These things are done using patching.
Mock Objects Python Patching
Patching is quite simple. Basically, we use it to mock parts of your system that shouldn’t be called in our unit tests. In a nutshell, we isolate unit that we want to test. We can mock pretty much anything this way. Let’s we observe this piece of code inside of path.py file:
In our unit tests for current_path function, we don’t really want to call os method because it is not essential for our functionality, and also the return
value might differ between environments. This is some third-party/import we talked about which we want to simulate and remove that particular dependency. This is where the
Using the patch method, we create a mock object that will handle this call – os_mocked. Note the way we called patch on ‘path.os’. This way we can mock any object from any import. The other way we can write this is:
Pay attention to the additional parameter that we pass into the test method. This additional parameter holds our mock object. In this article, we will use this convention.
The same approach we can use if we want to mock context managers. Let’s extend our path.py with the function for reading from a file:
Now, we want to mock open context manager, because we don’t want to really open the file in our unit test. This can take a while and we can load a lot of data in memory. So, we can do something like this:
We use StringIO as a return value of open context manager mock.
There’s nothing we haven’t already seen here except the __enter__method.
Patching can be applied to the classes from our system as well. If you remember, in the previous article, we developed some funny Rick and Morty classes. One of the functionalities was that we can assign Morty to a Rick. Here is how those classes look like:
As a reminder here is how the test in Rick tests for assignment process looks like:
This might not be the best approach. Namely, we create objects of two different classes in the same test. Some may argue that this is more integration test than a unit test. Other may say that this is a unit test if we define the whole assignment process as a unit. Both might be right. However, let’s say that we want to isolate functionality in Rick class more. That would mean that we need to mock
In a nutshell, we mock Morty object using the patch
Another cool thing that we can do with Mock class is to mock just certain aspects of some class, ie. mock partially. Let’s mock just the mentioned assign method in Rick class:
This way you can isolate units even further.
Mock objects are an essential step if we want to properly isolate units that we want to test. With that, it is crucial to Test Driven Development. In this article, we got a chance to see how Mock class from the
Thank you for reading!
Read more posts from the author at Rubik’s Code.