Courtesy of Holger Krekel’s lecture
Recently, I had the privilege to attend a multi-day lecture with the author of pytest. While I’ve read the docs several times over now, I still picked up on a few new features and tricks. Here are some cool tips whose existence I’d like to stress.
Pytest Tricks & Tips
You can wrap pytest markers:
def skip_non_mac(func): pytest.mark.skipif(sys.platform != 'darwin', return="Skipping because test only applies to OSX.")(func) return func @skip_non_mac def test_me(): pass
You can use markers to pass in parameters to fixtures.
@pytest.mark.awesomemarker(what_are_awesome="markers!") def test_a(my_fixture): assert True @pytest.fixture() def my_fixture(request): marker = request.node.get_marker("awesomemarker") assert 'markers!' in marker.kwargs['what_are_awesome']
You can use yield fixtures to implement teardown with add finalizer.
@pytest.yield_fixture(scope="module") def a_fixture(): print "setup" yield 42 print "teardown" def test_me(a_fixture): print "testing!" # This is similar to how context managers are used. For example... @contextlib.contextmanager def my_context(): print "setup" yield 42 print "teardown" with my_context() as r: assert r == 42
Markers are Function Attributes
@pytest.mark.mymarker(hello="world") def test_me(): assert test_me.mymarker.kwargs["hello"] == "world"
Using “Assert 0” to Print
def test_me(): value_to_print = "hello world" assert 0, value_to_print
capfdare built-in to pytest and allow you to get the stdout and stderr that pytest captures.
Useful Pytest Options:
pytest --lf- rerun with failures only
pytest --ff- rerun all tests, but failures first
pytest -k- run tests whose names contain a keyword
pytest -l- show locals in tracebacks
pytest -r <chars>- show extra test summary info as specified by chars (f)ailed, (E)error, (s)skipped, (x)failed, (X)passed, (w)pytest-warnings, (a)all.
pytest --durations=N- show N slowest setup/test durations (N=0 for all).
Useful Pytest Pytest.ini options
confcutdir in pytest will restrict conftest.py resolution to a specific directory.
If you want to enforce a limit on markers (to prevent typoes in marker names) you can do the following:
[pytest] addopts = --strict markers = myawesomemarker: This is an explanation of my awesome marker. yourawesomemarker: This is an explanation of your awesome marker.
norecursedirs = .svn _build tmp*will prevent pytest from collecting tests from certain directories.
Useful Pytest Plugins
- pytest-instafail - Will display failure stacktraces as they happen.
- pytest-sugar - Pretty pytest reporting.
- pytest-quickcheck - For easy generating of random typed data.
- pytest-timeout - Enforce a timeout for a test run.
- pytest-catchlog - For capturing and modifying logs.
Generally Useful Python Utilities
- rlcompleter2 - Autocomplete in the python interpreter.
- pdb++ (aka
- pycmd - Comes with a number of useful python cli utilities:
- py.cleanup - Removes .pyc and similar files.
- py.lookup - Find text in recursively found .py files.
- py.countloc - Gives LOCs for lines and testlines.
- py.which - Prints location of an importable package or module.
- py.code - Useful for high-level python introspection of code.
sys.executable- Returns the path to the python interpreter that is currently being used.
- pip-tools - Essential for maintaining pinned python packages.
- execnet - Execution across Python interpreters, versions, platforms, and networks
Pytest DSLs (TODO: Finish this section)
Pytest is all about reducing boilerplate. If your testing consists primarily of parameters, a good option might be to implement your own pytest DSL. A major advantage of this is that non-python programmers can still use this DSL to expand on a suite of data driven tests.
The four main parts of a pytest DSL.
- Use the
pytest_collect_filehook to create a
YamlFile(Collector) nodes for the
YamlFilethen parses out a list of test
YamlItem.runtest()performs the test execution, including raising exceptions.
YamlItem.repr_failure()performs reporting in the event of failures.
Two Major Imports for Python 2⁄3 compliance
from __future__ import unicode_literals- Every string becomes unicode.
from __future__ import print_function- Use print function style