So the last couple of weeks I have been working on fixing the tests that were failing on the glance-replicator patch. While trying to fix these tests, I learned quite a bit about TDD, unit tests and mock.
The Importance of Unit Testing
Test-driven development (TDD) is a software development process that relies on the repetition of a very short development cycle: requirements are turned into very specific test cases, then the software is improved to pass the new tests, only.
When there are so many people working on such a huge amount of code, writing up new functionalities, features, behaviours etc can get pretty difficult to manage. As you edit/update/enhance code you can make a change which could break the code which was earlier functional and this break might even go undetected causing lots of damage later on to the development team and the stakeholders involved. If such a break occurs it might even get difficult to debug where the error has crept in and which piece of code is no longer working correctly.
Unit testing saves us here. It is a process in which the smallest testable parts of an application, called units, are individually and independently scrutinized for proper operation. Developers write tests for every class they produce. They are extremely helpful from a developer’s point of view. The tests are intended to test every aspect of the class that could conceivably not work.
OpenStack which is mainly written in Python employs the library that exists for unit testing in Python (the documentation for which can be found here) to good use. Every OpenStack project has its own set of unit tests contained within the project, which can be run using tox in most projects and/or run_tests.sh by developers.
Since we’re testing your code as we introduce functionality, we’re going to begin developing a suite of test cases that can be run each time you work on your logic. When a failure happens, we know that we have something to address. Of course, this comes at the expense of investing time to write a suite of tests early in development, then running them again and again, investing in machinery to automate running tests before a new merge (Good ol’ Jenkins) but as the project grows we can simply run the tests that you’ve developed to ensure that existing functionality isn’t broken when new functionality is introduced.
I came across the description of a good unit test here which I think was pretty informative. We can follow the principles mentioned in the link to the last alphabet and be able to write extremely good quality tests.
Now we know why testing is important and how to write a good testing suite but it is not always that simple. Sometimes the code we write directly interacts with what we can call “dirty” services. These are the services that are crucial to our application, but whose interactions have intended but undesired side-effects—that is, undesired in the context of an autonomous test run. These issues could be the overhead of a system call, filesystem accesses, network accesses, external API requests or in my case http requests.
The Python unittest library which facilitates unit testing includes a subpackage named unittest.mock—or if you declare it as a dependency, simply mock—which provides extremely powerful and useful means by which to mock and stub out these undesired side-effects.
Mocking basically is the replacement of one or more function calls or objects with mock calls or objects.
A mock function call returns a predefined value immediately, without doing any work. A mock object’s attributes and methods are similarly defined entirely in the test, without creating the real object or doing any work. However, it needs to be setup by the developer.
I have used the Mock Library to write tests in my glance-replicator patch for the functionalities I wrote while migrating from httplib to the requests library. I found this basic summary of how mock can be used here, which I found very useful when writing my tests.
That’s all for now! Thank you for reading! 😀