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

Fixture files

I added the use of a fixture file using the the pyaml module. When testing a stable starting point is essential. For the Doctest and Unittests this is simple: in the script we already switch the database to a testing version so adding the fixture is trivial. Place the testfixture.yaml file inside the private folder of your app.

For the userinterface test it is not so simple. For these tests the script depends on a separate instance of Web2py, so we can't inject the fixture file. I decided to use environment vars. If WEB2PY_USE_DB_TESTING is set to TRUE the app under test will switch to a separate sqlite database (testing.sqlite) and read the fixture file (only once during the lifetime of the app). At the start of the db model we make sure that the file testing.sqlite is used and at the end of the db definitions we read in the fixture file. See the model file below.

 

model db.py

import os
import yaml
    
if os.getenv('WEB2PY_USE_DB_TESTING')=='TRUE':
    db = DAL('sqlite://testing.sqlite')

[ db table defs etc]

##############################################################################
# When testing use separate testing db with a fixture
##############################################################################
    
if os.getenv('WEB2PY_USE_DB_TESTING')=='TRUE' and not os.getenv('WEB2PY_DB_TESTING_FILLED')=='TRUE':
    # use fixture file only once during lifetime of the app
    try: 
        import yaml
    except:
        raise HTTP(404,body=H2('Needs Python library pyaml'))
    assert db._uri=='sqlite://testing.sqlite'  # just to be certain...
    for table in db.tables:
        # Make sure to cascade, or this will fail 
        # for tables that have FK references.
        db[table].truncate("CASCADE")  
    try:  
        print "Fixture will be applied now"
        data = yaml.load(open('applications/%s/private/testfixture.yaml'%current.request.application))
        for table_name, rows in data.items():
            for r in rows:
                db[table_name].insert(
                   **r
                )        
    except:
        print "File testfixture.yaml not found in %s/private folder"%current.request.application

    os.environ['WEB2PY_DB_TESTING_FILLED']='TRUE' # prevents reapplying  fixture during app run

I further discovered a problem with running all tests in one go. For the doctests and the unittest we need the -m parameter which makes sure the that the database is connected, with the 'global' db and auth available in the script environment. But when we want to run the userinterface test we use a separate web2py instance to test which - at least if it is a local instance - also wants to use the same database file. Inside the script we could close the database using db._adapter.connection.close() , but the run-time environment handling of web2py expects to close the connections when the script ends, which produces an error. Reopening the connection didn't seem to work.  I decided to keep the idea of a a single file to run all tests, but now using the -m parameter ( use model files) causes the doctests and unittests to run and leaving out -m parameter causes the UI tests to run. As the UI test take much more time is is maybe handy too that you can decide wich tests to run from the command line.

But if there is a better solution, I'm glad to hear about it. 

As Jonathan pointed out, the doctests need an extra blank line, haven't looked into that. And I removed the current.app var from the first version (just a convention of mine) and prevented this causing an error in the new version.

testRunner.py (new)

 

#!/usr/bin/python
"""
Execute with:
>   python web2py.py -S appname -M -R testRunner.py 
to run the doctests and unittests
>   python web2py.py -S appname    -R testRunner.py 
to run the userinterface tests

o With -M it runs all tests that exist in the tests directories of 'appname'.
o Also runs all doctests 
o Without -M parameter run the UI tests using Selenium

Unittests
=========

Inside the unittest file the class should have a name based on the 
file's path, like this:

FilenameDirectory -> DefaultTasksModel

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

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

depends on python modules unittest and selenium.

For documentation see slice 67 and 142 at http://www.web2pyslices.com

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

Changes and additions:
o appname 
o enable running on windows by using os.path
o moved db_test in from models 
o added UI tests using Selenium

version 0.92
2012/05/07
Nico de Groot
ndegroot0@gmail.com
"""

from gluon import current
import unittest
import glob
import sys
import doctest
import yaml
import os
import traceback
from copy import copy

def showfeedback():
    exc_type, exc_value, exc_traceback = sys.exc_info()
    print '-'*60
    for line in traceback.format_exception(exc_type, exc_value,exc_traceback):
        print line[:-1]
    print '-'*60

