Text
Tests
1. Unit testing
- It checks that a small piece of code, like a function or class, is expected in isolation from the rest of the system.
- It could break during refactoring or an implementation change.
What makes a good unit test?
- It should clearly explain what behaviour they’re testing.
- It should cover all use cases/edge cases for each public function.
- It should test all public-facing functions and avoid testing private functions. (_<function_name> naming for private functions)
- It should contain as little logic as possible.
- It should not test code outside the scope of the test.
2. Integration testing
- It checks that the different pieces of code work together, maybe several classes, or a subsystem.
3. System (end-to-end) testing
- It checks all of the systems under test in an environment as close to the end-user environment as possible.
4. Functional testing
- It checks a single bit of functionality of a system.
- It should only break if we are intentionally changing the functionality of the system.
5. Subcutaneous testing
- It tests against an interface just below the surface.
Naming conventions
- Test files should be named
test_<something>.py
or<something>_test.py
. • Test methods and functions should be namedtest_<something>
. • Test classes should be namedTest<Something>
.
Output for the run
.
:= passed test function or methodF
:= failuresE
:= errorss
:= skipsx
:= xfailsX
:= xpasses
Testing parameters
pip install pytest
- It will look for tests in the current directory and subdirectories. It looks for files starting with
test_
or ending with_test
. Also, you can specify the python test files or directories.
pytest <test_name.py>
pytest -v <test_name.py>::<test_<function>>
# runs only one test
pytest --collect-only
# like dryrun
pytest -k "<key>"
# lets you use an expression to find what test functions to run
It also can run a set of tests that have a common prefix or suffix in their names
pytest -k "asdict or defaults" --collect-only
# , where asdict and defaults are the words in the function’s name
pytest -v -m "<mark1> or/and/and not <mark2>"
# -m <mark>
marks a subset of your test functions so that they can be run together.
pytest -l <test_name.py>
# If you use the -l/–showlocals option, local variables and their values are displayed
with tracebacks for failing tests.
pytest -x
# exits when it encounters the first fail.
pytest --maxfail=<number_of_fails>
# --maxfail <number_of_fails>
stops after <number_of_fails>
pytest --lf
# last failed
pytest --ff
# first failed
pytest -v
# verbose
pytest -q
# quiet
pytest -r <chars>
# show extra test summary info as specified by chars (f)ailed, (E)error, (s)skipped, (x)failed, (X)passed, (p)passed, (P)passed with output, (a)all except pP.
assert
-
assert
to detect errors and debug easier.- If the condition is
True
, it continues to run. - Else, an
AssertionError
exception is raised with an optional error message.
- If the condition is
-
An assert is no mechanism for handling runtime errors.
-
Asserts can be globally disabled with an interpreter setting.
-
Syntax
assert <expr1> ["," <expr2>]
, where <expr1>
is the condition we test, and the optional <expr2>
is an error message that’s displayed if the assertion fails.
""" assert_stmt ::= "assert" expression1 ["," expression2]`
"""
if not expression1:
raise AssertionError(expression2)
-
Do not use asserts for data validation: Validate with regular if-statements and raise validation exceptions if necessary.
-
Asserts that never fail:
assert (counter == 10, 'It should have counted all the items')
- Helper functions to make test assertions:
- assertEqual
- assertTrue
- assertFalse, etc.
def apply_discount( product, discount ):
price = int(product['price'] * (1.0 - discount))
assert 0 <= price <= product['price']
return price
shoes = {'name': 'Fancy Shoes', 'price':14900}
apply_discount(product=shoes, discount=2.0)
pytest
assert something
assert a == b
assert a <= b
unittest
assertTrue(something)
assertEqual(a, b)
assertLessEqual(a, b)
Built-in fixtures
Using tmpdir and tmpdir_factory
- They create a temporary file system directory before your test runs and remove the directory when your test is finished.
1. tmpdir
- If you’re testing something that reads, writes, or modifies files, you can use
tmpdir
to create files or directories used by a single test. - It has a function scope.
- Any individual test that needs a temporary directory or file just for the single test can use
tmpdir
.
2. tmpdir_factory
- You can use
tmpdir_factory
when you want to set up a directory for many tests. - It has a session scope.
- For fixtures with a scope other than function (class, module, session).
@pytest.fixture
- A test fixture is an environment used to consistently test a piece of software.
- It sets up a system for the software testing process by initializing it, thereby satisfying any preconditions the system may have.
- For each fixture used by a test function there is typically a parameter (named after the fixture) in the test function’s definition.
Parameters
-
Parameter
s are different fromfunction argument
s.- They can be passed as a list to the argument
params
of@pytest.fixture()
decorator. - They must be iterables, such as generators, lists, tuples, sets, etc.
- Pytest consumes such iterables and converts them into a list.
- To use parameters, a fixture must consume a special fixture named
request
. request
containsrequest.param
which contains one element fromparams
.- The fixture is called as many times as the number of elements in the iterable of
params
argument, and the test function is called with values of fixtures the same number of times.
- They can be passed as a list to the argument
-
autouse
indicates that all tests in this file will use the fixture.- The code before
yield
runs before each test. yield
can return data to the test if desired.- The code after
yield
runs after each test.
- The code before
-
tmpdir
Setup
1. In-line setup
- It leads to duplication when multiple tests require the same initial data.
2. Delegate setup
- It places the test fixture in a separate standalone helper method that is accessed by multiple test methods.
3. Implicit setup
- It places the test fixture in a setup method which is used to set up multiple test methods.
- Each test method has its setup procedures and links to an external test fixture.
Marking test functions
- Marked tests from different files can all run together.
- A test can have more than one marker.
- A marker can be on multiple tests.
Mocking
- As code grows eventually, you will need to start adding mocks to your test suite.
unittest.mock
- It is Python’s mock object library. (
pip install mock
) - A mock object (from the class
Mock
) substitutes and imitates a real object within a testing environment. patch()
replaces the real objects in your code withMock
objects.- It can be used as either a context manager or a decorator.
- When you substitute an object in your code, the Mock must look like the real object it is replacing. Otherwise, your code will not be able to use the Mock in place of the original object.
from unittest.mock import Mock
# If you are mocking the json library and your program
# calls dumps(), then your Python mock object must
# also contain dumps().
json = Mock() # patch the json library
json.dumps()
# do_something(mock) # pass mock as an argument to do_something()
Parametrized testing
- A test function can turn into many test cases by running it multiple times with different test data.
pytest.ini
- It is optional.
- It contains a project-wide pytest configuration.
- There should be at most only one in your project.
- It can contain directives that change the behaviour of pytest, such as setting up a list of options that will always be used.
Running a subset of tests
- An expression can be used to match test names.
A single directory
pytest <dir> --tb=no
A single test file/module
pytest <dir>/<test_file_name>.py
A single test function
pytest <dir>/<test_file_name>.py::<function_name>
A single test class
- Test classes are a way to group tests that make sense to be grouped.
pytest <dir>/<test_file_name>.py::<ClassName>
A single test method of a test class
- If you don’t want to run all of a test class, just one method.
pytest <dir>/<test_file_name>.py::<ClassName>::<function_name>
A set of tests based on the test name
k
parameter enables you to pass in an expression to run tests that have certain names specified by the expression as a substring of the test name.
pytest -k <keyword>
pytest -k <keyword> and/or/and not/or not <keyword>
Skipping Tests
Markers
- The
skip
andskipif
markers enable you to skip tests that you don’t want to run.