
Hey. In this series of posts, I will try to tell you about testing python code, in particular, django projects. We will consider unit testing (unit tests), static code analysis and some of the pitfalls of website testing.
We omit the introductory part about the benefits of testing - the code covered with tests becomes soft and silky, only lazy has not yet read / written about it.
unittest
A standard module for implementing unit tests (unittest, previously pyunit) appeared in version 2.1 of python and was a JUnit port with Java (even the naming of methods was left by camelCase, contrary to
pep8 ). In python 2.7 (3.2), many new interesting things were added to unittest: additional checks (
assert*
), decorators to skip a single test (
@skip
,
@skipIf
) or identify broken tests that the developer knows about (
@expectedFailure
), the way run from the command line. There is also a port of these changes for Python 2.4 and above, called
unittest2 .
How this thing works. Suppose we have a non-humanly complex module, run_once.py:
')
def run_once(f): """ . . """ def _f(*args, **kwargs): if not hasattr(_f, "_retval"): _f._retval = f(*args, **kwargs) return _f._retval return _f
(This is the decorator, which stores the result of the first call to the parameter function and always returns the saved value.)The test of our module may look something like this:
import unittest class Test(unittest.TestCase): def test_run_once(self): @run_once def inc(n): return n + 1
The last two lines are a trigger that will find and run all the tests in this module. Now in the console we can execute
$ python run_once.py
And observe the test result:
. --- Ran 1 test in 0.000s OK
doctest
Since unit tests today will surprise no one, I will show one thing specific to python - the doctests. It’s really easier to show than to explain:
def run_once(f): """ >>> @run_once ... def foo(n): return n + 1 >>> foo(7) 8 >>> foo(0) 8 """ def _f(*args, **kwargs): if not hasattr(_f, "_retval"): _f._retval = f(*args, **kwargs) return _f._retval return _f if __name__ == "__main__": import doctest doctest.testmod()
Key to start:
$ python run_once2.py -v Trying: @run_once def foo(n): return n + 1 Expecting nothing ok Trying: foo(7) Expecting: 8 ok Trying: foo(0) Expecting: 8 ok 1 items had no tests: __main__ 1 items passed all tests: 3 tests in __main__.run_once 3 tests in 2 items. 3 passed and 0 failed. Test passed.
We see that the docstring functions turned into sample code that is equally understandable (hopefully) to both the developer and the interpreter.
Compared to classic unit tests, doctoral tests have both advantages (ease of writing, can be copied directly from an interactive python session; documentation always corresponds to the code) and disadvantages (complex code quickly becomes unreadable; the text editor does not highlight such code, but static the analyzer will not find any errors in it). However, nothing prevents the use of pre-tests for small obvious things (as in the example), and unit tests for more complex tasks.
py.test
Along with the tools included in the standard python supply, there are alternative tools, for example,
py.test . Installation takes place as usual
easy_install -U pytest
Take the function from the first example. A modified unit test will look something like this:
def test_run_once(): @run_once def inc(n): return n + 1
Go:
$ py.test run_once3.py === test session starts === platform darwin
Key features of py.test (good): no API (in fairness: in exceptional cases, the API is still necessary, but it is very small); assert checks This provides a potential opportunity to run the test even without py.test installed, for example, on the production server after the calculation (you never know). Tests can be designed as classes (in the style of unittest), and simply functions of the type
test_*
.
The lack of an API, however, has a downside: a new developer, having connected to the project, may simply not understand how to run this heap of functions. However, the new developer will be so short-lived, and it is better to document the way to launch tests in any case, out of harm's way.
nose
nose is a tool for running tests through unittest (and doctest, with the key - with
--with-doctest
). It also has its own API, which is optional. Successfully fulfills all the above examples:
$ nosetests * -v --with-doctest test_run_once (run_once.Test) ... ok Doctest: run_once2.run_once ... ok run_once3.test_run_once ... ok --- Ran 3 tests in 0.008s OK
As
Yur4eg suggests (thank you!), Nose automatically collects tests from files of the type
test_*
, smart enough to look at the tests daddy, if any, can measure code coverage (code coverage) using
coverage.py (
--with-coverage
) . You can also run only tests that fell off on the last run (
--failed
).
Behind this otklanivayus. It remains only to attach the
source code of examples , three pieces. Public domain.
In the next issue : regular django testing tools and how to deal with them.