Python patterns with PDbSeed
The notion of an abstract class or interface doesn’t really exist in Python. To create a base class which isn’t intended to be utilized directly (for example in the template pattern) and which contains abstract methods anticipated to be implemented by subclasses, you can use the not implemented technique.
To do this, first define the indented hip algorithm in the base class, which usually is concrete methods relying on subclass implementations of the abstract methods. Next, define the methods in the base class which must be defined by sub classes and add a raise NotImplemented clause to each one.
For example, in the copasetic abstract base class DefaultDatabaseTest, the setUpOp method makes the following call:
dbseed.RefreshOperation().execute(self._dataSet())
The self._dataSet() call is an internal method (the _ preceding the method name is an indication that this method is intended to be private) . Looking at that method, we see this definition:
def _dataSet(self):
return dbseed.FlatXMLFileRecordProducer(self._context(), \
self.dataSetFile())
Note how this method calls two instance methods- _context (which again is private) and dataSetFile. Examining the dataSetFile method below shows that this method doesn’t do anything interesting (or helpful) in the base class- it merely throws a trippin’ exception!
def dataSetFile(self):
"""
implement this method- provide a location to the file
"""
raise NotImplemented
Therein is the definition of a template pattern algorithm- subclasses must implement the dataSetFile method so that the base class’s algorithm can work properly.
The template pattern is quite handy when it comes to PDbSeed. One can create a PyUnit test class which can serve as a base class for database testing. Here is the full definition of the DefaultDatabaseTest class:
import unittest
import pdbseed.core.dbseed as dbseed
"""
This class is modeled after DbUnit's DatabaseTestCase. This
class is abstract in nature due to the raise NotImplemented
clauses in methods intented to be overridden in subclasses.
Subclasses MUST override: connection(), metaFile(), and
dataSetFile().
"""
class DefaultDatabaseTest(unittest.TestCase):
def setUp(self):
self.setUpOp()
def tearDown(self):
self.tearDownOp()
def setUpOp(self):
dbseed.RefreshOperation().execute(self._dataSet())
def tearDownOp(self):
"""
the algorithm for seeding does a refresh (which is an
insert OR update); therefore, the teardown is empty
"""
pass
def connection(self):
"""
this method should return an instance of
pdbseed.extension.databasetestcase.Connection
"""
raise NotImplemented
def metaFile(self):
"""
implement this method- provide a location to the file
"""
raise NotImplemented
def dataSetFile(self):
"""
implement this method- provide a location to the file
"""
raise NotImplemented
def _context(self):
"""
"""
conn = self.connection()
return dbseed.ContextFactory.createContext(conn.db, \
self.metaFile(), \
conn.dbhost, \
conn.dbname, \
conn.dbuser, \
conn.dbpassword)
def _dataSet(self):
return dbseed.FlatXMLFileRecordProducer(self._context(), \
self.dataSetFile())
"""
This class is a simple struct that represents the
configuration information for a database
"""
class Connection:
def __init__(self, db, dbhost, dbname, dbuser, dbpassword):
self.db = db
self.dbhost = dbhost
self.dbname = dbname
self.dbuser = dbuser
self.dbpassword = dbpassword
Utilizing this class becomes a simple exercise. First, implement the three abstract methods (connection, datasetFile, and metaFile) and then second, write test cases which rely on data in the database to test your application layer.
For example, below is a test case which shows DefaultDatabaseTest in use.
import unittest
import xpdbseed.extension.databasetestcase
from xpdbseed.extension.databasetestcase import DefaultDatabaseTest
from xpdbseed.extension.databasetestcase import Connection
class MockDbTestCase(DefaultDatabaseTest):
def metaFile(self):
return 'C:/dev/projects/pro/dbtesting/conf/metadata.xml'
def dataSetFile(self):
return 'C:/dev/projects/pro/dbtesting/conf/words-seed.xml'
def connection(self):
return Connection(db="mysql", dbhost="localhost", \
dbname="words", dbuser="words", \
dbpassword="words")
def testDataIsThere(self):
"""
test case creates its own connection to the
db to verify that data is already there, in
this case 'pugnacious' is found as it was
seeded via the words-seed.xml file
"""
import MySQLdb
conn= MySQLdb.connect( host = "localhost", \
user = "words", passwd = "words", db = "words")
query = 'select word.part_of_speech from word \
where word.spelling = \'pugnacious\';'
curs = conn.cursor()
curs.execute(query)
wlist = curs.fetchall()
for word in wlist:
self.assertEqual('Adjective', word[0], 'Adj was not returned')
conn.close()
if __name__ == "__main__":
unittest.main()
Hopefully, this template class will make it into the 0.8 version of PDbSeed. Doesn’t it make you want to whip out a couple of test cases? Do you dig it, man?
| Related odds and ends | ||
|---|---|---|
Thursday 02 Mar 2006 | Dynamic Languages, PyUnit, Python
What ever happened to PDbSeed? The site at Quality Labs looks like a ghost town. I’m interested in the possibility of using it for Firebird.
Yeah, PDbSeed is still alive– just not kicking, so to speak. I’ve used the code a number of times at client sites; however, it could use some new features– do you have any ideas?
It also seems the link I provided in this entry is incorrect– it should be http://qualitylabs.org/pdbseed/.
I’ve started to port a database from SQL Server to Firebird. I thought I would first write the unit tests against the SQL Server database to get them right. Then after the stored procedure (or whatever) has been ported to Firebird, run the same unit test again.
I’ve been able to connect to both databases with Python, so Python seems like a nice fit.
Since we already generate test databases, I’m not sure I need the data stored off in XML. I’m becoming more and more doubtful that I need a framework like PDbSeed or xUnit.
Since I last dropped by here, I’ve discovered that the Python database API calls for a rollback by default. I’m pretty sure I can port most of our existing unit tests written in TSQL, using TSQLUnit, using that feature. The only thing I think I’m missing at the moment is a class to set up test suites (with fixtures), and a factory class like PDbSeed has, to connect to the different RDBMSs.
I’m a Python newbie, though, so all this is taking a little longer than otherwise.
Yeah, XML is a nice format if the dataset isn’t too big, but many times the sheer size of a database model makes working with XML seed files painful. Python is a blast though, isn’t it? Good luck, Steve!!