def custom_execfile(test_file):
    if os.name=='nt':
        errlist = (WindowsError,ValueError,SystemExit)
    else:
        errlist = (OSError,ValueError,SystemExit)
    
    try:
        sys.path.append(os.path.split(test_file)[0]) # to support imports form current folder in the testfiles
        # modules are applications/[appname]/modules
        modules_path=os.path.join('applications',appname,'modules')
        sys.path.append(modules_path)       # to support imports from modules folder
        sys.path.append('site-packages')    # support imports from web2py/site-packages
        g=copy(globals())
        execfile(test_file, g,) 
    except errlist:
        pass # we know about the rotating logger error...
             # and SystemExit is not useful to detect
    except:
        showfeedback()
    return g

appname= current.request.application

try: 
    db
except NameError: w2p_models=False
else: 
    w2p_models=True


# Create testing db, but only when necessary,
# maybe the app is already configured to use the testing db

if w2p_models:  

    if not db._uri=='sqlite://testing.sqlite':
        # create a test database by copying the original db
        print "Create testing db to replace current db..."
        test_db = DAL('sqlite://testing.sqlite')
        for tablename in db.tables:  # Copy tables!
            table_copy = [copy(f) for f in db[tablename]]
            test_db.define_table(tablename, *table_copy)

        try:
            data = yaml.load(open('applications/%s/private/testfixture.yaml'%current.request.application))
            for table in test_db.tables:
                # Make sure to cascade, or this will fail 
                # for tables that have FK references.
                test_db[table].truncate("CASCADE")    
            for table_name, rows in data.items():
                for r in rows:
                    test_db[table_name].insert(
                       **r
                    )        
        except:
            print "No fixture (testfixture.yaml) found in /private"

        db=test_db
    
    try:
        # my own convention: make db available using current.app
        # used in the doctests
        current.app.db=db
    except AttributeError:
        pass
        #print "The app doesn't use current.app.db (convention)"
    
    suite = unittest.TestSuite()
    
    # find unittests
    path=os.path.join('applications',appname,'tests','*','*.py')
    test_files = glob.glob(path)
    test_files =[x for x in test_files if not os.path.split(x)[0].endswith('userinterface')]
    
    print len(test_files)," unittest files found."
    
    # find doctests in controller
    path=os.path.join('applications',appname,'controllers','*.py')
    doc_test_files = glob.glob(path)
    print len(doc_test_files)," controller files with possible doctests found."

    if not test_files and not doc_test_files:
        print "No unittest and doctest test files found for application: " + appname
    
    # Bring in all doc tests and submit them
    print "Adding doctests" if doc_test_files else "No doctests"
    for f in  doc_test_files:
        g=custom_execfile(f)
        suite.addTest(doctest.DocFileSuite(f, globs=g,module_relative=False))
    
    # Bring in all unit tests and their controllers/models/whatever
    print "Adding unittests" if test_files else "No unittests"
    i=0
    for test_file in test_files:
        g=custom_execfile(test_file)
        # Create the appropriate class name based on filename and path
        components = os.path.split(test_file)
        filename = str.capitalize(components[-1][:-3]) # without .py
        directory = str.capitalize(os.path.split(components[-2])[-1])
    
        # Load the to-be-tested file
        to_be_tested_file = os.path.join("applications",
                                          appname,
                                          directory.lower() ,
                                          filename.lower() + ".py")
    
        #send class name (attribute of g) to the suite
        suite.addTest(unittest.makeSuite(g[filename+directory[:-1]] )) # lose the s
    
    # lets get rolling (doc & unit)
    unittest.TextTestRunner(verbosity=2).run(suite)

