Testing in Python: Writing Test Cases with unittest

Testing in Python: Writing Test Cases with unittest

posted 12 min read

After developing the software application, you want to check and verify the functionality of the software before releasing it onto the market. You likely want to ensure its functionality based on the prescribed requirements. So, for that, a software testing framework is used here. Developers create multiple test cases to perform software testing. So, in this article, we will cover the basics of test cases, their purpose, and characteristics along with the techniques of writing good test cases. Also, the ways of organizing and managing the test cases effectively.

Importance of Test Case and unittest Module

Now you are wondering about test cases. In simple terms, it's like a blueprint to ensure the robustness and performance of the software application against diverse scenarios. It is important to write test cases because testing is nothing without it, and they act as an overseer for software quality. It provides a tester, the structured approach, to verify the software and make it error-prone before delivering it to the user. All this can be achieved by using the Python module i.e. unittest.

Understanding Test Cases

1. Definition of Test Cases

A test case is a set of conditions or actions used for performing software testing on the application to verify the expected functionality or features. It helps the testing team verify every part of the software to find bugs or errors beforehand. The test cases are written based on software requirements.

2. Purpose of Test Cases in Software Development

Testing needs to be performed with proper planning, otherwise the tester or the user will not be satisfied with the software working. In test cases, testers design and document all conditions in such a way that it covers all the scenarios before the testing phase. It has several key principles, such as detecting errors at early stages, helping to mitigate the risk of occurrence in the future, and the detailed coverage for testing the software in different environments.

3. Characteristics of Good Test Cases

  1. Clarity: The tester should write test cases in an understandable language, providing clarity to all the stakeholders involved in the testing. They should be clear enough to understand each pre-step to perform the testing.
  2. Completeness: The tester should write test cases concisely and in a structured way that covers all the scenarios that software can undergo. They should cover all the expected requirements and aspects of the software application.
  3. Independence: Each test case should be independent of the other, which means the result of one test case does not affect the outcome of the other. This way, you can better facilitate the bugs in isolation.
  4. Reliability: Each test case should be reliable in verifying software functionalities because it provides extensive coverage for testing the software in diverse circumstances. That makes the test cases more reliable than the unplanned and manual testing.

4. Benefits of Writing Test Cases

  1. Bug Detection: Test cases help us to identify the bugs and defects at the early stages of development. While executing the test cases, the tester can easily identify the loopholes or areas that need to be changed or updated. It ensures the system needs to be tested against multiple input values to get similar outcomes and track the bugs.
  2. Regression Testing: Changes are inevitable and need to be handled carefully without impacting the other functionality. Therefore, detailed test cases allow the software tester to verify the new functionality and assess its impact on other functionalities by the changes.
  3. Documentation: The tester documents the test cases concisely and in a structured manner, having a specific pattern that includes the pre and post-conditions and all other key points needed while testing. That helps the tester to keep a track record of each bug and error to show the software quality to the user.
  4. Collaboration: It also encourages collaboration between the team members while writing and executing the test cases. Not only that, but you can get early feedback from the user by collaborating with them.
Note: Many developers prefer to use an automated CI/CD pipeline to catch the changes done in the application. It will help you in advance with regression testing.

Introduction to the unittest Module

Overview of the unittest Module in Python

unittest is the automated testing library, introduced by Python for performing unit testing. It is the part of Python standard library that allows us to write and execute unit tests for our Python modules. Its working process is derived from OOP, having a set of classes and methods that provide us with the functionality to write tests and execute them.

Features of unittest

The unittest module offers developers many features that enable them to write robust, comprehensive, and maintainable test cases. The following key features are:

  • Test Fixture: In the unittest module, the test fixture represents the need to initialize and clean up the task before and after the test execution.
  • Test Suite: It consists of the collection of test cases having similar tasks.
  • Test Discovery: It is used for finding the test cases within the directory without manual intervention and running them.
  • Test case: It consists of a set of conditions needed to check and verify the working of software functionalities.
  • Test Runner: It is a final component used for running and executing the test and displaying the outcome to the user.

History and Evolution of unittest

The unittest testing module is part of Python standard library. Its idea is from JUnit, which is a Java testing framework. As we know, the JUnit testing module is designed to provide an automated way of testing the individual modules of the Java application. Therefore, that idea is adapted by Python, providing the PyUnit framework which was later evolved by Python 2.7 and added in the standard library as an unittest module for the Python developer.

Advantages of Using unittest for Writing Test Cases

Now that we have seen the history and features of the unittest module, let's see some advantages for writing better test cases.

  1. Firstly, it is a built-in library that contains all the functions and testing methods, so there is no need for separate installation.
  2. The developers familiar with JUnit will easily understand its working, as it is based on the JUnit framework.
  3. It offers a more concise and readable structured manner compared to manual testing, making test cases easier to write and understand.
  4. The unittest module offers simplicity and compatibility, especially with smaller projects or projects having strict and limited environments.

Getting Started with Writing Test Cases

Let's understand the writing and execution of a test case using the unittest module of Python with an example:

1. Setting up a Test Case

Suppose we create a class called employee, having all employee details. Now, we want to test the email pattern and payment increment method of each employee; for that, we need to write the test methods for checking emails as well as for payment of each employee. Let's start by writing the source code:

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)

First, create a directory for your project and navigate to it. Then create the new file with the name, test_employee.py. So, your directory structure will look something like this:

Then, import the unittest library into the test_employee.py file. Also, import the Employee class from the src directory, where the source code of emplyess.py is provided.

