If you benefit from web2py hope you feel encouraged to pay it forward by contributing back to society in whatever form you choose!

First of all, lets create a unit testing file that will run all our tests:

#!/usr/bin/python
"""
Runs all tests that exist in the tests directories.

The class should have a name based on the file's path, like this:
FilenameDirectory -> DefaultTasksModel

for example:
applications/app/tests/controllers/default.py
is
class DefaultController(unittest.TestCase)

BEWARE that the name is NOT in plural (controllers->Controller)

Execute with:
>   python web2py.py -S appname -M -R testRunner.py


02/03/2009
Jon Vlachoyiannis
jon@emotionull.com

"""

import unittest
import glob
import sys

suite = unittest.TestSuite()

# get all files with tests
test_files = glob.glob('applications/'+sys.argv[2]+'/tests/*/*.py')

if not len(test_files):
    raise Exception("No files found for app: " + sys.argv[2])

# Bring all unit tests in and their controllers/models/whatever
for test_file in test_files:
    execfile(test_file, globals())

    # Create the appropriate class name based on filename and path
    # TODO: use regex
    filename =  str.capitalize(test_file.split("/")[-1][:-3])
    directory =  str.capitalize(test_file.split("/")[-2][:-1])

    suite.addTest(unittest.makeSuite(globals()[filename+directory]))

    # Load the to-be-tested file
    execfile("applications/"+sys.argv[2]+"/"+directory.lower()+"s/"+filename.lower()+".py", globals())


db = test_db # Use the test database for all tests

unittest.TextTestRunner(verbosity=2).run(suite)

Let's place it in the same directory with web2py.py with the name testRunner.py

Now, let's place a helper file inside the gluon directory (name it test_helpers.py):

def form_postvars(tablename, fields, request, action="create", record_id=None):
    """
    Creates the appropriate request vars for forms
    """
    for field_name in fields:
        request.vars[field_name] = fields[field_name]

    if action == "create":
        request.vars["_formname"] = tablename + "_" + action
    elif action == "update":
        request.vars["_formname"] = tablename + "_" + str(record_id)
        request.vars["id"] = record_id
    elif action == "delete":
        request.vars["_formname"] = tablename + "_" + str(record_id)
        request.vars["id"] = record_id
        request.vars["delete_this_record"] = True
    else:
        raise Exception("The form action '", action, "' does not exist")

Now, let's create some tests. Inside our application there is a tests directory. There we create a directory called "controllers" and inside there we can put our test file for the controller. For example, here it is default.py (for a default controller)

#!/usr/bin/python

import unittest
from gluon.globals import Request, Session, Storage, Response
from gluon.contrib.test_helpers import form_postvars

class DefaultController(unittest.TestCase):
    def setUp(self):
        request = Request()  # Use a clean request
        session = Session()  # Use a clean session

    def testIndex(self):
        form_postvars("bla", {"body": "yes"}, request, action="create", record_id=None)     
        resp = index()

        self.assertFalse(resp['form'].errors) # do we have errors?
        self.assertFalse(resp['form'].errors.has_key('name')) # is something missing?

        self.assertEquals(2, len(resp["results"]))      

            db(db.bla.id>0).delete()  
        db.commit()

the name of the class should be the name of the controller we want to test and the directory it exists. For example: /controllers/appadmin.py is -> AppadminController (be careful is Controller not Controllers!).

Every function that starts with "test" is going to be executed. It is best to create tests with the same action.

form_postvars updates request.vars with the appropriate data so you can test forms.

You can call the action by it's name (index()) and the result is what the action returns as a dictionary.

Don't forget to commit to db!

You can also create a test database, just by create a model in your models directory: test_db.py

import copy

test_db = DAL('sqlite://testing.sqlite')
for tablename in db.tables:  # Copy tables!
    table_copy = [copy.copy(f) for f in db[tablename]]
    test_db.define_table(tablename, *table_copy)

Run your test files with this command:

> python web2py.py -S testa -M -R testRunner.py

Whenever you put files inside tests/controllers or tests/models/ or test/something of your app, they'll be checked.

Notice: This is WORK IN PROGRESS! The wiki is still updated and I'll post a full example to bitbucket when I find time. Check here: http://www.google.com/url?sa=D&q=http://web2py.com/AlterEgo/default/show/260&usg=AFQjCNFlARW6FkrzPBtmmW4-XHnRjpeN4Q and http://groups.google.com/group/web2py/browse_thread/thread/e3b4ea0deccabbee/cdc6128fdca491bb?q=unit+testing&lnk=nl&

