Crivens!

turbogears, nosetests und sqlalchemy.migrate

Mittwoch, 05. Mai 2010, Themen:

Ich habe heute mir halb den Kopf zerbrochen, wie ich bei TurboGears Tests anhand von Daten in einer Datenbank durchführen kann.

Das erste Problem ist eigentlich keines, aber es hat doch lange gedauert, bis ich darauf kam: Wie lade ich Test-Daten in eine separate Test-Datenbank?

Überall stand einfach nur, dass man zum Start der Anwendung benötigte Daten in websetup.py ablegen sollte. Mein Denkfehler war einfach nur, dass nosetests die SQL-Einstellungen von test.ini verwendet und nicht von developement.ini. Logisch! Aber hat bei mir etwas gedauert…

So, musste also einfach in der test.ini den Wert für sqlalchemy.url anpassen. Voreingestellt ist eine Memory-Datenbank über SQLite. Da ich jedoch Gebrauch von (MySQL)-spezifischen Enum-Feldern mache, musste ich diese ändern.

Das zweite Problem war die Integration von Sqlalchemy.migrate, welches ich zur Entwicklung der Datenbank verwende. Zuersteinmal habe ich die Basisklasse für den Test von Modellen rausgeworfen. Bei jedem Test hätte sich sonst das Datenbankschema komplett neu aufgebaut, Daten importiert und nach dem Test wieder die Datenbank zerstört. Und nein, einfach nur alle Modelle zu löschen geht bei mir nicht so einfach, da es einige zirkuläre Fremdschlüsselbeziehungen gibt.

Da nosetest verwendet wird, gibt es die Möglichkeit, in einem test-package für alle Tests verwendete setup und teardown Funktionen bereitzustellen.

Die Setup-Funktion bringt zuerst die Datenbank auf die aktuelle Version und lädt dann über websetup.py die Testdaten. Die Teardown-Funktion löscht wieder alle Tabellen und somit alle Daten. Das schöne daran ist, dass dies nur einmal pro Testreihe erfolgt.

Und falls ich doch zwischen den Tests mal wieder alles neu brauche, reicht ein einfacher Aufruf der restart() Funktion.

Als drittes sollte nun noch in TestController der Aufruf von websetup.py unterbunden werden. Haben wir ja schon :-)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
"""custom unit and functional test suite """

import os
from tg import config
from paste.deploy import loadapp
from paste.script.appinstall import SetupCommand
from routes import url_for
import migrate.versioning.api

from your_tg_app.model import DBSession

conf_dir = config.here
sqlalchemy_url = config['app_conf']['sqlalchemy.url']
repository = os.path.join(conf_dir, "migration")

def setup_package():
    """ loads the database fixture in websetup.py """
    # get the directory of the config file
    # setup the database
    migrate.versioning.api.upgrade(sqlalchemy_url, repository)
    # setup the data
    test_file = os.path.join(conf_dir, 'test.ini')
    cmd = SetupCommand('setup-app')
    cmd.run([test_file])

def teardown_package():
    """ removes all data in the database by downgrading via migrate """
    migrate.versioning.api.downgrade(sqlalchemy_url, repository, 0)

def restart():
    teardown_package()
    setup_package()

class TestController(object):
    """
    Base functional test case for the controllers.

    The delphi2 application instance (``self.app``) set up in this test 
    case (and descendants) has authentication disabled, so that developers can
    test the protected areas independently of the :mod:`repoze.who` plugins
    used initially. This way, authentication can be tested once and separately.

    Check delphi2.tests.functional.test_authentication for the repoze.who
    integration tests.

    This is the officially supported way to test protected areas with
    repoze.who-testutil (http://code.gustavonarea.net/repoze.who-testutil/).

    """

    application_under_test = 'main_without_authn'

    def setUp(self):
        """Method called by nose before running each test"""
        # Loading the application:
        conf_dir = config.here
        wsgiapp = loadapp('config:test.ini#%s' % self.application_under_test,
                          relative_to=conf_dir)
        self.app = TestApp(wsgiapp)
        # database setup done with setup_package()

    def tearDown(self):
        """Method called by nose after running each test"""
        # Cleaning up the database:
        DBSession.rollback()
Kommentare:
blog comments powered by Disqus