Unit Testing in Python

Unit Testing in Python

posted 12 min read

There are two types of developers in the programming world. The first one writes code and walks away, does not watch it run. They assume everything will go according to plan. There is the second type, who is writing unit tests. With Python built-in test modules, you can effectively write code to test your software's modules. It will give you peace of mind that eludes all too many programmers. Most companies and teams require their code to be thoroughly examined. This saves you a lot of time and also gives you confidence that your updates and refactoring don't have any unintended consequences to break your code execution in a way.
So, in this article, I will cover the details of the basic understanding of unit testing, its importance, the key principles and benefits while writing code in Python, how to write tests, its environment setup, and some best practices.

Understanding Unit Testing

Definition of Unit Testing

Unit testing is the practice or software testing framework used for examining the smallest piece of code, referred to as a unit, and testing them individually to check if they are functioning as intended. Additionally, it isolates the smallest piece of testable software from the remainder of the code to determine if it behaves as expected and according to the requirement in operation.

Purpose of Unit Testing

The primary purpose of unit testing is to verify each unit or component of the software individually and whether each functionality is working based on the user's requirements. It encourages the developer to write cleaner and modular code that is independent of other modules. Using unit testing, you can easily maintain the software application over time.

Key Principles of Unit Testing

Unit testing is the initial phase of software testing, where an individual unit or component of the software is tested against the needs and requirements of the user. Several key principles help us to ensure the correct testing mechanism so that each unit performs the expected designed functionality. Here are the principles:

1. Isolation

The first fundamental testing principle is isolation, which means that each unit that has undergone testing should not be independent of other modules. Each functionality is tested separately, like the module having one specific functionality. It ensures that the issue in one module does not affect the working of another module. So, make sure each tested unit is isolated.

2. Independence

Independence is another principle that requires consideration while testing. It means that each unit test is independent of the other unit test. In this way, the test failure of one unit does not impact the execution of another unit. Also, it ensures that the unit testing is done in any order and that one module testing is not waiting for another unit result.

3. Repeatable

This principle is most widely used for automated test suites, where results should be consistent irrespective of how many times we run the unit test. Therefore, the unit test code should be repeatable.

4. Fast

Each unit test should be able to quickly identify the defect when the testing fails. This principle ensures the fastest way of testing the individual unit instead of testing the whole application at once.

Benefits of Unit Testing

1. Early Bug Detection

Unit testing helps us find software bugs early. It is carried out by developers who test individual code prior to integrating multiple modules. Bugs or issues are identified very early and resolved without impacting other pieces of code, thus facilitating early detection of errors in the software.

2. Simplified Debugging

Unit testing also makes debugging easier and quicker. It helps simplify the debugging process, if a test fails only then the latest changes made in the code need to be debugged.

3. Code Documentation

Code documentation mostly shows how little code documentation gets written. Unit testing can make the documentation burden a little easier by encouraging better coding practices and leaving behind the piece of code that describes what your program is going to achieve.

4. Refactoring Confidence

It makes the coding process more agile when you add more and more features to software applications and sometimes need to change the old design and code. However, changing the already-tested code is both risky and extremely costly. If we have a unit test in place, then we can proceed with refactoring very confidently to verify each part of a code is working properly.

Getting Started with Unit Testing in Python

1. Introduction to Python's Built-in Unit Testing Framework: unittest

Python has introduced two main modules for performing unit testing, i.e. unittest and pytest. The unitest module is part of the Python standard library. So, there is no need to pip install it. It is a testing framework that allows us to write and execute unit tests for our Python modules. It has a set of classes and methods that provide us the functionality to write unit tests and execute them, working based on object-oriented concepts.

2. Setting up a project for Unit Testing

Before starting the technical details to understand the unit testing, make sure you installed Python with the VS Code on your machine and also set up the project in the necessary directory. So, let's get started.

2-1. Project Structure

First, create a directory and name it according to your choice. Then, create two subdirectories in it; the first one is named src, used for saving the main source code without unit testing. The second one is named test, where you put the unit test code. So, this is the way to set up the project in Python using the necessary directory structure. Let's say we have the following project structure:

