Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Unit Testing (in Python)

Greg Price
February 27, 2013

Unit Testing (in Python)

Tests help you write better code and make changes faster with confidence they're right. With the right tools and techniques, they don't have to be a burden. We'll show how to get started quickly with Python's 'unittest' and 'nosetests' libraries, and cover techniques for making your tests test the right thing, be robust to change, and run quickly.

As presented at ConFoo, 2013-02-27.

Greg Price

February 27, 2013
Tweet

More Decks by Greg Price

Other Decks in Programming

Transcript

  1. Unit Testing (in Python)
    Greg Price
    google:greg+price
    Solano Labs
    ConFoo
    2013-02-27
    1 / 58

    View full-size slide

  2. Full disclosure:
    http://tddium.com/
    2 / 58

    View full-size slide

  3. § 1: Just so
    3 / 58

    View full-size slide

  4. $ python
    Python 2.7.3 (default, Aug 1 2012)
    >>> import mycode
    >>> x = mycode.Foo(3, 4)
    >>> x.work(5)
    60
    4 / 58

    View full-size slide

  5. >>> # (go off and edit the code)
    >>> reload(mycode)
    >>> x = mycode.Foo(3, 4)
    >>> x.work(5)
    30
    5 / 58

    View full-size slide

  6. $ python
    Python 2.7.3 (default, Aug 1 2012)
    >>> import mycode
    >>> def f():
    ... x = mycode.Foo(3, 4)
    ... return x.work(5)
    ...
    >>> f()
    60
    6 / 58

    View full-size slide

  7. >>> # (go off and edit the code)
    >>> reload(mycode); f()
    30
    7 / 58

    View full-size slide

  8. >>> def f():
    ... x = mycode.Foo(3, 4)
    ... return x.work(5)
    ...
    >>> f()
    30
    >>> # now add x.work(15) to f()?
    8 / 58

    View full-size slide

  9. $ cat test_mycode.py
    import mycode
    x = mycode.Foo(3, 4)
    print x.work(5)
    $ python test_mycode.py
    60
    9 / 58

    View full-size slide

  10. $ # (go off and edit the code)
    $ python test_mycode.py
    30
    $ # (go edit test_mycode.py)
    $ python test_mycode.py
    30
    30
    10 / 58

    View full-size slide

  11. $ cat test_mycode.py
    import mycode
    def test_work():
    x = mycode.Foo(3, 4)
    assert x.work(5) == 30
    $ py.test -q
    collected 1 items
    .
    1 passed in 0.00 seconds
    11 / 58

    View full-size slide

  12. /§ 1: Just so
    12 / 58

    View full-size slide

  13. § 2: Pytest
    13 / 58

    View full-size slide

  14. unittest
    nosetests
    py.test
    14 / 58

    View full-size slide

  15. py.test
    15 / 58

    View full-size slide

  16. (almost)
    Nothing to learn
    16 / 58

    View full-size slide

  17. $ cat test_mycode.py
    import mycode
    def test_work():
    x = mycode.Foo(3, 4)
    assert x.work(5) == 30
    $ py.test -q
    collected 1 items
    .
    1 passed in 0.00 seconds
    17 / 58

    View full-size slide

  18. $ cat test_minimal.py
    def test_good():
    pass
    def test_bad():
    assert False
    18 / 58

    View full-size slide

  19. def test_str():
    x = ’sham spa ergs’
    assert x == ’ham spam eggs’
    19 / 58

    View full-size slide

  20. _________________ test_str _________________
    def test_str():
    x = ’sham spa ergs’
    > assert x == ’ham spam eggs’
    E assert ’sham spa ergs’ == ’ham spam eggs’
    E - sham spa ergs
    E ? - ^
    E + ham spam eggs
    E ? + ^
    test_diffs.py:7: AssertionError
    20 / 58

    View full-size slide

  21. def test_list():
    assert [1, 2] == [3, 2]
    21 / 58

    View full-size slide

  22. ________________ test_list _________________
    def test_list():
    > assert [1, 2] == [3, 2]
    E assert [1, 2] == [3, 2]
    E At index 0 diff: 1 != 3
    test_diffs.py:11: AssertionError
    22 / 58

    View full-size slide

  23. /§ 2: Pytest
    23 / 58

    View full-size slide

  24. § 3: Abstraction
    24 / 58

    View full-size slide

  25. Tests as data
    Fixtures
    25 / 58

    View full-size slide

  26. Abstraction at top
    Abstraction at bottom
    26 / 58

    View full-size slide

  27. Tests as data /
    Abstraction at top
    27 / 58

    View full-size slide

  28. # From PyPy test suite
    class FfiCallTests(object):
    def _run(self, atypes, avalues):
    # ...
    def fake_call(...):
    # ...
    for avalue in avalues:
    assert rffi.cast(TYPE, data)[0] == avalue
    # ...
    def f():
    # ... fake_call(...)
    f()
    # ... total 58 lines ...
    28 / 58

    View full-size slide

  29. class FfiCallTests(object):
    def _run(self, atypes, rtype, avalues, rvalue):
    cif_description = get_description(atypes, rtype)
    def verify(*args):
    assert args == tuple(avalues)
    return rvalue
    FUNC = lltype.FuncType([lltype.typeOf(avalue) for avalue in avalues],
    lltype.typeOf(rvalue))
    func = lltype.functionptr(FUNC, ’verify’, _callable=verify)
    func_addr = rffi.cast(rffi.VOIDP, func)
    for i in range(len(avalues)):
    cif_description.exchange_args[i] = (i+1) * 16
    cif_description.exchange_result = (len(avalues)+1) * 16
    unroll_avalues = unrolling_iterable(avalues)
    @jit.oopspec("libffi_call(cif_description,func_addr,exchange_buffer)")
    def fake_call(cif_description, func_addr, exchange_buffer):
    ofs = 16
    for avalue in unroll_avalues:
    TYPE = rffi.CArray(lltype.typeOf(avalue))
    data = rffi.ptradd(exchange_buffer, ofs)
    assert rffi.cast(lltype.Ptr(TYPE), data)[0] == avalue
    ofs += 16
    if rvalue is not None:
    write_rvalue = rvalue
    else:
    write_rvalue = 12923 # ignored
    TYPE = rffi.CArray(lltype.typeOf(write_rvalue))
    data = rffi.ptradd(exchange_buffer, ofs)
    rffi.cast(lltype.Ptr(TYPE), data)[0] = write_rvalue
    # (cont’d)
    29 / 58

    View full-size slide

  30. # (cont’d)
    def f():
    exbuf = lltype.malloc(rffi.CCHARP.TO, (len(avalues)+2) * 16,
    flavor=’raw’, zero=True)
    ofs = 16
    for avalue in unroll_avalues:
    TYPE = rffi.CArray(lltype.typeOf(avalue))
    data = rffi.ptradd(exbuf, ofs)
    rffi.cast(lltype.Ptr(TYPE), data)[0] = avalue
    ofs += 16
    fake_call(cif_description, func_addr, exbuf)
    if rvalue is None:
    res = 654321
    else:
    TYPE = rffi.CArray(lltype.typeOf(rvalue))
    data = rffi.ptradd(exbuf, ofs)
    res = rffi.cast(lltype.Ptr(TYPE), data)[0]
    lltype.free(exbuf, flavor=’raw’)
    return res
    res = f()
    assert res == rvalue or (res, rvalue) == (654321, None)
    res = self.interp_operations(f, [])
    assert res == rvalue or (res, rvalue) == (654321, None)
    self.check_operations_history(call_may_force=0,
    call_release_gil=1)
    30 / 58

    View full-size slide

  31. class FfiCallTests(object):
    def _run(self, atypes, avalues):
    # [58 lines]
    def test_simple_call(self):
    self._run([types.signed] * 2, [456, 789])
    def test_simple_call_float(self):
    self._run([types.double] * 2, [4.5, 6.7])
    def test_many_arguments(self):
    for i in [0, 6, 20]:
    self._run([types.signed] * i,
    [-12345*j for j in range(i)])
    31 / 58

    View full-size slide

  32. Fixtures /
    Abstraction at bottom
    32 / 58

    View full-size slide

  33. # example from pytest docs
    import pytest
    @pytest.fixture
    def smtp():
    import smtplib
    return smtplib.SMTP("merlinux.eu")
    def test_ehlo(smtp):
    response, msg = smtp.ehlo()
    assert response == 250
    assert "merlinux" in msg
    assert 0 # for demo purposes
    33 / 58

    View full-size slide

  34. ____________ test_ehlo _____________
    smtp =
    def test_ehlo(smtp):
    response, msg = smtp.ehlo()
    assert response == 250
    assert "merlinux" in msg
    > assert 0 # for demo purposes
    E assert 0
    test_smtpsimple.py:12: AssertionError
    34 / 58

    View full-size slide

  35. # adapted from Topaz, a Ruby interpreter
    # tests/conftest.py
    import pytest
    @pytest.fixture(scope=’session’)
    def space():
    # Building a space is expensive,
    # so create it just once.
    from topaz.objspace import ObjectSpace
    return ObjectSpace()
    35 / 58

    View full-size slide

  36. # Topaz various tests
    def test_addition(self, space):
    w_res = space.execute("return 1 + 2")
    assert space.int_w(w_res) == 3
    def test_lambda(self, space):
    w_res = space.execute("""
    l = lambda { |x| 3 }
    return [l.class, l.lambda?]
    """)
    w_cls, w_lambda = space.listview(w_res)
    assert w_cls is space.w_proc
    assert w_lambda is space.w_true
    36 / 58

    View full-size slide

  37. # Topaz tests/conftest.py
    import pytest
    @pytest.fixture(scope=’session’)
    def cached_space():
    from topaz.objspace import ObjectSpace
    return ObjectSpace()
    @pytest.fixture
    def space(cached_space):
    # Building a space is expensive, so create
    # one once, and then just deepcopy it.
    return copy.deepcopy(cached_space)
    37 / 58

    View full-size slide

  38. /§ 3: Abstraction
    38 / 58

    View full-size slide

  39. § 4: What to Test
    39 / 58

    View full-size slide

  40. Tests are code
    40 / 58

    View full-size slide

  41. webapp$ sloccount spec/
    SLOC Directory SLOC-by-Language
    7709 models ruby=7709
    1128 controllers ruby=1128
    [...]
    Total Physical SLOC = 13,454
    Estimated Person-Months = 36.77
    Total Estimated Cost = $413,938
    41 / 58

    View full-size slide

  42. Cost of tests:
    writing them
    42 / 58

    View full-size slide

  43. Cost of tests:
    maintaining them
    43 / 58

    View full-size slide

  44. Pure functions + clear, stable
    semantics
    44 / 58

    View full-size slide

  45. Pure functions + clear, stable
    semantics
    >
    External (⇒ stable) APIs
    44 / 58

    View full-size slide

  46. Pure functions + clear, stable
    semantics
    >
    External (⇒ stable) APIs
    Internal APIs (respect encapsulation)
    44 / 58

    View full-size slide

  47. Pure functions + clear, stable
    semantics
    >
    External (⇒ stable) APIs
    Internal APIs (respect encapsulation)
    consider disposable test (:/)
    44 / 58

    View full-size slide

  48. Test failure cases too
    45 / 58

    View full-size slide

  49. Test the tests
    46 / 58

    View full-size slide

  50. /§ 4: What to Test
    47 / 58

    View full-size slide

  51. (CC0, “Hans” on Pixabay)
    48 / 58

    View full-size slide

  52. (CC-BY, “soham pablo” on Flickr)
    49 / 58

    View full-size slide

  53. End
    http://pytest.org/
    http://tddium.com/
    50 / 58

    View full-size slide