14

I try to speedup my tests by putting test data from test case setUp() to setUpClass()/teardownClass class method, so it does not recreate same select only fixture for every test in test case.

@classmethod
def setUpClass(cls):
    plant.StuffFactory() #plant stuff with FactoryBoy
    transaction.commit()

@classmethod
def tearDownClass(cls):
    session.query(models.Stuff).delete() # delete planted stuff
    transaction.commit()

But i don't like deleting stuff with session.delete on my own, because i use many models and don't want track what i planted. I want something like

@classmethod
def tearDownClass(cls):
    session.clear() #delete all
    transaction.commit()

But session.close() or session.remove() will not affect committed data. So i seek some way to "cancel" setUpClass transaction.commit() like i don't plant anything.

I try nested transactions and savepoints but again they works only if data not committed yet.

Any pointers?

Nilesh
  • 20,521
  • 16
  • 92
  • 148
Aristarhys
  • 2,092
  • 7
  • 37
  • 68

2 Answers2

10

If you don't want things to be committed, simply don't call transaction.commit() :)

@classmethod
def setUpClass(cls):
    plant.StuffFactory() #plant stuff with FactoryBoy
    # you may possibly want to flush the session, so everything has proper IDs etc.
    DBSession.flush() 
    # let the transaction continue for the duration of the test

@classmethod
def tearDownClass(cls):
    # let the database make everything as if nothing happened
    transaction.rollback()

This would require that none of your code under test does explicit transaction management (transaction.commit() \ transaction.rollback() in application code), but that's a bad practice anyway:

As a general rule, the application should manage the lifecycle of the session externally to functions that deal with specific data. This is a fundamental separation of concerns which keeps data-specific operations agnostic of the context in which they access and manipulate that data.

Session Basics, SQLAlchemy Documentation (https://docs.sqlalchemy.org/en/13/orm/session_basics.html)

Sergey
  • 11,892
  • 2
  • 41
  • 52
  • 2
    I'm new to ORMs. Why would it be a bad practice to call `session.commit()` in application code? How else would you commit anything? The SQLAlchemy docs seem to suggest using `commit()` even for "substantial applications": https://docs.sqlalchemy.org/en/13/orm/session_basics.html#when-do-i-construct-a-session-when-do-i-commit-it-and-when-do-i-close-it – Mike B Jul 26 '21 at 00:43
  • Ah, I think I know why. OP doesn't specify what kind of tests he's writing, and I'm guessing Sergey's advice is for unit tests, which I 100% agree with. Personally, I'm focused on writing integration tests, which will naturally run into a call to `commit()`, so I need a different strategy. – Mike B Jul 26 '21 at 00:50
  • 2
    @mblakesley: I've added a quote from SA docs. For integration tests, I guess, you'd have to have a different strategy, such as deleting/recreating stuff in the db – Sergey Jul 29 '21 at 02:05
1

In setUpClass() you could create a save point as:

sp = transaction.savepoint()

then in tearDownClass() you could simply use:

sp.rollback()

Hope this helps.

Pankaj Sharma
  • 669
  • 5
  • 12
  • 1
    You see, `sp` will become invalid after `transaction.commit()`, so if i try to do `sp.rollback()` it will throw `InvalidSavepointRollbackError: invalidated by a later savepoint`, maybe `transaction.commit()` create new savepoint, but then how to retrive it? I need rollback to pre commit state, before all planting. – Aristarhys Jul 30 '14 at 10:08
  • You can declare it as class variable http://stackoverflow.com/questions/68645/static-class-variables-in-python – Pankaj Sharma Jul 30 '14 at 11:06
  • I am aware of usage like `cls.sp` Just try this: `sp = transaction.savepoint(); transaction.commit(); sp.rollback()` – Aristarhys Jul 30 '14 at 11:41