py.test: rapid testing with minimal effort
| Author: |
Holger Krekel, merlinux GmbH |
| event: | 7.7.2008, EuroPython 2008, Vilnius |
What is py.test?
- external testing tool
- automatically collects and executes tests
- minimal boilerplate approach
- per-project customization
- many mature features
Structure of Tutorial (I)
- writing and running tests:
- test functions
- test classes
- generative tests
- test driven development
- setup and teardown test state
- skipping tests
- doctests
Structure of Tutorial (II)
- writing and using extensions:
- conftest.py mechanism
- test collection hooks
- test running hooks
- existing extensions
First: Install py.test
- install "py.test" (see http://pylib.org):
- via "easy_install py"
- via download and "python setup.py install"
- via svn and adding py/bin to system $PATH
Write and run a first test
let's write a "test_ospath1.py":
import os.path, py
def test_abspath():
assert os.path.abspath("/a/b/c") == "/a/b/c"
p = "a/b"
cur = os.path.join(os.getcwd(), "a/b")
assert os.path.abspath("a/b/c") == "/a/b/c"
def test_typeerror():
assert py.test.raises(TypeError, "os.path.abspath()")
XXX check why "os.path.abspath(3)" cannot display source lines correctly
then issue on the command line: py.test test_ospath1.py
Putting tests in Test Classes
test_ospath2.py (one test failing):
import os.path
class TestOSPath:
def test_absolute(self):
assert os.path.abspath("/a/b/c") == "/a/b/c"
def test_relative(self):
p = "a/b"
cur = os.path.join(os.getcwd(), "a/b")
assert os.path.abspath("a/b/c") == cur
observations and notes
- py.test automatically collects test_* functions
- use py.test --collectonly to inspect collection
- you can write tests at module global level
- no need to subclass from py.test
- assertion failures provide detailed info
Generative / Parametrized tests
test_ospath3.py:
class TestOSPath:
def test_gen_absolute(self):
def checkabssame(p1):
print "checking on", repr(p1)
assert os.path.abspath(p1) == p1
for s in "/ /a /a/b c".split():
yield checkabssame, s
def test_gen_relative(self):
for p in "a a/b".split():
p = "a/b"
expected = os.path.join(os.getcwd(), p)
yield lambda x,y: x==y, p, expected
Useful Features
- stdout/stderr is captured per-test
- -l | --showlocals: show local variables in traceback
- --pdb: jump into pdb for failures
- -k KEYWORD: select tests to run
- -x | --exitfirst: stop on first test failure
Test Driven Development - at its best!
- meet py.test --looponfailing:
- does a full test run
- keeps a set of failing tests
- checks for file changes and re-runs failing tests
- if all failures are fixed, re-run the whole
great for refactoring!
Setup and Teardown of test state
hooks at module, class, instance level, example:
import py, os.path
def setup_module(mod):
mod.tmpdir = py.test.ensuretemp(__name__)
class TestOS:
def setup_method(self, method):
self.tmpdir = tmpdir.mkdir(method.__name__)
print "chdir() to ", self.tmpdir
self.oldcwd = self.tmpdir.chdir()
def teardown_method(self, method):
self.oldcwd.chdir()
def test_relative(self):
p = "a/b"
expected = os.path.join(str(self.tmpdir), p)
assert os.path.abspath(p) == expected + "xxx"
Skipping tests
the above tests do not make sense for win32,
you can insert e.g.:
class TestOSPosix:
def setup_class(cls):
if sys.platform == "win32":
py.test.skip("posix platform required")
you can also place such calls to "py.test.skip()" inside test functions
or in other test setup functions.
Doctests
py.test automatically collects test_doc.txt files:
this is an example doc test for the py.test tutorial
>>> x = 3
>>> print x
3
Customizing py.test
write "confest.py" files at project or global level:
integrate new test "items" or "collectors"
add command line options
influence the collection process
conftest.py's are picked up "upwards"
to aid debugging use:
py.test --collectonly
py.test --traceconfig
Important Internal Objects
Session: iterates over (custom) collection tree, executes
tests and reports outcomes.
Collection Nodes ("py.test.collect.*"):
Directory: collect files
Module: collect test Classes and Functions
Class/Instance: collect test methods
DoctestFile: collect doctest of a .txt file
Generator: collect generative tests
*Function*: sets up and executes a python test function
The collection process
- collection process forms a tree:
- leaves: so called "items", e.g. py.test.collect.Function
- nodes: so called "collectors" contain further collectors or items
- collection tree is built iterativels
test collection usually starts from directories or files
dynamic lookup of Collector/Item class from conftest.py files
Example conftest.py: control traversal
a conftest.py to influence collection:
import py
mydir = py.magic.autopath().dirpath()
class Directory(py.test.collect.Directory):
def run(self):
if self.fspath == mydir:
return ["x", "y", "z"]
return super(Directory, self).run()
Example conftest.py: add ReST check support
look into py/doc/conftest.py:
- this produces non-python test items
- py.test --collectonly shows the custom tree
- produces syntax and link checks
- drop the conftest.py file into your own project/doc directory
Example conftest.py: html page generation
http://codespeak.net/svn/user/arigo/hack/misc/htmlconftest
[courtesy Armin Rigo]
Example conftest.py: integrate traditional unittest's
http://johnnydebris.net/svn/projects/py_unittest
[courtesy Guido Wesdorp]
- additionally collect TestCase subclasses from
all test_*.py Module and execute them
- allows to mix xUnit and py style tests
Summary extensions
- conftest.py files are supposed to be "drop in" plugins
- you can extend and modify the collection process
- you can add new (non-python) test items
- if you use the above conftest.py's or have some of your own
please subscribe http://codespeak.net/mailman/listinfo/py-dev
note, however:
- no easy customization of reporting (yet)
- some details will change for py lib 1.0 release
XXX refactor / do extra slides see what could stay / below
recap: main features
- assertions by assert statement, nice debugging
- unittests, generative tests, doctests, rest-tests
- automatic customize collection of tests
- select tests by keyword
- capture stdout/stderr per-test
ad-hoc distribution of tests
py.test --dist sends tests to remote places
or multiple processors
- for each host:
- setup (ssh) connection
- sync local source code to remote place
- trigger running of (isolated) tests
ad-hoc distribution of tests (2)
- synchronise source code "1 -> N" simultaneously
- uses py.execnet:
- can connect e.g. via SSH
- expects plain Python executable on remote side
- no need to pre-install other software remotely
- generated AJAX application async displays test results
- demo
collect info about functions (apigen)
- collect function signatures (via settrace)
- track input values/types, stacktraces, return values
- web page with rich automatically produced information
- TODO: decoupling of collecting info and generating html
doctests
- basically work (automatically collected from text files)
- uses 2.5's doctest module
- needs more fine-grained integration
Unifying Test Reporting
py.test contains two approaches for processing test results:
- "old style": methods invoked on session object
- "new style": reporting events are sent to Reporter object
next:
- use reporting event architecture pervasively
- refactor existing reporting extensions
py.test extensions (conftest)
- all conftest.py files are considered in traversed directories
- can extend/modify testing process in arbitrary ways
- however:
- often requires too much knowledge of internal architecture
- no convention for organising shared test support code
- ergo: introduce mechanism to share test support code / plugins
test networks
- currently model for 1:1 process-to-process
- "A <-> B and B <-> C" connections do not imply "A <-> C"
- fix that!
- then: manage (large) network of test hosts
- select set of test deployment hosts by platform, load
- have modes for testing on several platforms at once
difference to "buildbot" ...
- py.test generally works from developers WC
- no need to first commit and wait for later (nightly) test run
- developer's choice of test setting
- more interactivity
summary development topics
- refactor/unify reporting mechanisms
- introduce shared test support code (plugins)
- improve py.execnet (towards networks)
- interactive debugging / introspection everywhere
- systematically persist test results
Goal
Maximize fun and efficiency of (test-driven) development