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. $ python Python 2.7.3 (default, Aug 1 2012) >>> import

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

    >>> x = mycode.Foo(3, 4) >>> x.work(5) 30 5 / 58
  3. $ 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
  4. >>> def f(): ... x = mycode.Foo(3, 4) ... return

    x.work(5) ... >>> f() 30 >>> # now add x.work(15) to f()? 8 / 58
  5. $ cat test_mycode.py import mycode x = mycode.Foo(3, 4) print

    x.work(5) $ python test_mycode.py 60 9 / 58
  6. $ # (go off and edit the code) $ python

    test_mycode.py 30 $ # (go edit test_mycode.py) $ python test_mycode.py 30 30 10 / 58
  7. $ 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
  8. $ 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
  9. _________________ 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
  10. ________________ 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
  11. # 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
  12. 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
  13. # (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
  14. 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
  15. # 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
  16. ____________ test_ehlo _____________ smtp = <smtplib.SMTP instance at 0x236cab8> 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
  17. # 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
  18. # 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
  19. # 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
  20. 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
  21. Pure functions + clear, stable semantics > External (⇒ stable)

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

    APIs Internal APIs (respect encapsulation) consider disposable test (:/) 44 / 58