Killer Web Development

by Marco Laspe

4.3 Testing the Tukker App

Equipped with the web application structure, the user stories and the design sketches we will now start creating the tukker app. This time we will use a different approach called Test-Driven-Development.

Wikipedia says:

Test-driven development

Test-driven development (TDD) is a software development process that relies on the repetition of a very short development cycle: first the developer writes a failing automated test case that defines a desired improvement or new function, then produces code to pass that test and finally refactors the new code to acceptable standards.

In TDD you first write a test, that has to fail and then write the production code, that passes the test. For example with our home page we will write a test that checks if there is a "Register Now!" link an if it's clickable. This way we simulate an user that comes to our home page.

Why Test-Driven Development

Imagine, you develop the app for John based on the user stories we created. You develop the code for the static pages, then John tests this pages. He shows you a few errors, you fix them. After that you begin with the rest of the web application, the new code affects the old one, errors creep in. Next time John tests the tukker app he an sees new errors on the static pages. He tells you again - a bit disappointed. You wonder: "But I didn't made any changes to the static pages."

TDD helps you to notice these errors and to be sure that your code is working. With tests you are free to optimize your code or to add new functionality - your test will let you know if something went wrong.

Writing the first test

Before we are writing our first test, we need to create our tukker app. Open a terminal and go to your web2py installation (this should seem familiar to you):

1.
2.
3.
4.
$ cd /your/web2py/folder # on Windows use backslashes
$ ./web2py.py -S tukker # web2py.exe on Windows and web2py.app on OS X
$ cd applications/tukker/
$ gedit .hgignore

again add the following code to .hgignore, save and close gedit:

1.
2.
3.
4.
5.
6.
7.
syntax: glob
cache/**
databases/**
errors/**
sessions/**
*
˜
*.pyc

of course you can copy the .hgignore file from the pitch application; back in the terminal initialize the Mercurial repository:

1.
2.
3.
$ hg init 
$ hg add .
$ hg commit -m"Initial commit"

Push the repository to BitBucket (optional)

Again you can push your repository to BitBucket, this is optional, but think about it, it's a free backup of your code.

Create a new repository called tukker or anything you like best. In the terminal:

1.
2.
3.
$ hg push https://your_username@bitbucket.org/your_username/tukker
...
$ Password: [your password]

Install the selenium testing framework

Now that we created the tukker, we will first setup our testing environment with Selenium. Selenium allows us to tell Firefox to behave like a real user - it's web browsing on autopilot.

In the terminal go to your application directory (On Windows you should probably download the package in your web browser and extract it to the lib directory and rename the directory to selenium, be sure to have a program that can handle tar files.):

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
$ cd /your/web2py/folder/applications/tukker # on Windows use backslashes
$ mkdir fts # fts stands for functional tests, on windows md
$ cd fts
$ mkdir lib # on windows md lib
$ cd lib
$ curl -O http://pypi.python.org/packages/source/s/selenium/selenium-2.33.0.tar.gz # This does not works on windows
$ tar zxf selenium-2.33.0.tar.gz # This works not on windows
$ rm selenium-2.33.0.tar.gz # on Windows del selenium-2.33.0.tar.gz
$ cp -r selenium-2.33.0/common/selenium ./ # on Windows use copy instead of cp
$ rm -r selenium-2.33.0 # again use del on Windows

now we need to create two files: __init__.py in the fts directory, functional_tests.py in the tukker application base directory. Let's fire up gedit:

1.
$ gedit __init__.py

in the gedit windows now press Ctrl+S to save the empty file. __init__.py tells the Python interpreter that the directory fts is a package, this is important for our next file functional_tests.py. In the gedit window press Ctrl+N, after the new tab gets created fill in the following content - this file runs our tests for us, don't worry if you don't understand this:

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.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
#!/usr/bin/env python
try: import unittest2 as unittest #for Python <= 2.6
except: import unittest
import sys, urllib2
sys.path.append('./fts/lib')
from selenium import webdriver
import subprocess
import sys
import os.path

ROOT = 'http://localhost:8001'

class FunctionalTest(unittest.TestCase):

@classmethod
def setUpClass(self):
self.web2py = start_web2py_server()
self.browser = webdriver.Firefox()
self.browser.implicitly_wait(1)

@classmethod
def tearDownClass(self):
self.browser.close()
self.web2py.kill()

def get_response_code(self, url):
"""Returns the response code of the given url

url the url to check for
return the response code of the given url
"""
handler = urllib2.urlopen(url)
return handler.getcode()


def start_web2py_server():
#noreload ensures single process
print os.path.curdir
return subprocess.Popen([
'python', '../../web2py.py', 'runserver', '-a "passwd"', '-p 8001'
])

def run_functional_tests(pattern=None):
print 'running tests'
if pattern is None:
tests = unittest.defaultTestLoader.discover('fts')
else:
pattern_with_globs = '*%s*' % (pattern,)
tests = unittest.defaultTestLoader.discover('fts', pattern=pattern_with_globs)

runner = unittest.TextTestRunner()
runner.run(tests)

if __name__ == '__main__':
if len(sys.argv) == 1:
run_functional_tests()
else:
run_functional_tests(pattern=sys.argv[1])

Test if Selenium works as exspected

Its time to test drive our Selenium setup to check if everything is working as we exspect it. Create a new file test_home.py in the fts directory:

1.
2.
$ cd fts
$ gedit test_static_pages.py

fill in the following content:

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
from functional_tests import FunctionalTest, ROOT

class TestHomePage (FunctionalTest):

def test_can_view_home_page(self):

# John opens his browser and goes to the home-page of the tukker app
self.browser.get(ROOT + '/tukker/')

# He's looking at the homepage and sees the Heading "Messages With 300 Chars"
body = self.browser.find_element_by_tag_name('body')
self.assertIn('Messages With 300 Chars', body.text)

save the file.

What does this test file do? In line 8 we open the URL http://localhost:8001/tukker/, then in line 11 und 12 we look for the text Messages With 300 Chars in the body of the webpage.

Let's see if the our tukker app will pass this test? In the terminal start the functional tests:

1.
$ python functional_tests.py

a Firefox window should appear and you should see the following output in the terminal:

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
running tests
.
web2py Web Framework
Created by Massimo Di Pierro, Copyright 2007-2011
Version 1.99.3 (2011-12-09 16:18:03) stable
Database drivers available: SQLite3, pymysql, CouchDB
Starting hardcron...
please visit:
http://127.0.0.1:8001
use "kill -SIGTERM 16625" to shutdown the web2py server
F
======================================================================
FAIL: test_can_view_home_page (test_static_pages.TestHomePage)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/macco/Dropbox/web2pytutorial/web2py/applications/tukker/fts/test_static_pages.py", line 12, in test_can_view_home_page
self.assertIn('Messages With 300 Chars', body.text)
AssertionError: 'Messages With 300 Chars' not found in u"Login | Register | Lost password?\nHome\nweb2py\n\xbb\nWelcome to web2py!\nTukker\ncustomize me!\nIndex\nShare\nHello World\nHow did you get here?\nYou are successfully running web2py\nYou visited the url /tukker/\nWhich called the function index() located in the file web2py/applications/tukker/controllers/default.py\nThe output of the file is a dictionary that was rendered by the view web2py/applications/tukker/views/default/index.html\nYou can modify this application and adapt it to your needs\nAdministrative Interface\nDon't know what to do?\nOnline examples\nweb2py.com\nDocumentation\nCopyright \xa9 2011"

----------------------------------------------------------------------
Ran 1 test in 10.792s

FAILED (failures=1)

of course the test fails, as we exspected and wanted it. It seems our testing framework is working; let's commit the changes we made to the repository:

1.
2.
3.
$ hg add fts/*
$ hg add functional_tests.py
$ hg commit -m"installed and setup Selenium"

Fix our first test

Ok now we know that our testing framework is working, we need to fix our first test.

Think about it. How would you fix this? What do we know?

  1. What do we need to change? Some text in the body of the home page needs to be "Messages With 300 Chars"
  2. Where do we find the files for fixing this? Remember the Model-View-Controller? The controller for the home page was the function 'index' in the file 'default.py', the corresponding view was index.html in views/default/index.html.

We need the include the text in our view. So open it and change the headline:

1.
$ gedit views/default/index.html

search for the line that starts with <h4> and change the text in this line to Messages With 300 Chars:

1.
2.
3.
4.
5.
6.
...
<
h3>{{=message}}</h3>

<h4>{{=T('Messages With 300 Chars')}}</h4>
<ol>
...

now let's check if we pass our functional test:

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
$ python functional_tests.py
running tests
.
web2py Web Framework
Created by Massimo Di Pierro, Copyright 2007-2011
Version 1.99.3 (2011-12-09 16:18:03) stable
Database drivers available: SQLite3, pymysql, CouchDB
Starting hardcron...
please visit:
http://127.0.0.1:8001
use "kill -SIGTERM 3282" to shutdown the web2py server
.
----------------------------------------------------------------------
Ran 1 test in 14.151s

OK

We can see that 1 test was run, later we will run much more tests. If you see the Ok at the end of the terminal we passed our first test. Congrats!. You've taken an important step in becoming a serious programmer.

Again let's commit our changes and then push our repository to BitBucket that we have a backup if we need one:

1.
2.
$ hg commit -m"Passed first functional test"
$ hg push https://bitbucket.org/killerwebdev/tukker

Books often read by web2py and Python experts:

Comments

  1. So... I had to figure out how to install selenium on Cygwin (download and run "ez-setup.py" to get easy-install, then "easy-install selenium") and now running functional_tests.py gives me: running tests Traceback (most recent call last): File "functional_tests.py", line 57, in <module> run_functional_tests() File "functional_tests.py", line 47, in run_functional_tests tests = unittest.defaultTestLoader.discover('fts') AttributeError: 'TestLoader' object has no attribute 'discover'

  2. That should be: running tests Traceback (most recent call last): File "functional_tests.py", line 57, in <module> run_functional_tests() File "functional_tests.py", line 47, in run_functional_tests tests = unittest.defaultTestLoader.discover('fts') AttributeError: 'TestLoader' object has no attribute 'discover'

  3. Looks like you need Python 2.7 for unnittest.defaultTestLoader.discover(). Cygwin currently has 2.6

  4. you have amigius directory refernces python functional_tests.py Traceback (most recent call last): File "functional_tests.py", line 4, in <module> from selenium import webdriver ImportError: No module named selenium This do not help much: "don't worry if you don't understand this:" $ cd /your/web2py/folder/applications/tukker # on Windows use backslashes $ mkdir fts # fts stands for functional tests, on windows md $ cd fts $ mkdir lib # on windows md lib $ cd lib are yiou shure it is lib or rather /usr/ib ? and if so then $ or # ? by ayour recipe it make: /..x../tuker ├── errors ├── fts │   ├── __init__.py │   └── lib │   ├── selenium │   │   ├── COPYING │   │   ├── CREDITS.txt │   │   ├── go │   │   ├── go.bat │   │   ├── MANIFEST.in │   │   ├── PKG-INFO │   │   ├── properties.yml │   │   ├── py │   │   │   ├── CHANGES │   │   │   ├── README │   │   │   └── selenium │   │   │   ├── common │   │   │   │   ├── exceptions.py │   │   │   │   └── __init__.py │   │   │   ├── __init__.py │   │   │   ├── selenium.py │   │   │   └── webdriver │   │   │   ├── chrome │   │   │   │   ├── __init__.py │   │   │   │   ├── service.py │   │   │   │   └── webdriver.py

Leave a Reply

Required fields are marked *.