Python

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?

Database-driven testing in Python

Quality Labs just released PDbSeed, a Python framework for seeding a database. This is quite a hip tool during developer testing of applications which require a database. If you’ve ever heard of DbUnit, this is its Python cousin, man.

For example, here is a rough example of a PyUnit test case which refreshes the database on setup with a known dataset and then asserts the existence of a specific column value to verify the data was properly inserted.

import unittest
import pdbseed.core.dbseed as dbseed

class database_testcase(unittest.TestCase):

   def setUp(self):
     """
      setUp updates the database with the data found
      in the dataset
     """
     dbseed.RefreshOperation().execute(self._dataSet())

   def testData(self):
     """
      test case creates a new connection to db and
      asserts a specific item from the xml file exists
     """
     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()

   def _dataSet(self):
    """
      method returns a dbseed dataset type.
    """
    dbhost = 'localhost'
    dbname = 'words'
    dbuser = 'words'
    dbpassword = 'words'
    datasetFilename = 'C:/dev/prdbtesting/conf/words-seed.xml'
    metaDataFilename = 'C:/dev/prdbtesting/conf/metadata.xml'

    context = dbseed.ContextFactory.createContext('mysql', metaDataFilename, \
       dbhost, dbname, dbuser, dbpassword)

    return dbseed.FlatXMLFileRecordProducer(context, datasetFilename)

if __name__ == "__main__":
    unittest.main()

This example reads two files. One is metadata file which describes a database schema and the other is a dataset which contains values to be inserted into the database.

For more information, see the PDbSeed documentation; additionally, the library bundles some example code. Dig it?