Testing And Benchmarking in Python

Here’s the translation of the Go testing and benchmarking example to Python, formatted in Markdown suitable for Hugo:

Unit testing is an important part of writing principled Python programs. The unittest module provides the tools we need to write unit tests, and we can run tests using the python -m unittest command.

For the sake of demonstration, this code is in a single file, but it could be split into separate files. Testing code typically lives in the same directory as the code it tests, often in a file with a name like test_*.py.

import unittest

# We'll be testing this simple implementation of an
# integer minimum. Typically, the code we're testing
# would be in a source file named something like
# `intutils.py`, and the test file for it would then
# be named `test_intutils.py`.

def int_min(a, b):
    if a < b:
        return a
    return b

# A test is created by writing a class that inherits from
# unittest.TestCase with methods beginning with `test_`.

class TestIntMin(unittest.TestCase):
    def test_int_min_basic(self):
        ans = int_min(2, -2)
        # self.assert* methods will report test failures
        self.assertEqual(ans, -2, "int_min(2, -2) = %d; want -2" % ans)

    # Writing tests can be repetitive, so it's idiomatic to
    # use a data-driven style, where test inputs and
    # expected outputs are listed in a data structure and a single
    # test method walks over them and performs the test logic.

    def test_int_min_table_driven(self):
        tests = [
            {'a': 0, 'b': 1, 'want': 0},
            {'a': 1, 'b': 0, 'want': 0},
            {'a': 2, 'b': -2, 'want': -2},
            {'a': 0, 'b': -1, 'want': -1},
            {'a': -1, 'b': 0, 'want': -1},
        ]

        for test in tests:
            # unittest doesn't have built-in subtests, but we can
            # achieve a similar effect with this pattern
            with self.subTest(a=test['a'], b=test['b']):
                ans = int_min(test['a'], test['b'])
                self.assertEqual(ans, test['want'], f"got {ans}, want {test['want']}")

# Python doesn't have built-in benchmarking like Go,
# but we can use the `timeit` module for similar purposes.

import timeit

def benchmark_int_min():
    # Typically the benchmark runs a function we're
    # benchmarking multiple times.
    SETUP_CODE = 'from __main__ import int_min'
    TEST_CODE = 'int_min(1, 2)'
    times = timeit.repeat(setup=SETUP_CODE,
                          stmt=TEST_CODE,
                          repeat=3,
                          number=1000000)
    print(f"int_min benchmark: {min(times) / 1000000:.6f} seconds per loop")

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

Run all tests in the current project in verbose mode:

$ python -m unittest -v test_int_min.py
test_int_min_basic (test_int_min.TestIntMin) ... ok
test_int_min_table_driven (test_int_min.TestIntMin) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.001s

OK

Run the benchmark:

$ python test_int_min.py
...
int_min benchmark: 0.000070 seconds per loop

In this Python version:

  1. We use the unittest module for testing, which provides similar functionality to Go’s testing package.
  2. Test functions are methods in a class that inherits from unittest.TestCase, and their names start with test_.
  3. We use self.assertEqual for assertions, which is similar to Go’s t.Errorf.
  4. Python doesn’t have built-in subtests, but we can use self.subTest for a similar effect in table-driven tests.
  5. For benchmarking, we use the timeit module, which allows us to measure the execution time of small bits of Python code.
  6. The benchmark function is separate from the test class and uses timeit.repeat to run the function multiple times and measure its performance.

This example demonstrates how to write and run unit tests and perform simple benchmarking in Python, covering similar concepts to the original Go example.