else: # no models, just the UI tests
    
    path=os.path.join('applications',appname,'tests','userinterface','case_*.py')
    selenium_test_files = glob.glob(path)
    print len(selenium_test_files)," userinterface testcases found."

    suite = unittest.TestSuite()
    
    # Bring in selenium tests and submit them to the suite
    print "Add external UI tests (Selenium)" if selenium_test_files else "No external UI tests"
    for test_file in  selenium_test_files:
    
        g = custom_execfile(test_file)
        # reconstruct the class name from filename
        components = os.path.split(test_file)
        filename = str.capitalize(components[-1][:-3]) # without .py
        classname = str.capitalize(filename.split("_")[1]) 
    
        #pass the classname to Suite
        suite.addTest(unittest.makeSuite(g[classname])) 

    unittest.TextTestRunner(verbosity=2).run(suite)
    if os.getenv('WEB2PY_USE_DB_TESTING') =="TRUE":
        print "UI tests have used the Testing-Fixture database in the webapp %s"%appname
        print "Reset environment variable WEB2PY_USE_DB_TESTING to switch to normal database"    

 

This is a script file to run all your tests in one go. Could be used for continuous integration testing. It is a augmented version of original work by jonromero and matclab.

testRunner.py (old)

#!/usr/bin/python
"""
Execute with:
>   python web2py.py -S appname -M -R testRunner.py

o Runs all tests that exist in the tests directories of 'appname'.
o Also runs all doctests and userinterface tests
o Adds each test to the unittestsuite

Current handling of the doctests need an extra blank line as pointed out by Jonathan below

Inside the unittest file the class should have a name based on the 
file's path, like this:

FilenameDirectory -> DefaultTasksModel

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

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

depends on python modules unittest and selenium.

For documentation see slice 67 and 142 at http://www.web2pyslices.com

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

Changes and additions:
o appname 
o enable running on windows by using os.path
o moved db_test in from models 
o added UI tests using Selenium

version 0.91
2011/08/08
Nico de Groot
ndegroot0@gmail.com
"""

from gluon import current
import unittest
import glob
import sys
import doctest
import os
import traceback
from copy import copy


# create a test database by copying the original db
test_db = DAL('sqlite://testing.sqlite')
for tablename in db.tables:  # Copy tables!
    table_copy = [copy(f) for f in db[tablename]]
    test_db.define_table(tablename, *table_copy)

db=test_db


def showfeedback():
    exc_type, exc_value, exc_traceback = sys.exc_info()
    print '-'*60
    for line in traceback.format_exception(exc_type, exc_value,exc_traceback):
        print line[:-1]
    print '-'*60


def custom_execfile(test_file):
    try:
        sys.path.append(os.path.split(test_file)[0]) # to support imports form current folder in the testfiles
        g=copy(globals())
        execfile(test_file, g) 
    except (WindowsError,ValueError,SystemExit):
        pass # we know about the rotating logger error...
             # and SystemExit is not useful to detect
    except:
        showfeedback()
    return g

suite = unittest.TestSuite()

appname= current.request.application

# find unittests
path=os.path.join('applications',appname,'tests','*','*.py')
test_files = glob.glob(path)
test_files =[x for x in test_files if not os.path.split(x)[0].endswith('userinterface')]

print len(test_files)," unittest files found."

# find doctests in controller
path=os.path.join('applications',appname,'controllers','*.py')
doc_test_files = glob.glob(path)
print len(doc_test_files)," controller files with possible doctests found."

# find selenium unittests
path=os.path.join('applications',appname,'tests','userinterface','case_*.py')
selenium_test_files = glob.glob(path)
print len(selenium_test_files)," userinterface testcases found."


if not test_files and not doc_test_files and not selenium_test_files:
    print "No unit, doctest or userinterface test files found for application: " + appname

# Bring in all doc tests and submit them
print "Run doctests" if doc_test_files else "No doctests"
for f in  doc_test_files:
    g=custom_execfile(f)
    suite.addTest(doctest.DocFileSuite(f, globs=g,
            module_relative=False))


# Bring in all unit tests and their controllers/models/whatever
print "Run unittests" if test_files else "No unittests"
for test_file in test_files:
    g=custom_execfile(test_file)
    # Create the appropriate class name based on filename and path
    components = os.path.split(test_file)
    filename = str.capitalize(components[-1][:-3]) # without .py
    directory = str.capitalize(os.path.split(components[-2])[-1])

    # Load the to-be-tested file
    to_be_tested_file = os.path.join("applications",
                                      appname,
                                      directory.lower() ,
                                      filename.lower() + ".py")

    #send class name (attribute of g) to the suite
    suite.addTest(unittest.makeSuite(g[filename+directory[:-1]] )) # lose the s

