Unit testing in Python

Unit testing in Python

Unit tests and the TDD methodology (test-driven development, or development through testing) really justify themselves, as many projects have proven the practical benefits of their use. Unit testing takes software development to a new level of quality and maturity. Without unit testing, when the moment comes to choose between functionality, time, and quality, it is quality that always suffers. Typically, developers do not have enough time for testing and there is a race for the number of released functionalities. To eliminate the problems of testing and detecting errors, unit tests help to solve these problems with little additional effort. The Python programming language has a very good system for unit testing, and many frameworks support working with unit tests.

Why unit testing is important?

Quite often, there are many excuses about why unit tests are not used. This applies to both developers and customers. Probably the best summary of all the excuses is gathered in one place in the book "Pragmatic unit testing in Java using JUnit" by Andy Hunt and Dave Thomas. This book clearly shows that unit testing is one of the most important working methods of a senior developer. Some developers may say, "my code is too difficult to test."  However, even at the user interface level, this problem can be solved. There are a lot of approaches to how to test UI in Python frameworks. If you have good code coverage with a set of tests, including a UI, you can always make new changes to the code, being fully confident that you haven't broken anything. The second reason given for why code is too difficult to test says that the architectural design is too confusing. In this case, unit testing helps to create a good design. Using unit tests is very simple, so if you can select a separate layer with logic and write tests for it, then you can automatically verify the entire business logic. And in the absence of a domain model, you can use mock objects, as for this, there are many libraries in Python. You can write thousands of lines of code very quickly and you will have success in the project right up to the point of satiety. The point of satiety in development comes when the addition of even the smallest and most harmless functionality generates all kinds of errors and it takes a lot of time to fix them. This is just where unit tests come in handy when you can localize the error in just a couple of minutes and improve your project’s architecture in a few hours. Properly designed unit tests have saved many thousands of projects in difficult situations.

What is unit testing?

Unit testing is a programming process that allows you to check the correctness of individual modules of the program source code. The idea is to write tests for each non-trivial function or method. This allows you to quickly check whether the next change in the code has led to regression. meaning errors in the already tested places of the program. Additionally, such tests facilitate the elimination of such errors.

Using unit testing in Python

Python also has an effective testing system. For Python, the library unittest is a fairly effective and popular unit testing tool. This standard module for writing unit tests in Python has been developed for a long time. Unittest is actually a JUnit port with Java (with some modifications specifically for Python). This allows both the module code and the writing of tests to easily trace an object-oriented style, which is very convenient for testing methods and classes. This tool has many features: checking received and expected values (assert), decorators that allow you to skip a separate test (@skip, @skipIf), or mark temporarily broken tests (@expectedFailure) and the list does not end there. Using asserts covers the needs of writing tests quite well. In addition to the basic functionality, unittest also provides the following options for improving tests:

  • you can collect tests into groups
  • collect test results (for example, for reporting)
  • OOP style reduces code duplication with similar test objects

In the use of unittest, there are several concepts. Let's look at them in order. Test case: test case is the smallest unit of testing. It checks for a specific response for a specific set of input data. Test suite: test suite is a collection of test cases. It is used to aggregate tests that must be run together. Test fixture: test fixture is the fixed state of the objects used as the source when performing tests. The purpose of using fixture is if you have a complex test case, then preparing the desired state can easily take a lot of resources (for example, you consider a function with certain accuracy and each next sign of accuracy in the calculations takes a day). Using fixture (on slang - fixtures), we skip the preliminary preparation of the state and immediately begin testing. Test fixture can appear, for example, in the form (database state, set of environment variables, set of files with the necessary content). Test runner: test runner is a component that organizes the execution of tests and provides the result to the user. When writing tests in Python, one should proceed from the following principles that are common enough for all programming languages:

  • The test should not depend on the results of other tests.
  • The test should use data specially prepared for it, and no other.
  • The test should not require user input.
  • Tests should not overlap each other (do not write the same tests 20 times). You can write partially overlapping tests.
  • Finding a bug means writing a test.
  • Tests should be maintained in working condition.
  • Unit tests should not check the performance of an entity (class, function).
  • Tests should check not only that the entity is working correctly on the correct data, but also that it appropriately with incorrect data.
  • Tests should be run regularly
Web Solutions