import unittest #importing the module
from src_code.employee import Employee

2. Anatomy of a Test Case Class

After that, declare the test class derived from the base class of the unittest module. Then, define the test fixture methods i.e. setup() and teardown() methods containing the code required before and after the execution of test methods.

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 tearDown(self): #deleting the employee 
        del self.emp1 

3. Writing Test Methods

Naming Conventions

The Python unittest module defines the naming convention to start the method name with the prefix test_ so that the unittest module can easily identify the test methods upon execution.

Tip: Always try to give meaningful names to the testing methods that describe the method behavior for clarity.

Test Method Structure

The structure of the test method consists of AAA (Arrange, Act, and Assert). In arrange, you provide the setup values which we have done in the setup() function. The act includes the input and output values and invokes the function. Therefore, writing the test methods consists of the input values, the output values, and the assert condition to verify the outcome.

def test_email(self):  #function names start from test_ prefix
    self.assertEqual(self.emp1.email, '*Emails are not allowed*')  #checks email of employee 1
    self.assertEqual(self.emp2.email, '*Emails are not allowed*')  #check email of employee 2
    
def test_increment(self): #function names start from test_ prefix
    self.emp1.increment()  #call the increment function
    self.emp2.increment() 
    self.assertEqual(self.emp1.pay, 52500) #verfiy the increment value
    self.assertEqual(self.emp2.pay, 63000)

4. Running the Test Case

For running the test case, the test runner function is used at the end of the test_employee.py file.

Output

Organizing Test Cases

Test case management and organization play a crucial role in streamlining the testing process, enhancing productivity, and ensuring comprehensive test coverage. Well-structured test cases will help the QA team to test the specific functionality easily, track the record of test case results, and figure out the bugs promptly. If the test cases are unorganized, then it creates chaos between the team members, leading to inefficient and duplicate efforts.

1. Grouping Test Cases into Test Suites

In software testing, the test suite is the collection of test cases that belong to the same category such as functionalities, scenarios, and features. Each test suite works for a specific aspect. This will help the tester to categorize similar features and execute the multiple test cases parallel effectively.

2. Using test loaders to discover and load test cases

Test loader is one of the functions of the unittest module, used for dynamically loading the test cases from the test suite. An automated process of locating and executing the test cases reduces the need for manual intervention and the risk of human errors. You can use the unittest.TestLoader() function, to discover the test case.

import unittest #importing the module
from src_code.employee import Employee

class email_testing(unittest.TestCase): #testcase 1: for testing the email
    def test_email(self):
        self.emp1 = Employee('john', 50000)
        self.emp2 = Employee('stephen', 60000)
        self.assertEqual(self.emp1.email, '*Emails are not allowed*')
        self.assertEqual(self.emp2.email, '*Emails are not allowed*')

class increment_testing(unittest.TestCase): #testcase 2: for testing the payement increment
    def test_increment(self):
        self.emp1 = Employee('john', 50000)
        self.emp2 = Employee('stephen', 60000)
        self.emp1.increment()
        self.emp2.increment()
        self.assertEqual(self.emp1.pay, 52500)
        self.assertEqual(self.emp2.pay, 63000)

def create_test_suite(): #create suite 
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(email_testing)) #add email testing testcase in the suite
    suite.addTest(unittest.makeSuite(increment_testing)) #add payment testing testcase in the suite
    return suite
        
if __name__ == '__main__': #used to class the main class of unittest
    loader = unittest.TestLoader()#deriving the loader from unittest module
    suite = create_test_suite()
    unittest.TextTestRunner().run(suite)

Output

3. Organizing Test Suites and Test Cases Using Modules and Classes

Organizing the test suite and test cases using modules and classes provides the structures and granular approach for testing the application. The modules define the test cases, which means each module has specific functionality. Whereas, the classes define the test suite, which contains the group of test cases having similar features and scenarios. This approach will help the developer enhance the readability of the code, and promote the reusability of the modules.

Tip: Try to follow a test-driven development approach, this will prepare you to write test cases before the actual development.
FAQs Q: How can I run a test file using the command prompt?
A: You can easily run the file in CMD by using this command "python -m unittest test_file.py".
Q: If I dont use the prefix "test_" my method consider as test method?
A: No, the unittest module doesn't take the method as a testing method if the naming convention is not followed properly.
Q: What is test coverage?
A: Test coverage is the technique that tells us how much application features and code are covered by test cases.

Wrapping Up

In conclusion, we learned that test cases are the essential building blocks of software testing. Most developers use test cases to verify the software application's robustness, system maintenance, and specific features based on the requirements given by the user. Additionally, it helps you evaluate the quality and reliability of your application. Whether you are working on an enterprise-level application or developing a small program, testing is important to ensure every feature works properly, even if it's a hectic process. However, all this becomes easy with the help of Python's built-in testing framework i.e. unittest. So, stay with me for the details of this module. I hope this guide is helpful for you. Happy Coding!


Reference

unittest Module
Test Case

If you read this far, tweet to the author to show them you care. Tweet a Thanks

More Posts

Unit Testing in Python

Abdul Daim - Apr 4

Writing to Files In Python

Abdul Daim - Apr 12

Mastering Reading and Writing CSV Files in Python

Abdul Daim - Mar 27

Mastering Data Visualization with Matplotlib in Python

Muzzamil Abbas - Apr 18

Decorators in Python [With Examples]

aditi-coding - Mar 26
chevron_left