# Bring in all selenium tests and submit them to the suite
print "Run external UI tests (Selenium)" if selenium_test_files else "No external UI tests"
for test_file in  selenium_test_files:

    g = custom_execfile(test_file)
    # reconstruct the class name from filename
    components = os.path.split(test_file)
    filename = str.capitalize(components[-1][:-3]) # without .py
    classname = str.capitalize(filename.split("_")[1]) 

    #pass the classname to Suite
    suite.addTest(unittest.makeSuite(g[classname])) 

# lets get rolling
unittest.TextTestRunner(verbosity=2).run(suite)

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)

howto and conventions

  1. Put this script in the web2py root folder, with the name *testRunner.py*
  2. Put the helper script in the gluon contrib folder, name it test_helpers.py
  3. Install unittest and selenium using pip or easy_install.
  4. Create a tests folder in your application folder and go inside this folder
  5. Create a folder controllers and/or models for your unittests
  6. Create a folder userinterface for your ui tests

See the web2py book for examples of Doctests, see below for an example of a unittest and a userinterface test

unit test

For now see the origin slice for information.

[appname]/tests/controller/default.py

#!/usr/bin/python
#found when running python web2py.py -S welcome -M -R testRunner.py 

import unittest
import cPickle as pickle
from gluon import * 
from gluon.contrib.test_helpers import form_postvars
from gluon import current

class DefaultController(unittest.TestCase):
    def __init__(self, p):
        global auth, session, request
        unittest.TestCase.__init__(self, p)
        self.session = pickle.dumps(current.session)
        current.request.application = 'welcome'
        current.request.controller = 'default'
        self.request = pickle.dumps(current.request)

    def setUp(self):
        global response, session, request, auth
        current.session = pickle.loads(self.session)
        current.request = pickle.loads(self.request)
        auth = Auth(globals(), db)
        auth.define_tables()

    def _testRedirect(self, callable, url="/index"):
        try:
            resp = callable() # auth.register() creates & submits registration form
            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 unauthenticated user on %s() : %s (%s)" %
                    (callable.__name__, e.headers['Location'],url))
        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 a user in the db
        current.request.function='register'
        resp = auth.register() # get the form
        current.request = form_postvars("auth_user", {
            "email": "essai@gmail.com",
            "first_name": "e_first",
            "last_name": "e_last",
            "password" : "blob",
            "password_two": "blob",
            "_formkey": resp.formkey,
            },
            current.request, action="register",
            record_id=None, )     
        self._testRedirect(auth.register,'/welcome/default/index')
        self.assertTrue(auth.is_logged_in())
        self.assertEquals(auth.user.first_name, 'e_first')

userinterface test

You can read my blog for a introduction to Selenium. The file below is not very general, it is is tied to my application named dibsa. It tests an registration form called Aanmelding. This testcase imports a file named util(.py) from the same folder.

case_aanmelding.py

""" Uses Selenium2/webdriver to test the UI of DIBSA
    Version 0.9: 
    contain tests for aanmelding/registration page
"""
import unittest, time, re
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver import ActionChains
from util import Browser