Different libraries and frameworks for unit testing on Python

Unittest

The most popular library for unit testing in Python is unittest. Let’s take a look at a simple example of unit testing in Python and unittest. Other unit test libraries work in the same manner as unittest. Consider the following code example:

import unittest

class TestUM (unittest.TestCase):
    def setUp (self):
        pass

    def tearDown (self):
        pass

    def test_numbers_3_4 (self):
        self.assertEqual (3 * 4, 12 )

    def test_strings_a_3 (self):
        self.assertEqual ('a' * 3, 'aaa')

if __name__ == '__main__':
    unittest.main ()
Different libraries and frameworks for unit testing on Python

This example shows a common template for most tests. There is an inheritance from TestCase, there are two simple tests, as well as an overload of the methods built into TestCase:

  • The def setUp (self) method is called BEFORE each test.
  • The def tearDown (self) method is called AFTER each test.
  • The def test_numbers_3_4() provides test for 3 and 4.
  • The def test_strings_a_3() provides a test for variables a and 3.

A list of similar ready-made functions such as for unittest in Python:

setUp preparation of the test run; called before each test
tearDown called after the test has been run and the result is recorded. The method is launched even in case of an exception in the test body
setUpClass the method is called before all class tests are run
tearDownClass called after running all class tests
setUpModule called before running all module classes
tearDownModule called after running all module tests

PyTest

PyTest

is an open-source Python-based testing framework. It is designed for all-purpose testing and has the capability for Functional and API testing:

  • More pythonic way of writing your tests.
  • Supports simple or complex code to test API, databases, and UIs.
  • Has a Simple python syntax.
  • Number of plugins.
  • Ability to run tests in parallel.
  • Ability to run selected specific subset of tests.

Robot

The popular Robot Framework is an open-source Automation Testing framework based on Python. This framework is developed in Python and also runs on Jython (JVM) and IronPython (.NET). Keyword style is used to write test cases in this framework. The Robot is supporting automation testing on cross-platform (Windows, Mac OS, and Linux) for desktop applications, mobile applications, web applications, etc. The Robot framework is also used for Robotic Process Automation (RPA). The use of tabular data syntax, keyword-driven testing, rich libraries and toolsets, and parallel testing are advantages of this specific testing framework for Python.

Django

Django allows writing automated testing for modern web development. It is possible to use a collection of tests (a test suite) to solve, or avoid, a number of problems:

  • When you're writing new code, you can use tests to validate your code works as expected.
  • When you're refactoring or modifying old code, you can use tests to ensure your changes haven't affected your application's behavior unexpectedly.
  • Testing a Web application is a complex task because a Web application is made of several layers of logic, from HTTP-level request handling, to form validation and processing and to template rendering. With Django's test-execution framework and assorted utilities, it is possible to simulate requests, insert test data, inspect your application's output and generally verify your code is doing what it should be doing.

The preferred way to write tests in Django is using the unittest module built-in to the Python standard library. It is possible also to use any other Python test framework. Django provides an API and tools for that kind of integration.

Flask

Flask provides ample opportunity to test your web application using tests and by processing local context variables. To test the application, the add-on module is usually added as a separate file, and a skeleton is created in it to use the unittest module. The code for the setUp() method creates a new test client and usually initializes a new database. This function is called before each individual test function is executed. To delete the database after the test is completed, you can use the tearDown() method. In addition, during the configuration process, the TESTING configuration flag is activated. This leads to disabling error trapping during the request processing, and error reports appear when executing test requests for the application. Using self.app.get, a request is sent to the HTTP GET application with the specified path. The return value will be a response_class object. Now it is possible to use the appropriate attributes to check the return value as a string. The test client also provides the test_request_context() method, which can be used in conjunction and the “with” statement to temporarily activate the request context.

Conclusion

To conclude this article, it can be said that Python and frameworks that work with this programming language support unit tests very well. The recommendation of Svitla Systems' developers is that in projects on Python and its frameworks it is necessary to use unit testing. We have convinced many customers of the benefits of writing unit tests and they were very grateful to us when our teams could quickly localize the errors and fix them when creating new functionality. It also has a positive effect on the architecture of the project. Regardless of the Python framework that you use, it is necessary to write unit tests to cover the functionality of your system so that it can be successfully maintained in operation for many years.