Workshop of code testing
-
unittest (stdlib)
-
coverage >= 6.0
-
cProfile (stdlib)
-
timeit (stdlib)
-
tempfile (stdlib)
-
pytest >= 7.0
-
pytest-cov >= 3.0
-
pytest-mock >= 3.7
It is a standard library for python, and its version will depend on your python instalation.
import unittest
class DefaultWidgetSizeTestCase(unittest.TestCase):
def test_default_widget_size(self):
widget = Widget('The widget')
self.assertEqual(widget.size(), (50, 50))
Its is an alternative library, backwards compatible with unittest as well as adding a new design philosophy to tests.
def inc(x):
return x + 1
def test_answer():
assert inc(3) == 5
More about Fixtures, specialy how to do Teardowns and Cleanups.
import unittest
class WidgetTestCase(unittest.TestCase):
def setUp(self):
self.widget = Widget('The widget')
def test_default_widget_size(self):
self.assertEqual(self.widget.size(), (50,50),
'incorrect default size')
# Functional test fixtures
@pytest.fixture
def fruit_bowl():
return [Fruit("apple"), Fruit("banana")]
def test_fruit_salad(fruit_bowl):
# Act
fruit_salad = FruitSalad(*fruit_bowl)
# Assert
assert all(fruit.cubed for fruit in fruit_salad.fruit)
# Test classes fixtures, through the use of scope, this can also be made available through whole modules, packages or session
@pytest.fixture(scope='class')
def input(request):
request.cls.varA = 1
request.cls.varB = 2
request.cls.varC = 3
request.cls.modified_varA = 2
@pytest.mark.usefixtures('input')
class TestClass:
def test_1(self):
sum(self.varA, self.varB)
def test_2(self):
sum(self.varC, self.modified_varA)
import unittest
class WidgetTestCase(unittest.TestCase):
def setUp(self):
self.widget = Widget('The widget')
def tearDown(self):
self.widget.dispose()
def test_default_widget_size(self):
self.assertEqual(self.widget.size(), (50,50),
'incorrect default size')
import pytest
@pytest.fixture()
def resource():
print("setup")
yield "resource"
print("teardown")
class TestResource:
def test_that_depends_on_resource(self, resource):
print("testing {}".format(resource))
testdata = [
(datetime(2001, 12, 12), datetime(2001, 12, 11), timedelta(1)),
(datetime(2001, 12, 11), datetime(2001, 12, 12), timedelta(-1)),
]
@pytest.mark.parametrize("a,b,expected", testdata)
def test_timedistance_v0(a, b, expected):
diff = a - b
assert diff == expected
import tempfile
# create a temporary file and write some data to it
fp = tempfile.TemporaryFile()
fp.write(b'Hello world!')
# read data from file
fp.seek(0)
fp.read()
# close the file, it will be removed
fp.close()
# create a temporary file using a context manager
with tempfile.TemporaryFile() as fp:
fp.write(b'Hello world!')
fp.seek(0)
fp.read()
# file is now closed and removed
# create a temporary directory using the context manager
with tempfile.TemporaryDirectory() as tmpdirname:
print('created temporary directory', tmpdirname)
# directory and contents have been removed
import unittest
from pathlib import Path
import tempfile
class TestCase(unittest.TestCase):
def setUp(self):
self.temp_folder = tempfile.TemporaryDirectory()
self.working_dir = Path(self.temp_dir.name)
def tearDown(self):
self.temp_folder.cleanup()
# content of test_tmpdir.py
def test_create_file(tmpdir):
p = tmpdir.mkdir("sub").join("hello.txt")
p.write("content")
assert p.read() == "content"
assert len(tmpdir.listdir()) == 1
assert 0
import unittest
def whatever(i):
return i/0
class TestWhatEver(unittest.TestCase):
def test_whatever(self):
with self.assertRaises(ZeroDivisionError):
whatever(3)
def test_whatever(self):
self.assertRaises(ZeroDivisionError, div, 3,0)
def test_raises():
with pytest.raises(Exception) as exc_info:
raise Exception('some info')
# these asserts are identical; you can use either one
assert exc_info.value.args[0] == 'some info'
assert str(exc_info.value) == 'some info'
def test_another_raises():
with pytest.raises(ValueError, match='must be 0 or None'):
raise ValueError('value must be 0 or None')
def test_the_second_another_raises():
with pytest.raises(ValueError, match=r'must be \d+$'):
raise ValueError('value must be 42')
Tests can be caracterized by their scope. They can test individual functions and methods and other structures, can test the interaction of many, or enven full use case scenarios.
The appropriate coverage of the test with different kinds of tests, guarantes that from its small building blocks, to larger interoperable blocks and usecases, the expectations regarding inputs and outputs are validated.
- Example - Testing IO functions
- Example - Testing Data Structures
- Example - Testing Mocked SeqIO BLAST
- Example - Testing Uniprot API call
import unittest
from unittest.mock import patch, Mock, MagicMock
from tmp import my_module
class MyClassTestCase(unittest.TestCase):
def test_create_class_call_method(self):
# Create a mock to return for MyClass.
m = MagicMock()
# Patch my_method's return value.
m.my_method = Mock(return_value=2)
# Patch MyClass. Here, we could use autospec=True for more
# complex classes.
with patch('tmp.my_module.MyClass', return_value=m) as p:
value = my_module.create_class_call_method()
# Method should be called once.
p.assert_called_once()
# In the original my_method, we would get a return value of 1.
# However, if we successfully patched it, we'll get a return
# value of 2.
self.assertEqual(value, 2)
if __name__ == '__main__':
unittest.main()
from stackoverflow
import os
class UnixFS:
@staticmethod
def rm(filename):
os.remove(filename)
def test_unix_fs(mocker):
mocker.patch('os.remove')
UnixFS.rm('file')
os.remove.assert_called_once_with('file')
Coverage https://coverage.readthedocs.io/en/6.4/
coverage run -m unittest test_arg1.py test_arg2.py test_arg3.py
coverage run -m pytest test_arg1.py test_arg2.py test_arg3.py
pytest --cov=src/package tests/
-------------------- coverage: ... ---------------------
Name Stmts Miss Cover
----------------------------------------
myproj/__init__ 2 0 100%
myproj/myproj 257 13 94%
myproj/feature4286 94 7 92%
----------------------------------------
TOTAL 353 20 94%
import cProfile
import re
cProfile.run('re.compile("foo|bar")')
197 function calls (192 primitive calls) in 0.002 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.001 0.001 <string>:1(<module>)
1 0.000 0.000 0.001 0.001 re.py:212(compile)
1 0.000 0.000 0.001 0.001 re.py:268(_compile)
1 0.000 0.000 0.000 0.000 sre_compile.py:172(_compile_charset)
1 0.000 0.000 0.000 0.000 sre_compile.py:201(_optimize_charset)
4 0.000 0.000 0.000 0.000 sre_compile.py:25(_identityfunction)
3/1 0.000 0.000 0.000 0.000 sre_compile.py:33(_compile)
python -m cProfile [-o output_file] [-s sort_order] (-m module | myscript.py)
python -m timeit [-n N] [-r N] [-u U] [-s S] [-h] [statement ...]