class Aanmelding(unittest.TestCase):
    def setUp(self):
        """test using Chrome"""
        self.verificationErrors = []
        self.browser = Browser("chrome") #webdriver.Chrome()
        self.action_chains = ActionChains(self.browser)

    def set_chrome(self):
        self.browser = Browser("chrome") #webdriver.Chrome()
        self.action_chains = ActionChains(self.browser)
    def set_firefox(self):
        self.browser = Browser("firefox") #webdriver.Firefox()
        self.action_chains = ActionChains(self.b)

    def test_aanmelding_c_is_p(self):
 #       self.set_chrome()
        self._aanmelding_c_is_p()

    def _aanmelding_c_is_p(self, variant=1):
        """ user is participant, from basket:
        1: autopay
        2: invoice requested"""
        b=self.browser
        b.get("http://127.0.0.1:8000/dibsa/aanmelding/index?sdnr=2")
        assert "Dibsa" in b.title
        b.find_element_by_name("company").send_keys("TST")
        b.find_element_by_name("firstname").send_keys("N.C.")
        b.find_element_by_name("prefix").send_keys("de")
        b.find_element_by_name("lastname").send_keys("Groot")
        el = b.find_element_by_id("person_gender")
        #el.click() # selecteer
        el.send_keys("M")
        el = b.find_element_by_id("pref0")
        assert el.text=='Geen voorkeur, klik op knopje ->ws eenws tweews drie'
        b.find_element_by_name("street").send_keys("Heidelberglaan 2")
        b.find_element_by_name("postalcode").send_keys("3584 CS")
        b.find_element_by_name("city").send_keys("Utrecht")
        b.find_element_by_name("email").send_keys("n.c.degroot(A)uvt.nl")
        b.find_element_by_name("memo").send_keys("Testcase")
        b.find_element_by_xpath("//input[@value='Verder']").click()
        assert "toon_overzicht" in b.current_url

    def tst_aanmelding_c_is_not_p(self):
        b=self.browser
        b.get("http://127.0.0.1:8000/dibsa/aanmelding/index?sdnr=2")
        b.find_element_by_name("company").send_keys("TST")
        b.find_element_by_name("firstname").send_keys("N.C.")
        b.find_element_by_name("prefix").send_keys("de")
        b.find_element_by_name("lastname").send_keys("Groot")
        el = b.find_element_by_id("person_gender")
        el.click() # selecteer
        el.send_keys("M")
        b.find_element_by_name("isParticipant").click()

        b.find_element_by_name("street").send_keys("Heidelberglaan 2")
        b.find_element_by_name("postalcode").send_keys("3584 CS")
        b.find_element_by_name("city").send_keys("Utrecht")
        b.find_element_by_name("email").send_keys("n.c.degroot@uvt.nl")
        b.find_element_by_name("memo").send_keys("Testcase")
        b.find_element_by_xpath("//input[@value='Verder']").click()
        assert "toon_overzicht" in b.current_url


    def tearDown(self):
        self.browser.close()
        self.assertEqual([], self.verificationErrors)

if __name__ == "__main__":
    unittest.main()
print("code sample");

util.py

""" model en util classes         
"""
# global packages
import os
import time
import logging
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver import ActionChains


class Browser(object):
    def __init__(self,name="chrome"):
        if name=="chrome":
            self.browser=webdriver.Chrome()
        elif name == "firefox":
            self.browser=webdriver.Firefox()
        else:
            raise Exception("NYI")
        self.action_chains = ActionChains(self.browser)

    @property
    def title(self):
        return self.browser.title

    @property
    def current_url(self):
        return self.browser.current_url

    @property
    def url(self):
        return self.browser.current_url

    def set_chrome(self):
        self.browser = webdriver.Chrome()
        self.action_chains = ActionChains(self.browser)

    def set_firefox(self):
        self.browser = webdriver.Firefox()
        self.action_chains = ActionChains(self.b)

    def find_element_by_name(self,name):
        try:
            el=self.browser.find_element_by_name(name)
            return el
        except:
            msg ="Failed to find name %s"%name
            raise Exception(msg)

    def find_element_by_id(self,id):
        try:
            el=self.browser.find_element_by_id(id)
            return el
        except:
            msg = "Failed to find id %s"%id
            raise Exception(msg)

    def find_element_by_xpath(self,xpath):
        try:
            el=self.browser.find_element_by_xpath(xpath)
            return el
        except:
            msg = "Failed to find xpath %s"%xpath
            raise Exception(msg)

    def get(self,url):
        return self.browser.get(url)

    def close(self):
        time.sleep(4)
        return self.browser.close()

# 'safe' globals
logging=logging
base="http://127.0.0.1:8000"
app="dibsa"