![][1]

2-2. Installation of necessary Libraries

As discussed above, unittest is the testing framework and is part of the Python standard library. So, there is no need for additional installation. Also, just make sure that the python is installed on your computer.

Unit Testing Algorithm

![][2]

Let us see a simple algorithm of unit testing in which you can start by writing test code and adding these tests to the test suit. Then you run the unit test, if the test passes, you add one more test or add to the next level of testing. In case of test failure, you make the necessary changes and execute the unit test again but failed. However, you repeat the cycle and make changes until the code is perfectly working and returns the expected results. On the other hand, after making changes if the test passes, you can add it to the test suite and move on to the next unit test. After completing all the tests and checking the functionality you can stop the testing.

3. Writing your First Unit Test

3-1. Creating Source Code

Before demonstrating the testing, you require the source code upon which unit testing is performed. Suppose you are making a simple calculator that performs basic math operations like addition, subtraction, multiplication, and division. All these operations are written in the file named calculator.py, which is placed in the src directory. Let's move towards the coding part.

print("SIMPLE CALCULATOR") print("Having operation '+', '-', '*' and '/'") class calculator: def addition(num1, num2): #function for addition num3 = num1 + num2 return num3 def subtraction(num1, num2): #function for subtraction num3 = num1 - num2 return num3 def multiplication(num1, num2): #function for multiplication return num1 * num2 def division(num1, num2): #function for division num3 = num1 / num2 return num3 result1 = addition(10, 5) result2 = multiplication(5, 5) print("Addition of two numbers:" , result1) print("Multiplication of two numbers:" , result2)

3-2. Writing Test Methods

Now, we want to test the methods inside the above code. For that, we need to import the unittest module, then required to create a test method which is based on the base class TestCase containing functions for performing unit testing.

import unittest #importing the module from src_code.calculation import calculator # importing the function form the main class class Testing(unittest.TestCase): #used the base class for creating testing subclass def test_addition(self): #create the test function self.assertEqual(calculator.addition(5,5), 10) #takes the values and its answer and check its correctness self.assertEqual(calculator.addition(6,7), 13) def test_multiplication(self): #another function self.assertEqual(calculator.multiplication(5,5), 25) #takes the values and its answer and check its correctness self.assertEqual(calculator.multiplication(4,2),8) if __name__ == '__main__': #used to class the main class of unittest unittest.main()

In the above code, addition() and multiplication() functions of calculator.py are tested. To achieve this, we have to do the following:

  1. Import the unittest library and the calculator class from calculator.py.
  2. Then, create the testing method for both addition() and multiplication() methods using the calculator instance.
  3. Use the assertEqual function that checks that the given values output or the expected output should be the same and equal.

3-3. Running Tests

For running the unit test, we use unittest.main(), which will execute the test when the program runs. It will displays the output when the program tested successfully but, if the program fails, the compiler will highlight the error in the terminal and show that the testing failed.

![][3]

If we change the excepted output values of the multiplication() function, then testing fails and this will be displayed in the terminal:

![][4]
Note: Remember, it is the convention that all the testing methods should start with the keyword test_ because this way unittest can easily identify the method that needs to be executed.

Anatomy of a Unit Test

1. Test fixture setup and teardown

After understanding the basics of the unittest module, we move towards the more advanced concepts of the testing framework used for optimizing the code. Test fixture setup and teardown are widely used for initializing and cleaning up the task before and after the test execution. Suppose we create a class called employee having all employee details. Now, we want to test the emails and payment increment of each employee, for that, we have to write the test code for each employee, and it's hectic. Also, it creates redundant code repeatedly. So, to resolve this problem, I am going to use two components in the unittest modules used for optimizing the code.

1-1. setUp() method

The setUp() method will run its code before every single test is executed. It is helpful when you have to perform the same task again and again in each test function. Therefore, placing that piece of code in the setUp() function will help you in redundancy reduction and optimization. Let's move towards the coding part for better understanding.

