Introduction
As software developers, we have to write a lot of unit tests for our software. And for the dynamic nature and the ease of writing tests alongside the code, Python can be a viable option for unit testing of our software. So, let's dive into the nitty-gritty of writing unit tests and explore the best practices and techniques to ensure our code's reliability and maintainability.
How to use Python for unit testing?
In python, we generally write unit tests using testing frameworks such as unittest
. These tests validate specific behaviors of individual units, typically by asserting expected outcomes against actual results. The unittest
module is included in the Python's standard library, which provides a framework for organizing and running unit tests and offers its own classes and methods for creating the test cases, running them, and reporting the results.
How to write your first Python testcase?
For this case, we are considering a simple Flask application which is using MongoDB as the database. Now, we will be writing some APIs to interact with the database and we'll be testing them by writing unit testcases for them using the unittest
library!!
Let's first create our application
Let's create the file app.py
. Here, we will connect out app with the database and write our API !!
First, let's import our necessary packages, and connect the MongoDB container named task_manager
with our application:
from flask import Flask, request, jsonify
from pymongo import MongoClient
from flask_cors import CORS
from bson import ObjectId
app = Flask(__name__)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
client = MongoClient('mongodb://localhost:27017/task_manager')
db = client['task_manager']
collection = db['tasks']
Now, with the database being connected, let's write one API for the application:
@app.route('/api/tasks', methods=['POST'])
def create_task():
data = request.get_json()
task = {
'title': data['title'],
'description': data['description']
}
result = collection.insert_one(task)
return jsonify({'message': 'Task created successfully', 'id': str(result.inserted_id)}), 201
With our app being ready, now let's write the unit test case for this API we have just wrote!!
Let's write the testcases
Let's create another file called test_app.py
where we'll write the test case for our application. Now, let's first import the necessary libraries required for the testing purpose:
import unittest
from app import app
from unittest.mock import patch, MagicMock
import json
from bson import ObjectId
Now, let's create our testing class and do the setup along with writing the testcase for our POST
request API:
class TestTaskManager(unittest.TestCase):
# Unit test case for testing the Task Manager API.
def setUp(self):
"""
Set up the test client and mock the collection used in the app.
This method runs before each test.
"""
# Initialize the test client for the app
self.app = app.test_client()
self.app.testing = True
# Create a mock for the collection used in the app
self.collection_mock = MagicMock()
# Patch the collection in the app with the mock
self.patcher = patch('app.collection', self.collection_mock)
self.patcher.start()
def tearDown(self):
"""
Stop the patcher after each test.
This method runs after each test.
"""
# Stop the patcher to clean up after tests
self.patcher.stop()
def test_create_task(self):
"""
Test the task creation endpoint.
"""
# Data to be sent in the POST request
data = {'title': 'Test Task', 'description': 'This is a test task'}
# Make a POST request to create a new task
response = self.app.post('/api/tasks', json=data)
# Assert that the response status code is 201 (Created)
self.assertEqual(response.status_code, 201)
# Assert that the response contains the success message
self.assertIn(b'Task created successfully', response.data)
Now, that out test case has been written, it's time to put it on some test. So, we need to run the command python3 test_app.py
in the terminal to get the result of the test,
which look kind of like this!! And, as we can see that our test has passed successfully.
In case of any failure, we will get the result something like this, when the command is run in the terminal:
This is could happen if there is any problem in the data that is being sent has some flaws or there is any problem with the address.
Now, if we had multiple APIs in our application, we would have to write more test cases to test each one of them!!
How to check the test coverage?
Now, what if we want to check how much code does our written unit test cases cover!? Here comes Keploy, which helps us to easily check our test coverage in some simple steps.
First we need to install Keploy's Python SDK:
pip install keploy pytest
Next, we can create a test file for running Keploy's API tests and we can name the file test_keploy.py
and the contents of the file will be as follows:
from keploy import run, RunOptions
def test_keploy():
try:
options = RunOptions(delay=15, debug=False, port=0)
except ValueError as e:
print(e)
run("python3 -m coverage run -p --data-file=.coverage.keploy python3 app.py", options)
We also need to create a .coveragerc
file to ignore the coverage of the libraries that is calculated. The contents of the file will be as follows:
[run]
omit =
/usr/*
sigterm = true
Now to run our unit test with Keploy, we can run the command given below:
python3 -m coverage run -p --data-file=.coverage.unit -m pytest -s test_keploy.py test_app.py
Now, to combine the coverage from the unit tests, and Keploy's API tests, and then generate the coverage report for the test run we can use these commands:
python3 -m coverage combine
python3 -m coverage report
Best practices for writing test cases in Python
The practices mentioned below are not exclusive to Python, but works for all kinds of unit test cases. But as we are discussing about unit test case writing here, so it's important to mention it here:
Keep Tests Simple and Focused
While writing a unit test, we must focus on a single aspect of functionality, keeping the test cases as simple and easy to understand as possible.
Use Descriptive Test Names
Clear and descriptive test names improve the readability and help other developers/maintainers to understand the purpose of each test case.
Isolate Test Cases
We should avoid dependencies between test cases by isolating the units under test. And for that, we can use techniques such as mocking or dependency injection to replace external dependencies with test doubles.
Common pitfalls to avoid while writing Python test cases
Now that we know about the best practices of writing unit test cases, let's focus on some common mistakes that we must avoid while writing the test cases. These includes:
Testing Implementation Details
Always avoid testing the implementation details, because as far as I've seen, these tests can become prone to breaking when refactoring the code.
Ignoring Edge Cases
Remember to ensure that our unit tests cover edge cases and boundary conditions to validate the robustness of our code under various problematic scenarios.
Conclusion
Writing unit tests is a fundamental aspect of software development, which ensures the code reliability and maintainability. And by following best practices and leveraging advanced techniques, and also embracing tools like Keploy, developers can create robust test suites that validate their code's behavior under different conditions. And, I hope after going through this blog, you will be using Python a lot more to write your test cases!
So well, it's a wrap for now!! Hope you folks have enriched yourself today with lots of known or unknown concepts. I wish you a great day ahead and till then keep learning and keep exploring!!
FAQs
What is the purpose of unit testing?
Unit testing aims to validate the individual units or components of a software application to ensure that they behave as expected, enhancing code reliability and maintainability.
How do I write my first unit test in Python?
To write your first unit test in Python, you'll need to set up a test environment, create a test case class that inherits from
unittest.TestCase
, write test methods within the class to validate specific behaviors, and then run your tests using a test runner.Which tools and libraries are available for unit testing in Python?
Python offers several tools and libraries for unit testing, including pytest, which simplifies test writing and execution, and nose2, an extension of Python's built-in unittest framework with additional features for test discovery and running.