class Person(dict):
    def __init__(self, \
                 company="FKT",\
                 firstname="N.C.",\
                 prefix="de", \
                 lastname="Groot", \
                 street="Heidelberglaan 2",\
                 postalcode="3584 CS",\
                 city="Utrecht",\
                 email="n.c.degroot(AT)uvt.nl",\
                 memo="Testcase",\
                 account="123"):
        self["company"]=company
        self["firstname"]=firstname
        self["prefix"]=prefix
        self["lastname"]=lastname
        self["street"]=street
        self["postalcode"]=postalcode
        self["city"]=city
        self["email"]=email
        self["memo"]=memo
        self["account"]=account

class Participant(dict):
    def __init__(self, \
                 #company="UvT",\
                 firstname="Henk",\
                 prefix="de", \
                 lastname="Vries", \
                 #street="Heidelberglaan 2",\
                 #postalcode="3584 CS",\
                 #city="Utrecht",\
                 #email="n.c.degroot(AT)uvt.nl",\
                 #memo="Testcase",\
                 #account="123"\
                 ):
#        self["company"]=company
        self["firstname"]=firstname
        self["prefix"]=prefix
        self["lastname"]=lastname

class Pref(dict):
    def __init__(self,pref0,pref1,pref2):
        self["pref0"]=pref0
        self["pref1"]=pref1
        self["pref2"]=pref2


def sendMail(cnaam):
    address=""
    maintainer="n.c.degroot(AT)uvt.nl"
    print "send mail to %s"%maintainer
    text='mailto:%s?subject=Studiedag%20testrapport'%(address)+\
         '&body=Resultaten' +\
         "zojuist heb ik de test '%s' gedraaid. "%cnaam
    os.startfile(text)


Yamlfile testfixture.yaml
discount:
 - activity: 1
   reason: student
   info: ik ben een student
   amount: 10.0
 - activity: 1
   reason: alumnus
   info: Ik ben Alumnus
   amount: 15.0
 - activity: 2
   reason: student
   info: ik ben een student
   amount: 10.0
 - activity: 2
   reason: alumnus
   info: Ik ben Alumnus
   amount: 15.0
 - activity: 3
   reason: student
   info: ik ben een student
   amount: 17.0
 - activity: 3
   reason: alumnus
   info: Ik ben Alumnus
   amount: 18.0
 - activity: 4
   reason: student
   info: ik ben een student
   amount: 10.0
 - activity: 4
   reason: alumnus
   info: Ik ben Alumnus
   amount: 15.0
activity:
 - name: 1. No workshops, autopay required
   nr_prefs: 0
   nr_sessions: 0
   amount: 100
   w_start: !!timestamp '2021-01-02 10:00:00'
   w_end: !!timestamp '2021-01-02 17:00:00'
   w_register: !!timestamp '2021-01-02 10:00:00'
   autopay_required: true
 - name: 2. No workshops, no prefs, only options
   nr_prefs: 0
   nr_sessions: 1
   amount: 200
   w_start: !!timestamp '2021-01-02 10:00:00'
   w_end: !!timestamp '2021-01-02 17:00:00'
   w_register: !!timestamp '2021-01-02 10:00:00'
 - name: 3. workshops, prefs, no options
   nr_prefs: 2
   nr_sessions: 1
   amount: 300
   w_start: !!timestamp '2021-01-02 10:00:00'
   w_end: !!timestamp '2021-01-02 17:00:00'
   w_register: !!timestamp '2021-01-02 10:00:00'
 - name: 4. workshops, sessions, prefs, no options
   nr_prefs: 2
   nr_sessions: 2
   amount: 400
   w_start: !!timestamp '2021-01-02 10:00:00'
   w_end: !!timestamp '2021-01-02 17:00:00'
   w_register: !!timestamp '2021-01-02 10:00:00'
 - name: 5. workshops, sessions, prefs, no options, single prefs
   nr_prefs: 2
   nr_sessions: 2
   combined_prefs: true
   amount: 400
   w_start: !!timestamp '2021-01-02 10:00:00'
   w_end: !!timestamp '2021-01-02 17:00:00'
   w_register: !!timestamp '2021-01-02 10:00:00'
