Hlavní navigace

Testy z aplikace

15. 1. 2013 20:49 Petr Blahoš

Původně jsem chtěl psát o logování, ale to je vlastně tak přímočaré, že to moc nemá cenu. Raději se podívám na to, jak spustit unittesty z programu.

Pravděpodobně máte aplikaci napsanou v nějakém frameworku, třeba pyramid, a pak Vám ten framework nabízí možnost, jak spustit testy. Pyramid používá setuptools a když napíšete v rootu aplikace

python setup.py test
tak se testy provedou. To se sice dá zamontovat do nějakého auto-build/auto-deploy systému, ale ještě to není úplně ono, když chci spouštět ty testy přímo v tom běžícím frameworku. Víte, jak má většina frameworků takový ten reload při změně zdrojáku, tak kromě toho, že se reloadne aplikace, tam chci spustit testy. Jak na to? Samozřejmě můžu spustit další python setup.py test, ale to trvá strašně dlouho. Já raději (a teď mě asi budete mít za blázna) spustím ty testy přímo v kontextu aplikace. Ale jak na to? Dokumentace v pythonu (2.7) k balíku unittest se ale moc nezabývá tím, jak ty testy spouštět programově. Stejně tak dokumentace k nose jako by na tohle zapomněla. Tak se na to podíváme sami.

V dokumentaci najdeme jednoduchý příklad, v němž se testy provedou zavoláním

unittest.main()
Co vlastně unittest.main() dělá? Tohle:
  1. Nahraje testy v aktuálním modulu: loader.defaultTestLoader.lo­adTestFromModule
  2. Spustí testy: tests.runTests
Na začátek dobrý, ale co když máme víc souborů? Co když (jak je zvykem) hledáme testy v adresáři test našeho projektu? To umí funkce unittest.TestLoader.discover a její konkrétní instance unittest.defaultTestLoader.discover, nová v Pythonu 2.7. Ta hledá testy v souborech v adresáři. Tak to bychom měli. Teď už to ale bude chtít trochu struktury.
virtualenv PYSKTESTENV
cd PYSKTESTENV
. ./bin/activate.sh
easy_install pyramid
bin/pcreate PYSKTEST1
cd PYSKTEST1
python setup.py develop

Tohle mi vytvořilo vpodstatě prázdný projekt, který nic neumí. Mě nestačí to, že testy má v souboru pysktest1/tests.py, já chci adresář. Zároveň si kladu otázku, jestli má ten adresář být součástí balíčku, tedy v adresáři pysktest1, nebo jestli má být v rootu aplikace. Je to asi jedno. Já ho dám do rootu aplikace, ze dvou důvodů: Bude jednodušší jej nedistribuovat s binárním balíčkem, a když se budu z testu odkazovat na aplikaci, tak se budu odkazovat absolutníma cestama. Ne import model, ale import pysktest1.model.

# jsme v adresáři PYSKTESTENV/PYSKTEST1
mkdir tests
touch __init__.py
mv pysktest1/tests.py tests/test1.py

Teď musíme editovat setup.py a v něm upravit test_suite=„pysktest1“ na test_suite=„tests“ a bude nám fungovat python setup.py test. Test zatím neprojde, ještě v něm musíme upravit relativní import na absolutní. Jak na to spuštění testu z aplikace? Doplníme si pysktest1/tests/__init__.py právě o spouštěč testů:

import sys
import unittest

class _WritelnDecorator(object):
    """Used to decorate file-like objects with a handy 'writeln' method"""
    def __init__(self,stream):
        self.stream = stream
    def __getattr__(self, attr):
        if attr in ('stream', '__getstate__'):
            raise AttributeError(attr)
        return getattr(self.stream,attr)
    def writeln(self, arg=None):
        if arg:
            self.write(arg)
        self.write('\n') # text-mode streams translate to \r\n if needed

def runtests():
    tests = unittest.defaultTestLoader.discover(".")    #Python 2.7 or better?!?
    sys.stderr.write("UNIT TESTS:")
    res = unittest.TextTestResult(stream=_WritelnDecorator(sys.stderr), descriptions=True, verbosity=1)
    tests.run(res)
    if res.wasSuccessful():
        print "TESTS PASSED"
    else:
        print "SOME TESTS HAVE FAILED"
        res.printErrors()

(nebo tady)

Teď už stačí vpodstatě kdekoliv v aplikaci (např. v pysktest1/__init__.py funkci main udělat:

import tests
tests.runtests()
Funguje, ale má to jednu vadu.

Příště bych se chtěl podívat na tohle:

  • Proč musí být tests až v pysktest1 a ne v rootu
  • Jak to udělat v nose (a proč to vlastně dělat v nose)
  • Kdy se takhle nedá testovat, neboli jak si vlastně předáváme model

Sdílet