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:
- We use the
unittest
module for testing, which provides similar functionality to Go’stesting
package. - Test functions are methods in a class that inherits from
unittest.TestCase
, and their names start withtest_
. - We use
self.assertEqual
for assertions, which is similar to Go’st.Errorf
. - Python doesn’t have built-in subtests, but we can use
self.subTest
for a similar effect in table-driven tests. - For benchmarking, we use the
timeit
module, which allows us to measure the execution time of small bits of Python code. - 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.