View · Index

Testing with TCLWebtest

Testing with TCLWebtest

by Simon Carstensen and Joel Aufrecht

Tclwebtest is primarily for testing user interface and acceptance testing.

API testing is only part of testing your package - it doesn't test the code in our adp/tcl pairs. For this, we can use TCLWebtest (see sourceforge). TCLWebtest provides a library of functions (see command reference) that make it easy to call a page through HTTP, examine the results, and drive forms. TCLwebtest's functions overlap slightly with acs-automated-testing; see the example provided for one approach on integrating them.

TCLWebtest must be installed for to work. Since automated testing uses it, it should be part of every OpenACS installation. Note that TCLwebtest is installed automatically by Malte's install script.

Forum Posts:

Command Reference:

  • http://tclwebtest.sourceforge.net/doc/api_public.html 

Articles:

  • Automated Testing Best Practices see: https://openacs.org/doc/automated-testing-best-practices.html
  • testing packages for release en:Package_Testing_Process_

Tools:

  • Webtest-Recorder Firefox extension (TwtR) see http://www.km.co.at/km/twtr This module is a plugin for Firefox. It is used to generate/edit a tclwebtest script which can be used later for regression testing without the need of a browser. There is a certain overlap of the application range between selenium and TwtR. This plugin was developed by Åsmund Realfsen for regression/load testing of the assessment module.

Here are some guidelines on how to write automated tests with TCLWebtest. It is a joy to work with automated testing once you get the hang of it. We will use the "myfirstpackage" as an example.

Create the directory that will contain the test script and edit the script file. The directory location and file name are standards which are recognized by the automated testing package:

[$OPENACS_SERVICE_NAME www]$ mkdir /var/lib/aolserver/$OPENACS_SERVICE_NAME/packages/myfirstpackage/tcl/test
[$OPENACS_SERVICE_NAME www]$ cd /var/lib/aolserver/$OPENACS_SERVICE_NAME/packages/myfirstpackage/tcl/test

[$OPENACS_SERVICE_NAME test]$ emacs myfirstpackages-procs.tcl

Write the tests. This is obviously the big step :) The script should first call ad_library like any normal -procs.tcl file:

ad_library {
    ...
}

To create a test case you call aa_register_case test_case_name.. Once you've created the test case you start writing the needed logic. We'll use the tutorial package, "myfirstpackage," as an example. Let's say you just wrote an API for adding and deleting notes in the notes packages and wanted to test that. You'd probably want to write a test that first creates a note, then verifies that it was inserted, then perhaps deletes it again, and finally verifies that it is gone.

Naturally this means you'll be adding a lot of bogus data to the database, which you're not really interested in having there. To avoid this I usually do two things. I always put all my test code inside a call to aa_run_with_teardown which basically means that all the inserts, deletes, and updates will be rolled back once the test has been executed. A very useful feature. Instead of inserting bogus data like: set name "Simon", I tend to generate a random script in order avoid inserting a value that's already in the database:

set name [ad_generate_random_string]

Here's how the test case looks so far:

aa_register_case mfp_basic_test {
    My test
} {
    aa_run_with_teardown \
       -rollback \
       -test_code  {

       }
}

Now look at the actual test code. That's the code that goes inside -test_code {}. We want to implement test case API-001, "Given an object id from API-001, invoke mfp::note::get. Proc should return the specific word in the title."

      set name [ad_generate_random_string]
      set new_id [mfp::note::add -title $name]
      aa_true "Note add succeeded" [exists_and_not_null new_id]

To test our simple case, we must load the test file into the system (just as with the /tcl file in the basic tutorial, since the file didn't exist when the system started, the system doesn't know about it.) To make this file take effect, go to the APM and choose "Reload changed" for "MyFirstPackage". Since we'll be changing it frequently, select "watch this file" on the next page. This will cause the system to check this file every time any page is requested, which is bad for production systems but convenient for developing. We can also add some aa_register_case flags to make it easier to run the test. The -procs flag, which indicates which procs are tested by this test case, makes it easier to find procs in your package that aren't tested at all. The -cats flag, setting categories, makes it easier to control which tests to run. The smoke test setting means that this is a basic test case that can and should be run any time you are doing any test. (a definition of "smoke test")

Once the file is loaded, go to ACS Automated Testing and click on myfirstpackage. You should see your test case. Run it and examine the results.

Example

Now we can add the rest of the API tests, including a test with deliberately bad data. The complete test looks like:

ad_library {
    Test cases for my first package.
}

