Infosec Matrix

Collection of Best Writeups for HackTheBox, Portswigger, Bug Bounty, TryHackme, OverTheWire…

Best Practices for Writing Unit Tests in Python

Madhu deepak
Infosec Matrix
Published in
4 min readJan 20, 2025

--

image by Real Python

Unit testing is an essential aspect of modern software development. By validating the functionality of individual components, it ensures code reliability, minimizes bugs, and provides developers with the confidence to refactor and extend their applications. Python, with its vast ecosystem of testing libraries, offers powerful tools to write and maintain unit tests. In this article, we’ll explore the best practices for writing efficient, maintainable, and effective unit tests in Python.

1. Understand the Role of Unit Testing

Unit tests are designed to verify the behavior of a single unit of code — usually a function or a method — in isolation. Their primary goals include:

  • Detecting bugs early in the development lifecycle.
  • Ensuring that new changes do not break existing functionality.
  • Providing documentation for expected behavior.

Unit tests should focus on a specific piece of functionality, avoiding dependencies on external systems like databases or APIs.

2. Choose the Right Framework

Python’s built-in unittest framework provides a solid foundation for testing. However, many developers prefer third-party tools like pytest for their simplicity and powerful features.

Key Advantages of pytest:

  • Concise and readable syntax.
  • Advanced features like fixtures, parameterized tests, and plugins.
  • Better support for large test suites.

While unittest is great for beginners, consider pytest for more complex projects.

3. Follow the Arrange-Act-Assert (AAA) Pattern

The AAA pattern helps organize your test cases into three distinct phases:

  • Arrange: Set up the data and environment for the test.
  • Act: Execute the code being tested.
  • Assert: Verify that the code’s behavior matches expectations.

Example:

# Example using pytest

def test_add_numbers():
# Arrange
a = 5
b = 7
# Act
result = a + b
# Assert
assert result == 12

This structure improves readability and consistency.

4. Test Edge Cases

While testing standard use cases is important, edge cases often expose hidden bugs. Consider scenarios like:

  • Empty inputs (e.g., an empty list or string).
  • Boundary values (e.g., maximum/minimum integers).
  • Invalid inputs (e.g., wrong data types or out-of-range values).

Example:

import pytest

def test_division_by_zero():
with pytest.raises(ZeroDivisionError):
result = 1 / 0

5. Isolate Your Tests

Unit tests should operate in complete isolation, focusing only on the unit under test. Use mocking to replace external dependencies, such as databases or APIs.

Example with Mocking:

from unittest.mock import Mock

def test_external_api_call():
# Arrange
mock_api = Mock()
mock_api.get_data.return_value = {"id": 1, "name": "Test"}
# Act
result = mock_api.get_data()
# Assert
assert result == {"id": 1, "name": "Test"}

6. Write Descriptive Test Names

Clear, descriptive test names improve readability and make it easier to understand the test’s purpose. Use a naming convention that reflects the specific behavior being tested.

Example:

def test_calculate_discount_for_loyal_customer():
discount = calculate_discount(100, customer_type="loyal")
assert discount == 10

7. Keep Tests Small and Independent

Each test should focus on a single piece of functionality. Avoid combining multiple assertions or testing multiple behaviors in one test.

Good Practice:

def test_functionality_a():
assert functionality_a() == expected_result_a

def test_functionality_b():
assert functionality_b() == expected_result_b

8. Use Fixtures to Simplify Setup

Fixtures are reusable components that provide consistent test setups. Python’s pytest makes it easy to create fixtures for commonly used resources.

Example:

import pytest

@pytest.fixture
def sample_data():
return [1, 2, 3, 4]
def test_sum(sample_data):
assert sum(sample_data) == 10

9. Run Tests Frequently

Incorporate testing into your development workflow by running tests frequently. Use tools like pytest-watch to monitor changes and run tests automatically.

Example Command:

ptw  # Automatically runs tests on file changes

10. Aim for Meaningful Test Coverage

While high test coverage is desirable, prioritize quality over quantity. Use tools like coverage.py to measure coverage and focus on testing critical and complex paths in your code.

Example Command:

coverage run -m pytest
coverage report

11. Document Your Tests

Add comments or docstrings to explain complex test scenarios, especially when the logic isn’t straightforward. This helps other developers (or your future self) understand the purpose of each test.

Example:

# Test ensuring VIP customers receive a 20% discount
def test_vip_customer_discount():
...

12. Integrate Tests with CI/CD Pipelines

Automate your tests by integrating them into a Continuous Integration/Continuous Deployment (CI/CD) pipeline. This ensures tests run automatically on every push or pull request.

Example GitHub Action:

name: Run Python Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: '3.9'
- name: Install dependencies
run: pip install pytest
- name: Run tests
run: pytest

Conclusion

Unit testing is a vital practice for ensuring Python code is robust, maintainable, and bug-free. By following these best practices, you’ll not only improve your code quality but also make development faster and less error-prone. Remember, good testing practices are an investment that pays dividends throughout your software’s lifecycle.

--

--

Infosec Matrix
Infosec Matrix

Published in Infosec Matrix

Collection of Best Writeups for HackTheBox, Portswigger, Bug Bounty, TryHackme, OverTheWire, PwnCollege, PicoCTF, and More.

Madhu deepak
Madhu deepak

Written by Madhu deepak

Software Engineer and Developer