Testing the AddressBook

Testing the address book requires some techniques that we didn't need for ChatStat. Most of the AddressBook is hidden behind a login screen, so we'll have to log in for testing the application. It also requires us to fill out a lot of forms. Finally, it has a JavaScript interface that we can't test without a live web browser. In this section, we'll see how to test those parts of the application.

Logging in

We'll start by adding a test database module to AddressBook. This is almost an exact copy of the one we created for ChatStat, but we add a convenience function for creating a test user and logging in as that user:

package AddressBook::Test::Database::Live;
use strict;
use warnings;
use AddressBook::Schema::AddressDB;
use Directory::Scratch;
use YAML qw(DumpFile); use FindBin qw($Bin);
use base 'Exporter';
our @EXPORT = qw/log_in schema/;
my $schema;
my $config;
BEGIN {
my $tmp = Directory::Scratch->new;
my $db = $tmp->touch('db'),
my $dsn = "DBI:SQLite:$db";
$schema = AddressBook::Schema::AddressDB->connect($dsn);
$schema->deploy;
$config = "$Bin/../addressbook_local.yml";
DumpFile($config, {'Model::AddressDB' => {connect_info =>
[$dsn]}});
}
sub schema { $schema };
sub log_in { # counts as 4 tests
my $mech = shift;
my $person = schema()->
resultset('People')->create({ firstname => 'Test',
lastname => 'User',
});
my $user = schema()->resultset('User')->
create({ username => 'test_user',
password => 'ABCDEFGHI',
person => $person,
});
$mech->get_ok('http://localhost/login'),
$mech->form_name('login'),
$mech->field(username => 'test_user'),
$mech->field(password => 'ABCDEFGHI'),
$mech->click('_submit'),
$mech->content_like(qr/Logged in successfully/);
$mech->content_like(qr/Logged in as/);
$mech->content_like(qr/Test User/);
return $user;
}
END { unlink $config };
1;

The important part of this module is the log_in function. It creates a test user and uses Mechanize's form functionality to visit the login page. Enter the username and password, "click" submit and check that the user logged in successfully.

Now it's easy to start writing tests. Let's start by testing the login and logout functionality in a file called t/live-login.t as follows:

use strict;
use warnings;
use Test::More tests => 7;
use AddressBook::Test::Database::Live;
use Test::WWW::Mechanize::Catalyst 'AddressBook';
my $schema = schema();
my $mech = Test::WWW::Mechanize::Catalyst->new;
log_in($mech);
$mech->follow_link_ok({url_regex => qr/logout/},
'follow logout link'),
$mech->content_like(qr/Not logged in/);
$mech->content_unlike(qr/Test User/);

All we do here is create the schema and mech objects, try to log in (which is 4 assertions), and then try to log out. It's important to note that Mechanize is just like a regular web browser, and cookies and sessions are preserved. (There are even back, forward, and reload buttons that Mech can click.)

Testing forms

Now that we have a logged-in user, let's try creating a person, then adding an address for him. To get started, let's add the standard header to t/live-add- person.t as follows:

use strict;
use warnings;
use Test::More tests => 17;
use AddressBook::Test::Database::Live;
use Test::WWW::Mechanize::Catalyst 'AddressBook';
my $schema = schema();
my $mech = Test::WWW::Mechanize::Catalyst->new;

We'll continue by adding the user that log_in creates to the editor and viewer roles, so the ACL rules don't prevent the test from running:

# give user a role that lets him edit
my $user = log_in($mech); # 4 tests
my $viewer = $schema->resultset('Role')->create({ role => 'editor' });
my $editor = $schema->resultset('Role')->create({ role => 'viewer' });
$schema->resultset('UserRole')->create({ user => $user->id,
role => $editor->id });
$schema->resultset('UserRole')->create({ user => $user->id,
role => $viewer->id });

Now the test user can access and edit any part of the application. Let's try adding a new person by clicking links and filling out forms:

# now try adding a new person
$mech->get_ok('http://localhost/'),
$mech->follow_link_ok({ text => 'Add a new person' });
$mech->content_like(qr/Adding a new person/, 'on the add person page'),
$mech->form_number(1); # first form
$mech->field(firstname => 'Avery'),
$mech->field(lastname => 'Newperson'),
$mech->click('_submit'),
$mech->content_like(qr/Added Avery Newperson/, 'added a very new person'),

Now there should be a new person, Avery Newperson, in the database. Let's retrieve a person record from the database to make sure that the interface is actually updating the database:

# find ID of new person
my $person = $schema->resultset('People')->
find(qw/Avery Newperson/, { key => 'name' });
my $id = $person->id;
ok $person->id > 0, 'got a person';

Now that we have the ID, let's add a home address for Avery:

# add an address
$mech->get_ok("http://localhost/address/add/$id");
$mech->content_like(qr/Adding a new address s* for Avery Newperson/);
$mech->form_number(1);
$mech->field(location => 'Home'),
$mech->field(postal => 'This is the postal address.'),
$mech->field(phone => '123-456-7890'),
$mech->field(email => '[email protected]'),
$mech->click('_submit'),
$mech->content_like(qr/Added new address for Avery Newperson/);
$mech->content_like(qr/This is the postal address[.]/);

If that worked, the $person above should now have some related addresses as follows:

# see if that's in the database
my $address = $person->search_related('addresses')->first;
is $address->postal, 'This is the postal address.';
my $aid = $address->id;

Let's conclude by deleting the address as follows:

