Python tutorials > Testing > Unit Testing > How to run tests?

How to run tests?

This tutorial covers various methods to execute unit tests in Python. We'll explore using the unittest module, pytest, and command-line execution. Understanding how to run tests is crucial for ensuring the reliability and correctness of your Python code.

Using the unittest Module and Test Discovery

The unittest module is Python's built-in testing framework. To run tests, you typically define test cases by subclassing unittest.TestCase. Test methods within the class should start with test_. Calling unittest.main() discovers and runs the tests in the current module.

To run the tests, save the code as, for example, test_my_module.py, and then execute it from the command line:

python test_my_module.py

import unittest

class MyTest(unittest.TestCase):
    def test_addition(self):
        self.assertEqual(2 + 2, 4)

if __name__ == '__main__':
    unittest.main()

Running Tests with pytest

pytest is a popular third-party testing framework known for its simplicity and ease of use. With pytest, you don't need to inherit from a specific class. Tests are simply functions whose names start with test_ or are classes starting with Test containing methods starting with test_.

To run tests with pytest, save the code as, for example, test_my_function.py, and then execute it from the command line:

pytest

pytest will automatically discover and run all tests in the current directory and its subdirectories.

Install pytest using pip: pip install pytest

# test_my_function.py

def test_addition():
    assert 2 + 2 == 4

Specifying Test Files or Directories with pytest

You can specify specific test files or directories to run with pytest:

  • Run a specific file: pytest test_my_function.py
  • Run tests in a specific directory: pytest tests/

Running Tests using Test Discovery (unittest)

The unittest module also provides test discovery. This allows you to automatically find and run tests in a directory structure. The command above searches the tests directory for files matching the pattern test_*.py and runs the tests within them.

  • -m unittest: Invokes the unittest module as a script.
  • discover: Tells unittest to use test discovery.
  • -s tests: Specifies the directory to start searching from (tests in this case).
  • -p 'test_*.py': Specifies the pattern to match test files (test_*.py).

python -m unittest discover -s tests -p 'test_*.py'

Concepts Behind Running Tests

Running tests involves several key concepts:

  • Test Discovery: The process of automatically finding test files and test cases.
  • Test Runner: The tool (e.g., unittest.main(), pytest) that executes the tests and reports the results.
  • Test Suite: A collection of test cases that are run together.
  • Assertions: Statements within test methods that check for expected results (e.g., self.assertEqual(), assert).

Real-Life Use Case

Imagine you're developing a library for mathematical calculations. You would create a tests directory containing files like test_addition.py, test_subtraction.py, etc. Each file would contain test cases for the corresponding functions. You can then run all tests using pytest or python -m unittest discover to ensure that all functions are working correctly after any code changes. This prevents regressions and ensures the library's reliability.

Best Practices

  • Keep tests independent: Each test should be independent of others to avoid cascading failures.
  • Write clear and descriptive test names: Test names should clearly indicate what is being tested.
  • Use assertions effectively: Choose the appropriate assertion method for each test.
  • Run tests frequently: Integrate testing into your development workflow.
  • Arrange-Act-Assert: Structure your tests using the Arrange-Act-Assert pattern for clarity.

Interview Tip

Be prepared to discuss different testing frameworks (unittest, pytest), their advantages and disadvantages, and when to use each. Also, be ready to explain the importance of writing good unit tests and how they contribute to code quality and maintainability.

When to Use unittest vs. pytest

Use unittest when you need a testing framework that's built into Python and don't want to install external dependencies. Use pytest when you want a more feature-rich and easier-to-use framework, especially for larger projects with more complex testing needs. pytest is often preferred for its automatic test discovery and simplified syntax.

Alternatives

Other Python testing frameworks include:

  • nose and nose2: Older alternatives to pytest.
  • doctest: Allows you to embed tests directly within docstrings.
  • tox: A tool for automating testing in multiple Python environments.

Pros of Unit Testing

  • Early bug detection: Finds bugs early in the development cycle.
  • Improved code quality: Encourages writing modular and testable code.
  • Regression prevention: Ensures that new changes don't break existing functionality.
  • Code documentation: Tests serve as a form of documentation.

Cons of Unit Testing

  • Time investment: Writing unit tests takes time and effort.
  • Maintenance overhead: Tests need to be updated when the code changes.
  • Can't catch all bugs: Unit tests only test individual units of code and may not catch integration or system-level bugs.

FAQ

  • Why are my tests not being discovered?

    Ensure that your test files and test methods follow the naming conventions of the testing framework you're using (e.g., test_*.py, test_*). Also, check that the test files are in the correct directory and that the test runner is configured to search that directory.

  • How do I run tests in a specific order?

    The order in which tests are run is generally not guaranteed. If you need to ensure a specific order, you should refactor your tests to be independent of each other. If that's not possible, some testing frameworks provide mechanisms for specifying test order, but this should be used sparingly.

  • How do I skip a test?

    In unittest, you can use the @unittest.skip decorator. In pytest, you can use the @pytest.mark.skip decorator.

    Example (unittest):

    @unittest.skip("Skipping this test because it's not yet implemented")
    def test_something():
        assert True

    Example (pytest):

    import pytest
    
    @pytest.mark.skip(reason="Skipping this test because it's not yet implemented")
    def test_something():
        assert True