employee.py class Employee: #employee class increment_amt = 1.05 #variable with increment value def __init__(self, name, pay): #function for taking values self.name = name self.pay = pay @property def email(self): #email function return '{}@gmail.com'.format(self.name) def increment(self): #function of incremeting the payment self.pay = int(self.pay * self.increment_amt) emp_testing.py import unittest #importing the module from src_code.employee import Employee class Testing(unittest.TestCase): #used the base class for creating testing subclass def setUp(self): #setup the resource before each execution self.emp1 = Employee('john', 50000) self.emp2 = Employee('stephen', 60000) def test_email(self): self.assertEqual(self.emp1.email, '*Emails are not allowed*') self.assertEqual(self.emp2.email, '*Emails are not allowed*') def test_increment(self): self.emp1.increment() self.emp2.increment() self.assertEqual(self.emp1.pay, 52500) self.assertEqual(self.emp2.pay, 63000) if __name__ == '__main__': #used to class the main class of unittest unittest.main() ![][5]

1-2. tearDown() method

The tearDown() will run its code after the test execution. It is for releasing the resources after using it like database connection or some cleanup activities.

def setUp(self): #setup the resource before each execution self.emp1 = Employee('john', 50000) self.emp2 = Employee('stephen', 60000) def tearDown(self): #deleting the employee del self.emp1

2. Writing Assertions

2-1. Using Assert Statements

In Python, the assert is the built-in statement used for checking the conditions. If the condition is true, it will not show anything but in case of error or the false condition, it will raise the error. Most assert statements are considered as exceptions and errors, but developers prefer their utilization for testing and debugging purposes.

n = 1 assert 1 < n, 'This condition is false' #checks and print only if its false Output ![][6]

2-2. Common Assert Methods

As we discussed the assert statement above, there is no need to use it separately in the program. The unittest module contains the base class TesCase and provides all the assert functions that work exactly like assert statements without detailed coding. These methods are used within the test method to check whether the resulting output matches the excepted output. Here, I will discuss the common assert methods provided by the TestCase class of unittest.

![][7]
Tip: Always try to avoid testing the external dependencies like databases, files, or network connections because they slow down tests and make them brittle.
FAQs Q: What is the unittest.TestCase in the unittest module?
A: unittest.TestCase is the base class of the unittest module that serves as a container for all individual test
Q: When should I start writing the test cases?
A: The best approach is to write when you are developing the application because it ensures that application is free of bugs from the early stage.

Wrapping Up

To sum up, we discussed the basics of unit testing and got to know its importance in the development and maintenance of software. We also learn the unit testing techniques by using the coding examples. It shows that unit testing is a very crucial phase before other testing. It also ensures that the individual module is error-prone and modular. Developer needs to understand that the use of unit testing during the application developement will save most of their time and ensure the quality of software. I hope this guide is helpful for you. Also, if you have any questions or concerns, then feel free to ask and give feedback. Thank you for following this guide. Happy Coding!


Reference

Unit Testing [1]: https://logiclair.org/?qa=blob&qa_blobid=16541390965964414061 [2]: https://logiclair.org/?qa=blob&qa_blobid=4202928536661986523 [3]: https://logiclair.org/?qa=blob&qa_blobid=7841557182954355083 [4]: https://logiclair.org/?qa=blob&qa_blobid=9280295931013992206 [5]: https://logiclair.org/?qa=blob&qa_blobid=1643797813287049192 [6]: https://logiclair.org/?qa=blob&qa_blobid=3774790010506432385 [7]: https://logiclair.org/?qa=blob&qa_blobid=12721698954172678740
If you read this far, tweet to the author to show them you care. Tweet a Thanks

More Posts

Testing in Python: Writing Test Cases with unittest

Abdul Daim - Apr 8, 2024

Tkinter library in Python

Tejas Vaij - Mar 12, 2024

How to use Builder design Pattern for test data generation in automation testing

Faisal khatri - Oct 14, 2024

Build a Discord python assistant with plain Langchain

Astra Bertelli - Jun 1, 2024

Leveraging Python for System Administration

Tejas Vaij - May 1, 2024
chevron_left