Howdy,
I've been running into some odd/unexpected behavior while trying to write and run some controller unit tests with TG 0.9. I've been writing a testcase class that inherits from testutil.DBTest so I can set up some objects in the database and then use them within the controller test. What I've found doing this is that I have to create a new set of objects, with different searchable values, for each test. If I didn't do that, the old version of the object would get used, and values that I set in the object cretion would not be available.
This actually seems to be odd behavior to me. I would actually expect values in the database to *not* persist between tests. I've extracted some of the DBTest code into my own DBTest class and added code in the tearDown method to drop the tables that are used in the tests, so that the in-memory tables get recreated each time.
My test case class is:
--- begin IsolatedDBTestCase.py ---
import unittest
import inspect
import sqlobject
from sqlobject.inheritance import InheritableSQLObject
from turbogears import database, testutil
class IsolatedDBTestCase(unittest.TestCase):
"""
The IsolatedDBTestCase class is a unit test testcase that creates
the SQL tables in memory as needed, and drops them at the end of
the test. This isolates testcases that populate tables from each
other and removes any order dependency. The code for setUp comes
directly from testutil.DBTest. The tearDown code to drop the
created tables is (obviously) a modified version of the setUp code,
just altered to drop the tables after a rollback.
"""
model = None
def setUp(self):
print "In setUp()"
self.model = __import__("turbobudget", {}, {}, ["model"]).model
for item in self.model.__dict__.values():
if inspect.isclass(item) \
and issubclass(item, sqlobject.SQLObject) \
and item != sqlobject.SQLObject \
and item != InheritableSQLObject:
item.createTable(ifNotExists=True)
def tearDown(self):
database.rollback_all()
for item in self.model.__dict__.values():
if inspect.isclass(item) \
and issubclass(item, sqlobject.SQLObject) \
and item != sqlobject.SQLObject \
and item != InheritableSQLObject:
item.dropTable()
--- end IsolatedDBTestCase.py
---
I ripped out some of the code that makes DBTest more generic, since I refactored this out for my own testing, but the important work is in the tearDown() method, which essentially just drops any of the tables that were created in the setUp() method.
My controller testcases now look approximately like:
--- begin ---
from turbobudget.controllers import Root
from turbobudget.model import Budget, Envelope, User, Expense
import cherrypy
from turbogears.database
import PackageHub
from turbogears import testutil
from IsolatedDBTestCase import IsolatedDBTestCase
cherrypy.root = Root()
class TestEnvelopeMethods(IsolatedDBTestCase):
def test_getEnvelope(self):
"Set up an envelope and retrieve it"
# Arrange
#Budget._connection.debug = True
e1 = Envelope(name = "E1", description = "E1",
budget = None, value = 500)
id =
e1.id # Act
result = testutil.call(cherrypy.root.getEnvelope,
name = "E1")
# Assert
assert result['name'] == "E1"
assert result['value'] == 500
def test_getEnvelopeWithValue(self):
"Set up an envelope with a value and retrieve it"
# Arrange
#Budget._connection.debug = True
e1 = Envelope(name = "E1", description = "E1",
budget = None, value = 1000)
id =
e1.id # Act
result = testutil.call(cherrypy.root.getEnvelope,
name = "E1")
# Assert
assert result['name'] == "E1"
assert result['value'] == 1000, "Got %s" % result['value']
def test_tallyEnvelopeWithOneExpense(self):
"Set up an envelope with one expense and tally it"
# Arrange
#Budget._connection.debug = True
e1 = Envelope(name = "E1", description = "E1",
budget = None, value = 750)
ex1 = Expense(description = "Expense 1", category = "Test",
amount = 8.00, envelope = e1)
# Act
result = testutil.call(cherrypy.root.getEnvelope,
name = "E1")
# Assert
assert result['name'] == "E1"
assert result['value'] == 750.0 - 8.0, "Got %s" % result['value']
def test_tallyEnvelopeWithExpenses(self):
"Set up an envelope with expenses and tally it"
# Arrange
#Budget._connection.debug = True
e1 = Envelope(name = "E1", description = "E1",
budget = None, value = 825)
ex1 = Expense(description = "Expense 1", category = "Test",
amount = 8.00, envelope = e1)
ex2 = Expense(description = "Expense 2", category = "Test",
amount = 14.00, envelope = e1)
ex3 = Expense(description = "Expense 3", category = "Test",
amount = 21.00, envelope = e1)
# Act
result = testutil.call(cherrypy.root.getEnvelope,
name = "E1")
# Assert
assert result['name'] == "E1"
assert result['value'] == 825.0 - 43.0, "Got %s" % result['value']
--- end ---
Before I made the tearDown() change, I had to use a new name for each envelope to keep the in-controller lookup (
Envelope.select(...)) from always getting the first one that was created.
I hope someone finds this useful. To me, this seems like a more unit-test friendly behavior for tearDown().
- Bill
--
Bill Woodward
wpw...@gmail.com
http://www.saifa.net"I have more trouble with D. L. Moody than with any other man I ever met." -- D. L. Moody
s/D. L. Moody/Bill Woodward/g