aa_register_case \
    -cats {smoke api} \
    -procs {mfp::note::add mfp::note::get mfp::note::delete} \
    mfp_basic_test \
    {
        A simple test that adds, retrieves, and deletes a record.
    } {
        aa_run_with_teardown \
            -rollback \
            -test_code  {
                set name [ad_generate_random_string]
                set new_id [mfp::note::add -title $name]
                aa_true "Note add succeeded" [exists_and_not_null new_id]
                
                mfp::note::get -item_id $new_id -array note_array
                aa_true "Note contains correct title" [string equal $note_array(title) $name]
                
                mfp::note::delete -item_id $new_id
                
                set get_again [catch {mfp::note::get -item_id $new_id -array note_array}]
                aa_false "After deleting a note, retrieving it fails" [expr $get_again == 0]
            }
    }

aa_register_case \
    -cats {api} \
    -procs {mfp::note::add mfp::note::get mfp::note::delete} \
    mfp_bad_data_test \
    {
        A simple test that adds, retrieves, and deletes a record, using some tricky data.
    } {
        aa_run_with_teardown \
            -rollback \
            -test_code  {
                set name {-Bad [BAD] \077 { $Bad}} 
                append name [ad_generate_random_string]
                set new_id [mfp::note::add -title $name]
                aa_true "Note add succeeded" [exists_and_not_null new_id]
                
                mfp::note::get -item_id $new_id -array note_array
                aa_true "Note contains correct title" [string equal $note_array(title) $name]
                aa_log "Title is $name"
                mfp::note::delete -item_id $new_id
                
                set get_again [catch {mfp::note::get -item_id $new_id -array note_array}]
                aa_false "After deleting a note, retrieving it fails" [expr $get_again == 0]
            }
    }


aa_register_case \
    -cats {web smoke} \
    -libraries tclwebtest \
    mfp_web_basic_test \
    {
        A simple tclwebtest test case for the tutorial demo package.
        
        @author Peter Marklund
    } {
        # we need to get a user_id here so that it's available throughout
        # this proc
        set user_id [db_nextval acs_object_id_seq]

        set note_title [ad_generate_random_string]

        # NOTE: Never use the aa_run_with_teardown with the rollback switch
        # when running Tclwebtest tests since this will put the test code in
        # a transaction and changes won't be visible across HTTP requests.
        
        aa_run_with_teardown -test_code {
            
            #-------------------------------------------------------------
            # Login
            #-------------------------------------------------------------
            
            # Make a site-wide admin user for this test
            # We use an admin to avoid permission issues
            array set user_info [twt::user::create -admin -user_id $user_id]
            
            # Login the user
            twt::user::login $user_info(email) $user_info(password)
            
            #-------------------------------------------------------------
            # New Note
            #-------------------------------------------------------------
            
            # Request note-edit page
            set package_uri [apm_package_url_from_key myfirstpackage]
            set edit_uri "${package_uri}note-edit"
            aa_log "[twt::server_url]$edit_uri"
            twt::do_request "[twt::server_url]$edit_uri"
            
            # Submit a new note

            tclwebtest::form find ~n note
            tclwebtest::field find ~n title
            tclwebtest::field fill $note_title
            tclwebtest::form submit
            
            #-------------------------------------------------------------
            # Retrieve note
            #-------------------------------------------------------------
            
            # Request index page and verify that note is in listing
            tclwebtest::do_request $package_uri                 
            aa_true "New note with title \"$note_title\" is found in index page" \
                [string match "*${note_title}*" [tclwebtest::response body]]
            
            #-------------------------------------------------------------
            # Delete Note
            #-------------------------------------------------------------
            # Delete all notes

            # Three options to delete the note
            # 1) go directly to the database to get the id
            # 2) require an API function that takes name and returns ID
            # 3) screen-scrape for the ID
            # all options are problematic.  We'll do #1 in this example:

            set note_id [db_string get_note_id_from_name " 
                select item_id 
                  from cr_items 
                 where name = :note_title  
                   and content_type = 'mfp_note'
            " -default 0]

            aa_log "Deleting note with id $note_id"

            set delete_uri "${package_uri}note-delete?item_id=${note_id}"
            twt::do_request $delete_uri
            
            # Request index page and verify that note is in listing
            tclwebtest::do_request $package_uri                 
            aa_true "Note with title \"$note_title\" is not found in index page after deletion." \
                ![string match "*${note_title}*" [tclwebtest::response body]]
            
        } -teardown_code {
            
            twt::user::delete -user_id $user_id
        }
    }


previous March 2024
Sun Mon Tue Wed Thu Fri Sat
25 26 27 28 29 1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31 1 2 3 4 5 6

Popular tags

17 , 5.10 , 5.10.0 , 5.9.0 , 5.9.1 , ad_form , ADP , ajax , aolserver , asynchronous , bgdelivery , bootstrap , bugtracker , CentOS , COMET , compatibility , CSP , CSRF , cvs , debian , docker , docker-compose , emacs , engineering-standards , exec , fedora , FreeBSD , guidelines , host-node-map , hstore
No registered users in community xowiki
in last 30 minutes
Contributors

OpenACS.org