Comments (2)

  • Login to post



  • 0
    matclab 14 years ago
    I finally use the following helper :
    
    def form_postvars(tablename, fields, request, action="create", 
            record_id=None):
        """
        Creates the appropriate request vars for forms
        """
    
        vars = {}
        for field_name in fields:
            vars[field_name] = fields[field_name]
        if action == "create":
            vars["_formname"] = tablename + "_" + action
        elif action == "update":
            vars["_formname"] = tablename + "_" + str(record_id)
            vars["id"] = record_id
        elif action == "delete":
            vars["_formname"] = tablename + "_" + str(record_id)
            vars["id"] = record_id
            vars["delete_this_record"] = True
        elif action:
            vars["_formname"] = action
    
        request['vars'].update(vars)
        request['post_vars'].update(vars)
    
    
    and the following test class: (note that without pickling at init and unpickling at setup, the session and request was not cleaned up before each test.)
    
    #!/usr/bin/python
    
    #python web2py.py -S postcard -M -R testRunner.py
    
    import unittest
    from gluon.globals import Request, Session, Storage, Response
    from gluon.contrib.test_helpers import form_postvars
    from gluon.html import BEAUTIFY
    import cPickle as pickle
    
    
    
    class DefaultController(unittest.TestCase):
        def __init__(self, p):
            global auth, session, request
            unittest.TestCase.__init__(self, p)
            self.session = pickle.dumps(session)
            request.application = 'appname'
            request.controller = 'default'
            self.request = pickle.dumps(request)
    
    
        def setUp(self):
            global response, session, request, auth
            session = pickle.loads(self.session)
            request = pickle.loads(self.request)
            auth = Auth(globals(), db)
            auth.define_tables()
       def _testRedirect(self, callable, url="/index"):
            try:
                resp = callable()
                self.fail("%s should raise an exception\n%s" % (
                    callable.__name__,
                    resp.errors))
            except HTTP, e:
                self.assertTrue(e.headers['Location'] == url,
                        "Wrong redirection url for unlogged user on %s() : %s" %
                        (callable.__name__, e.headers['Location']))
            else:
                self.fail("%s should raise an HTTP exception\n%s" % (
                    callable.__name__,
                    e))
    
        def emptyUserDB(self):
            db(db.auth_user.id>0).delete()
            db.commit()
    
        def testRegisterSuccess(self):
            self.emptyUserDB()
            #Register
            request.function='register'
            resp = auth.register()
            form_postvars("auth_user", {
                "email": "essai@gmail.com",
                "first_name": "e_first",
                "last_name": "e_lats",
                "password" : "blob",
                "password_two": "blob",
                "_formkey": resp.formkey,
                },
                request, action="register",
                record_id=None, )     
            self._testRedirect(auth.register)
            self.assertTrue(auth.is_logged_in())
            self.assertEquals(auth.user.first_name, 'e_first')
    
    
    

  • 0
    matclab 14 years ago
    I've added Doctest for controllers files in testRunner.py :
    #!/usr/bin/python
    """
    Runs all tests that exist in the tests directories.
    
    The class should have a name based on the file's path, like this:
    FilenameDirectory -> DefaultTasksModel
    
    for example:
    applications/app/tests/controllers/default.py
    is
    class DefaultController(unittest.TestCase)
    
    BEWARE that the name is NOT in plural (controllers->Controller)
    
    Execute with:
    >   python web2py.py -S appname -M -R testRunner.py
    
    
    02/03/2009
    Jon Vlachoyiannis
    jon@emotionull.com
    
    """
    
    import unittest
    import glob
    import sys
    import doctest
    
    suite = unittest.TestSuite()
    from copy import copy
    
    # get all files with tests
    test_files = glob.glob('applications/'+sys.argv[2]+'/tests/*/*.py')
    doc_test_files = glob.glob('applications/'+sys.argv[2]+'/controllers/*.py')
    
    if not test_files and not doc_test_files:
        raise Exception("No files found for app: " + sys.argv[2])
    
    for f in  doc_test_files:
        g = copy(globals())
        execfile(f, g)
        suite.addTest(doctest.DocFileSuite(f, globs=g,
                module_relative=False))
    
    
    # Bring all unit tests in and their controllers/models/whatever
    for test_file in test_files:
        g = copy(globals())
        execfile(test_file, g)
    
        # Create the appropriate class name based on filename and path
        # TODO: use regex
        filename =  str.capitalize(test_file.split("/")[-1][:-3])
        directory =  str.capitalize(test_file.split("/")[-2][:-1])
    
    
        # Load the to-be-tested file
        to_be_tested_files = "applications/" + sys.argv[2] + "/" + \
            directory.lower() + "s/" + filename.lower() + ".py"
    
        execfile(to_be_tested_files, g)
    
        suite.addTest(unittest.makeSuite(g[filename+directory]))
    
    
    
    
    db = test_db # Use the test database for all tests
    
    unittest.TextTestRunner(verbosity=2).run(suite)
    

Hosting graciously provided by:
Python Anywhere