Test driven development
DOING BASIC TESTS WITH FIREWATIR AND TDD
FireWatir is my preferred testing suite for developing web applications. Watir is a domain-language that describes things you do with a web browser: navigate to this page, find this input field, type in this text, find this button and click on it. Combinded with a unit testing framework like Ruby's Unit::Test, and you can write scripts that perform the basic functions of any application website and report back on what works and what fails.
The basis of Test-Driven Design is simple, but getting it right seems a little weird. You really do
have to write the tests first. So I'm going to start with the two absolutely simplest scripts
you've ever seen. I haven't even run the application builder script (rails
END {$ff.close if $ff} # close ff at completion of the tests
require 'rubygems'
require 'firewatir'
require 'test/unit'
require 'test/unit/ui/console/testrunner'
require 'net/http'
require 'json'
require 'optparse'
$options = {:address => 'http://localhost:8000/'}
OptionParser.new do |opts|
opts.banner = "Usage: example.rb [options]"
opts.on("-a", "--address", "Base address to test") do |v|
$options[:address] = v
end
end.parse!
This script is meant to be called by all testing scripts in the system. It does two things: defines all of the libraries that the tests will need, defines a default address where we expect to find the development server, and accepts an alternative address as needed.
FireWatir invokes and drives Firefox. The END clause at the top of this script banishes the window in which the tests were run when FireWatir has run all of its scripts.
Now, here's the most basic test you'll ever run. I keep this in a file called, surprise, basic_test.rb. This test has an important side-effect, which I'll discuss in a moment.
require 'setup'
class TC_Basic < Test::Unit::TestCase
include FireWatir
def test_01_server_presence
$ff = FireWatir::Firefox.start($options[:address])
sleep 2
# Do this rather than an assert; the tests cannot run if the server
# is not present.
if ($ff.title =~ /Page Load Error/) then
puts "Could not contact server"
exit
end
end
def test_02_homepage_presence
$ff.goto($options[:address])
assert($ff.title =~ /SecretProject/)
end
end
This file imports the setup script, which in turn does all of the argument parsing and setting up of libraries and stuff. So that's all taken care of. It also defines a single test case, and two tests within that test case. The first test is not really a test at all; it does the invocation of Firefox, sending it the address of the server. If it cannot contact the server, it does not "fail" as a test should, instead it halts the tests right there.
The second test is much more fundamental, and is an accurate test. It sets one variable (the home page URL), and asserts one true thing about the operation (the page's title is set correctly)
One thing to note about these tests: I haven't actually written the code these tests will test yet. That's important. My job, now, is to make these tests pass. Once I've done that, I will go on to other things: logging in and registration, and then user interaction.
Each test will define one thing the user can actually do: list the user's documents, pick a document, edit some detail of the document, save the document, delete the document, etc. etc. That's what TDD is for. TDD is part of your documentation: it tells future testers what you expected your program to do well.