On Oct 11, 2013, at 7:14 PM, Ken Lareau <[email protected]> wrote:

> In the process of trying to find an efficient way to manage a test database
> for a large set of tests for a database library I'm writing that uses 
> SQLAlchemy,
> I came across this page:
> 
>     http://alextechrants.blogspot.fi/2013/08/unit-testing-sqlalchemy-apps.html
> 
> This is definitely what I want to do, with one catch: I already have a session
> management system in place for the library that seems to conflict with the
> sample code given on the webpage, and I'm not having luck reconciling it.

I find the approach on that page a little awkward - it's using new globals for 
no good reason and also the begin_nested() seems strange.   The test itself 
then has a "self.session", so the test itself is using a test-bound session, 
the choice of globals for "connection" and "engine" seems even more weird.

The way this works is:

1. test fixture gets at an engine, from configurational system, locally, 
whereever.

2. test fixture gets a connection, holds onto it locally.

3. test fixture gets a transaction from connection - this is a top level 
transaction, using connection.begin()

4. test fixture then does whatever the test needs to get at a session.  if the 
code being tested relies upon a global registry, it injects the connection.  
below is using a traditional scoped session:

    def setUp(self):
        self.conn = engine.connect()
        self.trans = self.conn.begin()

        from application.model import the_scoped_session
        self.session = the_scoped_session(bind=self.conn)

now above, the test fixture has a hold on "self.session".  but - this is the 
*same* session that's in the registry (the registry here being 
"application.model.the_scoped_session").   if some other code somewhere calls 
upon the_scoped_session(), they get the *same* session.   it's a registry, 
that's the point of it.

if you have some other kind of registration thing in place, you'd need to 
figure out how to load it up with a new Session bound to that local connection.


5. test fixture releases the session:

    def tearDown(self):
        the_scoped_session.remove()
        self.trans.rollback()
        self.conn.close()

so note, we don't have to bother doing anything special with the Session at 
teardown time, we just dump it.   we roll back the transaction that we've 
created externally to it.

an example of running through this is in the docs at:

http://docs.sqlalchemy.org/en/rel_0_8/orm/session.html#joining-a-session-into-an-external-transaction

> 
> and I am able to use Session to manage all my access to the ORM.
> My attempt to modify the code on the webpage currently looks like this:
> 
> import unittest2 as unittest
> 
> from tagopsdb.database import init_database, init_session
> from tagopsdb.database.meta import Session
> 
> 
> def setup_module():
>     global transaction, connection, engine
> 
>     # Connect to the database and create the schema within a transaction
>     engine = init_session('dbtest', 'dbtestpasswd', hostname='localhost',
>                           db_name='TagOpsDB_Testing')
>     init_database()
> 
>     # If you want to insert fixtures to the DB, do it here
> 
> 
> def teardown_module():
>     # Roll back the top level transaction and disconnect from the database
>     Session.rollback()
>     Session.close()
>     engine.dispose()

I tend to organize things such that the scope of this transaction is *per 
test*, not per module as you're doing.  the engine, that can be per module, or 
preferably per-application.   But i'd be linking the lifespan of the Session to 
that of the transaction (which again begin_nested() should be a begin()).

> 
> I modified my test classes to subclass DatabaseTest, but an attempt
> to run the tests results in:
> 
> UnboundExecutionError: Could not locate a bind configured on mapper 
> Mapper|Environments|environments or this Session

make sure the Session is created with an explicit bind to the connection.


Attachment: signature.asc
Description: Message signed with OpenPGP using GPGMail

Reply via email to