class: center, middle # TDD with py.test How to do test-driven-development with py.test .footnote[© 2015 by Arnold Krille] --- # TDD the big picture ## why tests ## why test-driven development? --- ## why tests - prevent bugs from recurring - proof a solution works - can check for error-combinations that would otherwise be hard to do - time spent fixing regressions is better spent writing tests in the first place - better for documentation than explicit tutorials or howtos - tests are the first customer --- ## test everything .red[“I don't know what it should look like, I can't start with the test!”] - Tests aren't unchangable. - Writing tests with incomplete knowledge can result in more tests, especially in tests for bad paths. - If you don't know what it should be, why are you starting to write code? --- # Set up the env - python + virtualenv + pip The dream-team of python - *virtualenv*: - create encapsulated python environments - only selected python version and packages available inside - prevent system-environment from cluttering - easy switching between python versions for tests. - *pip*: - package installer for python - download and install packages from pypi.python.org (or its mirrors) --- ## virtualenv - create a virtual environment: ```bash $> virtualenv .venv ``` - activate a virtual environment: ```bash $> . .venv/bin/activate ``` - install requirements with _pip_: ```bash (.venv)$> pip install pytest ``` --- ## the 'requirements.txt' - file to gather required dependancies and versions: ```bash (.venv)$> cat requirements.txt pytest==2.6.4 pytest-xdist==1.11 (.venv)$> ``` - install all requirements with pip: ```bash (.venv)$> pip install -r requirements.txt ``` --- # py.test - [pytest.org](http://pytest.org) - test-framework for python - backward compatible to _unittests_ - better test-discovery - better assertions - better output - nice options - lots of extensions (localserver, coverage, bdd, mock) --- ## finding tests Tests are found by: - files `test_
.py` - classes `class Test
(object):` - functions `def test_
():` --- ## write py.test tests A first test in `test_problem.py`: ```python def test_true(): assert True ``` and run it: ```bash (.venv)$> py.test ========== test session starts ========== platform linux2 -- Python 2.7.6 -- py-1.4.26 -- pytest-2.6.4 plugins: xdist collected 1 items test_problem.py . ========== 1 passed in 0.06 seconds ======= ``` --- ## first test [cont.] Run it continuously and verbose: ```bash (.venv)$> py.test -f -v ``` *`-f` = follow* - Runs all (selected) tests - Watches for changes in the code - When no test failed before, run all tests - When some tests failed before, run only the failed tests - Once all failed tests pass again, run all tests again *`-v` = verbose* - Print the test-names and '.green[PASSED]' / '.red[FAILED]' instead of just '.' / 'F' --- ## make it fail ```bash [...] collected 1 items test_problem.py::test_true FAILED ========== FAILURES ========== __________ test_true __________ def test_true(): x = 5 > assert x == 3 E assert 5 == 3 test_problem.py:4: AssertionError ========== 1 failed in 0.09 seconds ========== ``` --- ## pytest.mark.parameterize Parameters for py.tests have special meaning: they load fixtures (see [pytest.org/latest/fixture.html](http://pytest.org/latest/fixture.html)). Unless given as parameters;-) ```python import pytest @pytest.mark.parametrize( 'input1, input2, equal', [ (1, 1, True), (-1, 1, False) ] ) def test_compare(input1, input2, equal): assert (input1 == input2) == equal ``` --- ## execute parametrize ```bash [...] collected 3 items test_problem.py::test_true PASSED test_problem.py::test_compare[1-1-True] PASSED test_problem.py::test_compare[-1-1-False] PASSED ========== 3 passed in 0.11 seconds ========== ``` --- # hands on - simon says (aka PO) - write tests first --- ## divide by three - part I simon says: ``` When I give it the number 3 Then it should return "Fizz" When I give it the number 6 Then it should return "Fizz" When I give it the number 4 Then it should return "4" ``` Task: - Write as three tests - Implement the function to make the test .green[pass] ;-) - Then write as one parameterized test --- ## divide by three - part II simon says: ``` When I give it a number dividable by 3 Then it should return "Fizz" ``` (Why didn't you say so before?) Task: - Write a test to feed mod3 numbers and check for "Fizz" - Write a test to feed non-mod3 numbers and check for original number - Fix the function to make the tests .green[pass] --- ## divide by five simon says: ``` When I give it a number dividable by 5 Then it should return "Buzz" ``` Task: - write a test to feed mod5 numbers and check for "Buzz" - make the tests .green[pass] - write a test to feed non-mod5 numbers and check for original number - notice anything with 15, 30, 45, 60 and so on? --- # CodeKata Do more stuff with this fizzbuzz problem: - Reimplement without if-statement Useful link(s): - [www.pytest.org](http://www.pytest.org)