# now delete the address
$mech->follow_link_ok({url_regex => qr{address/delete/$aid} },
'blow away address'),
$mech->content_like(qr/Deleted Avery Newperson's Home address/);
$mech->content_unlike(qr/This is the postal address[.]/);

Now you can run the test with make test or prove:

$ prove -Ilib t/live-add-person.t t/live-add-person....ok
All tests successful.
Files=1, Tests=17, 2 wallclock secs ( 1.68 cusr + 0.08 csys = 1.76 CPU)

What used to be clicking through the interface manually is now an automatic test that can tell you in just a few seconds whether your application works. For a real application, you'll want to test everything, but this test is a good start.

Good tests will not only include tests of common cases but also edge cases. For example, if a field requires a number, what happens if the user enters "0 but true" (not a number) or "0e0" (a number)? Edge cases are where bugs show up, so try to be tricky and eliminate them in development. It's much more fun to fix a bug that a test finds as opposed to one that causes your site to go down at three in the morning.

As an aside, if you'd like to have your tests written automatically, you can install a CPAN module called HTTP::Recorder. HTTP::Recorder will act as a proxy between your application and the browser, and will translate any clicks and form submissions into code that you can paste into your Mechanize-based test. It's good for recording activity during user testing. If someone runs into a bug, you'll have a detailed log of what they clicked. You can copy that into your test file, add some assertions and you'll be able to fix the bug without having the user click through the interface to find the bug again.

Testing JavaScript

The remaining part of the AddressBook to be tested is the jemplate-based in-place address editor. To test this, we'll use a tool called Selenium RC (http://www.openqa.org/) to run tests directly in the browser. Selenium is a tool that sits between your application, the test script, and the web browser. The test script sends the Selenium server commands to run inside the web browser. The web browser then executes those commands, and returns the results to the Selenium server and then your test script.

It can be hard to get it up and running because it requires the Java Runtime Environment (JRE) and a working web browser. It is somewhat experimental, and the author has only tested on Cygwin with Firefox and IE and Debian GNU/Linux with Firefox.

Developing Selenium tests is pretty easy, thanks to the Selenium IDE Firefox extension available at http://www.openqa.org/selenium-ide/. It turns Firefox into an IDE that can record and edit tests, and save them to the Perl format.

To create a test against the Catalyst application, we'll start by recording the basic outline of the test with Selenium IDE.

To get started, start the AddressBook application, then open the IDE, and click the red record button. Then enter http://localhost:3000/ in the address bar and wait for the page to load. Log in as the test user, navigate to the list of all addresses, click the first edit link to edit the address in place, and then change each of the fields to a new value. Click on Submit to store the changes, and then highlight all the text you just entered (now as part of the static page). Then, right-click and select verifyTextPresent for each field, as shown in the next screenshot:

Testing JavaScript

You should now have a Selenium test that appears as shown in the following screenshot:

Testing JavaScript

Now you can select all the records, change the clipboard format to Perl, and copy-and-paste the result into t/selenium-edit.t. We're going to edit the result a bit, though.

Here's what you get right out of the Selenium IDE:

$sel->open_ok("/");
$sel->click_ok("link=Log in");
$sel->wait_for_page_to_load_ok("30000");
$sel->type_ok("username", "test_user");
$sel->type_ok("password", "ABCDEFGHI");
$sel->click_ok("login_submit");
$sel->wait_for_page_to_load_ok("30000");
$sel->click_ok("link=Look at all people");
$sel->wait_for_page_to_load_ok("30000");
$sel->type_ok("location", "New Address");
$sel->type_ok("postal", "This is a test");
$sel->type_ok("phone", "123-456-7890");
$sel->type_ok("email", "[email protected]");
$sel->click_ok("_submit");
$sel->wait_for_page_to_load_ok("30000");
$sel->is_text_present_ok("New Address");
$sel->is_text_present_ok("This is a test");
$sel->is_text_present_ok("123-456-7890");
$sel->is_text_present_ok("[email protected]");
$sel->click_ok("//a[@onclick='show_address_edit_form(6);return false;']");

To make this test run exactly as in the IDE, you just need to use Test::WWW::Selenium::Catalyst by adding something like the following code to the top of the file:

use strict;
use warnings;
use Test::More tests => 20;
use AddressBook::Test::Database::Live;
use Test::WWW::Selenium::Catalyst 'AddressBook';
my $sel = Test::WWW::Selenium::Catalyst->start;

There are still a few tweaks that we need to make the test work. We need to replace the hard-coded 6 with the ID of a sample address we create. We'll start by creating the necessary database records right before we create the $sel object:

my $person = schema()->
$sel objectresultset('People')->create({ firstname => 'Test', lastname => 'User',
});
my $user = schema()->resultset('User')->
create({ username => 'test_user',
password => 'ABCDEFGHI',
person => $person,
});
my $address = schema()->resultset('Addresses')->
create({ person => $person->id,
location => 'Home',
postal => 'Postal address.',
phone => 'Phone',
email => '[email protected]',
});
my $aid = $address->id;

Then you just need to replace the 6 on the last line with $aid. As soon as you do that, you should be able to run the test from prove. A web browser window will pop up and run the actions that your test script dictates. If everything goes well, you should see the "All tests successful" message at the very end. After it's working, you should add some more tests to see that the database was modified as you expected it to be.

If all doesn't go well, take a look at the manual page for Test::WWW::Selenium::Catalyst.

Once it's up and running you should find it a valuable testing tool, especially as the Selenium IDE makes it easy for anyone to record test cases.

Selenium isn't the only way to test JavaScript. You can also write Test::More style tests in JavaScript by using the Test::Simple module from the JSAN (the JavaScript version of the CPAN). Check it out at http://openjsan.org/doc/t/th/theory/ Test/Simple/0.21/.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.67.70