workshop:
 - name: act3, ws1
   activity: 3
   number: 1
 - name: act3, ws2
   activity: 3
   number: 2
 - name: act3, ws3
   activity: 3
   number: 3
 - name: act4, ws1
   activity: 4
   number: 1
 - name: act4, ws2
   activity: 4
   number: 2
 - name: act4, ws3
   activity: 4
   number: 3
 - name: act5, ws1
   activity: 5
   number: 1
 - name: act5, ws2
   activity: 5
   number: 2
 - name: act5, ws3
   activity: 5
   number: 3
sessions:
 - workshop: 1
   number: 1
 - workshop: 2
   number: 1
 - workshop: 3
   number: 1
 - workshop: 4
   number: 1
 - workshop: 5
   number: 1
 - workshop: 6
   number: 1
 - workshop: 4
   number: 1
 - workshop: 5
   number: 1
 - workshop: 5
   number: 2
 - workshop: 6
   number: 2
 - workshop: 7
   number: 1
 - workshop: 8
   number: 1
 - workshop: 8
   number: 2
 - workshop: 9
   number: 2
optional:
 - name: diner
   activity: 2
   number: 1
   amount: 11
 - name: lunch
   activity: 2
   number: 2
   amount: 12
 - name: diner
   activity: 1
   number: 1
   amount: 12
 - name: lunch
   activity: 1
   number: 2
   amount: 13
  

Comments (2)

  • Login to post



  • 0
    web2pyslices 3 years ago
    To answer my second question, it is necessary to place a blank line after the last expected input for doctest to work properly. So my simple example became:
    def simple_test():
        '''
    >>> simple_test()
    'hello world'
    
        '''
        return ('hello world')
    
    Note the blank line before the closing '''

  • 0
    web2pyslices 3 years ago
    Hi, I am trying to use this script, but I am having some troubles. 1) I had to comment out line 60 to avoid
    Traceback (most recent call last):
      File "/home/jon/code/web2py_1.99.2/gluon/shell.py", line 206, in run
        execfile(startfile, _env)
      File "testrunner.py", line 60, in 
        current.app.db=db
    AttributeError: 'thread._local' object has no attribute 'app'
    
    It doesn't look (to me) as if current.app.db gets used again in any case. 2. More importantly I can't make doctests work. I have a simple test controller:
    def simple_test():
        '''
    >>> simple_test()
    'hello world'
        '''
        return ('hello world')
    
    I can run it from the 'Edit Application' menu (http://127.0.0.1:8000/admin/default/design/junk) and it runs fine. I can run it via "python -m doctest -v applications/junk/controllers/junk.py" and it works. But if I use the testRunner.py script I get a problem (rather verbose output below). Basically it reads the expected value as running on to the next line (the end of comment line). Any pointers you can give would be appreciated. I do like the approach of running all three of doctests, unit tests and selenium tests in one script. Thanks, Jonathan.
    python web2py.py -S junk -M -R testrunner.py 
    web2py Web Framework
    Created by Massimo Di Pierro, Copyright 2007-2011
    Version 1.99.2 (2011-09-26 06:55:33) stable
    Database drivers available: SQLite3, pymysql
    0  unittest files found.
    3  controller files with possible doctests found.
    0  userinterface testcases found.
    Run doctests
    No unittests
    No external UI tests
    applications/junk/controllers/junk.py
    Doctest: junk.py ... FAIL
    applications/junk/controllers/appadmin.py
    Doctest: appadmin.py ... ok
    applications/junk/controllers/default.py
    Doctest: default.py ... ok
    
    ======================================================================
    FAIL: applications/junk/controllers/junk.py
    Doctest: junk.py
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/usr/lib/python2.7/doctest.py", line 2166, in runTest
        raise self.failureException(self.format_failure(new.getvalue()))
    AssertionError: Failed doctest test for junk.py
      File "applications/junk/controllers/junk.py", line 0
    
    ----------------------------------------------------------------------
    File "applications/junk/controllers/junk.py", line 3, in junk.py
    Failed example:
        simple_test()
    Expected:
        'hello world'
            '''
            return ('hello world')
    Got:
        'hello world'
    
    
    ----------------------------------------------------------------------
    Ran 3 tests in 0.003s
    
    FAILED (failures=1)
    

Hosting graciously provided by:
Python Anywhere