[DB-SIG] DB-API unittests

Andrew Chambers andychambers2002 at yahoo.co.uk
Thu Dec 1 03:22:52 CET 2005


Hi All,

I've been doing some work on some unittests for the Python DB-API.

So far, I've created tests for...

   - Attempt to connect using invalid dbname should raise Error
   - Attempt to divide by zero should raise DataError
   - Attempt to violate foreign/primery key should raise IntegrityError
   - Attempt to create identical tables should raise ProgrammingError
   - Connection object should have minimum set of methods
   - Cursor object should have minimum set of methods
   - Simple select query should result in expected description/rowcount
   - Type_codes in description attr should evaluate to corresponding
     Singletons.

It only works for postgresql drivers since it connects to template1
which should be present on most postgresql databases 

It raises a few points (these may have been discussed here before but
googling the archives didn't reveal them.

- Evidently there is ambiguity about the way that dates should be entered.
  pgdb - curs.execute("select '%s'" % Date(2005, 01, 01))
  psycopg - curs.execute("select %s" % Date(2005, 01, 01))
  Don't know about the others - I don't have them installed

- Exceptions.  My interpretation of the API seems to deviate from that
  of psycopg and pgdb.  Does anyone have any views on which is correct?
  I wrote the tests without having any existing implementation in mind 
  just going from the spec.  Are the tests at fault?

I'd also be interested in what else you think should be tested.  I've
started to write some performance tests that simply query the pg_type
table for a fixed time period (30 secs).  These need more work though.
I'll post if anyone asks.

Finally, I couldn't think of a way to run these tests across a number of
modules without duplicating the code (although to be honest I haven't
given it much thought yet.  If anyone has any ideas I'd be interested to 
hear those too.

#!/usr/bin/env python

import unittest
import time
from <db_module_of_your_choice> import *


class ConnectionTestCase(unittest.TestCase):

	def testConnection(self):
		"""Connection Object returned should have minimum operations"""
		conn = connect(database="template1")
		self.assert_(set(('close', 'commit', 
		              'cursor', 'rollback')).issubset(set(dir(conn))))

	def testConnectFail(self):
		"""Should raise error on connecting with bad parameters"""
		self.assertRaises(DatabaseError, connect, database="fun'y name")

class CursorTestCase(unittest.TestCase):

	def setUp(self):
		self.conn = connect(database="template1")
		self.curs = self.conn.cursor()
	
	def tearDown(self):
		del self.curs
		del self.conn
	
	def testHasAttrs(self):
		"""Cursor object should have listed attributes\n
		
		"""
		self.curs.execute("select 1, 2;")
		required = ('arraysize', 'callproc', 'close', 
		            'description', 'execute', 'executemany', 
			    'fetchall', 'fetchmany', 'fetchone', 
			    'rowcount', 'setinputsizes', 'setoutputsize')
		actual   = (dir(self.curs))
		diff     = set(required).difference(set(actual))
		self.assert_(len(diff) == 0,
			"Absentees: %s" % diff)

	def testSelect(self):
		"""Data returned should be as expected
		
		For the query below, 
		rowcount = 4
		description = [(n, int4, 4, None, None, None, None), 
		               (t, 'text', -1, None, None, None, None)]
		                   """
		sql = """select 1 as n, 'text' as t
		         union
			 select 2, 'two'
			 union
			 select 3, 'three'
		"""
		required_rows = 3
		required_desc = [('n', 'int4', 4, None, None, None, None),
                                 ('t', 'text', -1, None, None, None, None)]
		self.curs.execute(sql)
		self.assert_(self.curs.rowcount == required_rows, 
		     "Actual rows: %s" % self.curs.rowcount)
		self.assert_(self.curs.description == required_desc,
		     "Actual description: %s" % self.curs.description)
	
	def testSelectParams(self):
		"""Query using parameters in simple pyformat
		"""
		sql = """select 1 as %s, '%s' as t
		         union
			 select %i, 'two'
			 union
			 select 3, 'three'
		"""
		self.curs.execute(sql, ('one', 'text', 2))
		required_rows = 3
		self.assert_(self.curs.rowcount == required_rows,
		     "Actual rows: %s" % self.curs.rowcount)
	
	def testSelectTypes(self):
		"""Description types should evaluate equal to module constants"""
		sql = """select '%(string)s', %(number)i, %(datetime)s"""
		self.curs.execute(sql, {'string' : "String", 
		                        'number' : 42, 
					'datetime' : Date(2005, 01, 01)})
		desc = self.curs.description
		self.assertEqual(desc[0][1], STRING, str(desc))
		self.assertEqual(desc[1][1], NUMBER)
		self.assertEqual(desc[2][1], DATETIME)


	def testInsertOK(self):
		"""Successfully insert valid data into test table
		"""
		create = """create table pygres_ins_test (
		              order_no  int  PRIMARY KEY,
			      supp_addr text,
			      order_date date
			    );
		"""
		insert = """insert into pygres_ins_test values (%i, '%s', %s)"""
		destroy = "drop table pygres_ins_test;"
		self.curs.execute(create)
		self.curs.execute(insert, (1, "Any'town", Date(2005, 11, 11)))
		self.curs.execute(destroy)
	
	def testProgrammingError(self):
		"""Attempt to create identical tables should raise ProgrammingError
		"""
		create = """create table dup (
			a	integer	
		);
		"""
		self.curs.execute(create)
		self.assertRaises(ProgrammingError, self.curs.execute, create)
	
	def testIntegrityErrorPK(self):
		"""Attempt to violate primary key should raise IntegrityError
		"""
		create = """create table order (
			order_id	integer      PRIMARY KEY,
			order_desc	text
		);
		"""
		insert = "insert into order values (%i, '%s')"
		self.curs.execute(create)
		self.curs.execute(insert, (1, 'First Order'))
		self.assertRaises(IntegrityError,
		                  self.curs.execute, insert, (1, 'Second Order'))
	
	def testIntegrityErrorFK(self):
		"""Attempt to violate foreign key should raise IntegrityError"""
		create_1 = """
		        create table order (
			order_id	integer		PRIMARY KEY,
			order_desc	text
			);"""
		create_2 = """
			create table order_item (
			order_id	integer REFERENCES order(order_id),
			product_id	integer,
			quantity	numeric,
			PRIMARY KEY(order_id, product_id)
			);
		"""
		insert_ord = "insert into order values (%i, '%s')"
		insert_ord_line = "insert into order_item values (%i, %i, %i)"
		self.curs.execute(create_1)
		self.curs.execute(insert_ord, (1, "First Order"))
		self.conn.commit()
		self.curs.execute(create_2)
		self.conn.commit()
		self.assertRaises(IntegrityError, 
		                  self.curs.execute, insert_ord_line, (2, 2, 2))
		self.conn.commit()
		self.curs.execute("drop table order cascade;")
		self.curs.execute("drop table order_item;")
		self.conn.commit()

	
	def testDataError(self):
		"""Attempt to divide by zero should raise DataError
		"""
		sql = "select (10 / 0)"
		self.assertRaises(DataError, self.curs.execute, sql)
	
class PerformanceTestCase(unittest.TestCase):

	def __connect(self):
		self.conn = connect(database="template1")
		self.curs = self.conn.cursor()

	def __close(self):
		del self.curs
		del self.conn

	def testRetrievals(self):
		self.__connect()	
		rows = 0
		start = time.time()
		while True:
			self.curs.execute("select * from pg_type")
			while True:
				row = self.curs.fetchone()
				rows += 1
				if time.time() - start > 30:
					print rows
					self.__close()
					return
				


def connectionSuite():
	"""Returns the Connection test suite"""
	return unittest.makeSuite(ConnectionTestCase)

def cursorSuite():
	return unittest.makeSuite(CursorTestCase)

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




More information about the DB-SIG mailing list