unit testing CGI scripts?
Robert Brewer
fumanchu at amor.org
Tue Feb 10 12:18:35 EST 2004
Jim Hefferon wrote:
> Does anyone have suggestions for unit testing CGI scripts? For
> instance, how can I set the FieldStorage to have certain values?
> Do I have to go with a dummy FieldStorage class? And is there a
> better way to check the output than to compare the resulting
> (sometimes very large) strings? Changing the HTML slightly means
> that I have to edit the tests and that defeats the purpose of the
> tests, to some extent. I'd greatly appreciate any tips.
There have been several times when I've needed to not only unit test,
but actually pull out a debugger for a CGI script. Both of these were
made possible by abstracting the UI from the rest of the code, as I
described at:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/225299
Having the UI isolated in this way allows me to write a third UI which
pretends to be a webserver, but which I can load with my own "client
requests" (some details may be different from the (older) recipe, but
you get the idea):
import re
from HTMLParser import HTMLParser
from cgi import parse_qs
import dejavu
from dejavu.html import uihtml
class UserInterfaceHTMLDebug(uihtml.UserInterfaceHTML):
"""Create an interface imitating a CGI interface.
Use this interface to test or debug (step through) dejavu code,
acting as if the requests were coming from a web browser.
For example:
ns = dejavu.Namespace()
cfgs = r'C:\data\default.conf, C:\data\myapp.conf'
ui = UserInterfaceHTMLDebug(cfgs, ns, 'rbre')
print ui.request('http://localhost/myapp/index.htm', 'ID=300')
ID = ui.responses[-1].form_elements()['ID']['value']
"""
responses = None
def __init__(self, configFileNames, namespace, user=''):
# Set response hook.
self.responses = []
# Set standard properties.
self.protocol = u'https'
self.port = 80
self.hostname = u'localhost'
self.physicalPath = u''
self.user = user
self.path = u'/'
self.script = u''
config = {u'Dispatch': {u'pattern':
r'%(AppURLRoot)s/(.*)\.htm$',
u'repl': r'%(\1)s'},
u'Dispatches': {u'default' :
u'dejavu.html.default.homepage',
u'-Error':
u'dejavu.html.uihtml.error'},
u'ConfigFiles': configFileNames,
}
self.load(config)
def request(self, script, params='', newUser=None):
"""Request a page (calls dispatch())."""
atoms = script.split(u"/")
self.script = atoms.pop()
self.path = u"/".join(atoms)
self.physicalPath = u''
if newUser is not None:
self.user = newUser
self.set_params(params)
retVal = self.dispatch()
return '%s/%s: %s' % (self.path, self.script, retVal)
def set_params(self, queryString):
"""Parse CGI params into a paramset."""
self.requestParams.flush()
inParams = parse_qs(unicode(queryString), True, False)
for eachName, eachValue in inParams.items():
for eachSubValue in eachValue:
self.requestParams.add_param(eachName, eachSubValue)
def write(self, textToOutput, doMarkUp=False):
if doMarkUp:
textToOutPut = self.mark_up_for_output(textToOutPut)
self.responses.append(Response(textToOutput.encode("UTF-8")))
class Response(unicode):
"""A web page response. This is a unicode string with extra
methods."""
def __repr__(self):
return u'<uidebug Response object>'
def elements(self, parser):
return parser.get_elements(self)
def form_elements(self):
elems = FormElementParser('input').get_elements(self)
elems.extend(FormElementParser('select').get_elements(self))
return elems
class DejavuParser(HTMLParser):
elements = {}
tagType = u''
def __init__(self, tagType=''):
HTMLParser.__init__(self)
self.elements = {}
self.tagType = tagType
def get_elements(self, input):
self.feed(input)
self.close()
return self.elements
class FormElementParser(DejavuParser):
def handle_starttag(self, tag, attrs):
if tag == self.tagType:
attrs = dict(attrs)
if 'name' in attrs:
attrTable = dict([(key, value) for key, value
in attrs.iteritems() if key !=
'name'])
self.elements[attrs['name']] = attrTable
Best investment I ever made, breaking out the UI like that. Hope it at
least gives you some ideas.
To Dutin Lee: the same can be said for database access, only it's more
complicated. I use an abstract StorageManager, with subclasses for ADO,
ODBC, etc.. including a subclass just for debugging. To some extent, it
depends on how large your project is; i.e., whether such an investment
will pay off.
Robert Brewer
MIS
Amor Ministries
fumanchu at amor.org
More information about the Python-list
mailing list