Testing GUIs Part I: RoR.

Posted by Uncle Bob on 01/13/2007

Testing GUIs is one of the the holy grails of Test Driven Develoment (TDD). Many teams who have adopted TDD for other parts of their projects have, for one reason or another, been unable to adequately test the GUI portion of their code.

In this series of article I will show that GUI testing is a solved problem. Over the years the TDD community has produced and accumulated tools, frameworks, libraries, and techniques that allow any team to test their GUI code as fully as any other part of their code.

Testing GUIs Part I: Ruby on Rails

In the world of web development, no community has solved the problem of GUI testing better than the Ruby on Rails community. When you work on a rails project, testing the GUI is simply de-rigeur. The rails framework provides all the necessary tools and access points for testing all aspects of the application, including the generation of HTML and the structure of the resulting web pages.

Web pages in rails are specified by .rhtml files that contain a mixture of HTML and ruby code similar to the way Java and HTML are mixed in .jsp files. The difference is that .rhtml files are translated at runtime rather than being compiled into servlets the way .jsp pages are. This makes it very easy for the rails environment to generate the HTML for a web page outside of the web container. Indeed, the web server does not need to be running.

This ease and portability of generating HTML means that the rails test framework merely needs to set up the variables needed by the ruby scriptlets within the .rhtml files, generate the HTML, and then parse that HTML into a form that the tests can query.

A typical example.

The tests query the HTML using an xpath-like syntax coupled with a suite of very powerful assertion functions. The best way to understand this is to see it. So here is a simple file named: autocomplete_teacher.rhtml.

<ul class="autocomplete_list">

<% @autocompleted_teachers.each do |t| %>

<li class="autocomplete_item"><%= "#{create_name_adornment(t)} #{t.last_name}, #{t.first_name}"%></li>

<% end %>

</ul>

You don’t have to be a ruby programmer to understand this. All it is doing is building an HTML list. The Ruby scriptlet between <% and %> tokens simple loops for each teacher creating an <li> tag from an “adornment”, and the first and last name. (The adornment happens to be the database id of the teacher in parentheses.) A simple test for this .rhtml file is:

 def test_autocomplete_teacher_finds_one_in_first_name

   post :autocomplete_teacher, :request=>{:teacher=>"B"}

   assert_template "autocomplete_teacher"

   assert_response :success

   assert_select "ul.autocomplete_list" do

     assert_select "li.autocomplete_item", :count => 1

     assert_select "li", "(1) Martin, Bob"

   end

 end

It should not come as any surprise that this test runs in a test environment in which the database has been pre-loaded with very specific data. For example, this test database always has “Bob Martin” being the first row (id=1) in the Teacher table.

The assert_select function is very powerful, and allows you to query large and complex HTML documents with surgical precision. Although this example give you just a glimpse of that power, you should be able to see that the rails testing scheme allows you to test that all the scriptlets in an .rhtml file are behaving correctly, and are correctly extracting data from the variables set by the controller.

An example using RSpec and Behavior Driven Design.

What follows is a more significant rails example that uses an alternate testing syntax known as Behavior Driven Design (BDD). The tool that accepts this syntax is called RSpec.

Imagine that we have a page that records telephone messages taken from teachers at different schools. Part of that page might have an .rhtml syntax that looks like this:

<h1>Message List</h1>

<table id="list">

 <tr class="list_header_row">

   <th class="list_header">Time</th>

   <th class="list_header">Caller</th>

   <th class="list_header">School</th>

   <th class="list_header">IEP</th>

 </tr>

<%time_chooser = TimeChooser.new%>

<% for message in @messages %>

 <%cell_class = cycle("list_content_even", "list_content_odd")%>

 <tr id="list_content_row">

   <td id="time"   class="<%=cell_class%>"><%=h(time_chooser.format_time(message.time)) %></td>

   <td id="caller" class="<%=cell_class%>"><%=h person_name(message.caller) %></td>

   <td id="school" class="<%=cell_class%>"><%=h message.school.name %></td>

   <td id="iep"    class="<%=cell_class%>"><%=h (message.iep ? "X" : "") %></td>

 </tr>

<% end %>

</table>

Clearly each message has a time, caller, school, and some kind of boolean field named “IEP”. We can test this .rhtml file with the following RSpec specification:

context "Given a request to render message/list with one message the page" do

 setup do

   m = mock "message"

   caller = mock "person",:null_object=>true

   school = mock "school"

   m.should_receive(:school).and_return(school)

   m.should_receive(:time).and_return(Time.parse("1/1/06"))

   m.should_receive(:caller).any_number_of_times.and_return(caller)

   m.should_receive(:iep).and_return(true)

   caller.should_receive(:first_name).and_return("Bob")

   caller.should_receive(:last_name).and_return("Martin")

   school.should_receive(:name).and_return("Jefferson")

   assigns[:messages]=[m]

   assigns[:message_pages] = mock "message_pages", :null_object=>true

   render 'message/list'

 end

 specify "should show the time" do

   response.should_have_tag :td, :content=>"12:00 AM 1/1", :attributes=>{:id=>"time"}

 end

 specify "should show caller first and last name" do

   response.should_have_tag :td, :content=>"Bob Martin", :attributes=>{:id=>"caller"}

 end

 specify "should show school name" do

   response.should_have_tag :td, :content=>"Jefferson", :attributes=>{:id=>"school"}

 end

 specify "should show the IEP field" do

   response.should_have_tag :td, :content=>"X",:attributes=>{:id=>"iep"}

 end

end

I’m not going to explain the setup function containing all that mock stuff you see at the start. Let me just say that the mocking facilities of RSpec are both powerful and convenient. Actually you shouldn’t have too much trouble understanding the setup if you try; but understanding it is not essential for this example. The interesting testing is in the specify blocks.

You shouldn’t have too much trouble reading the specify blocks. You can understand all of them if you understand the first. Here is what it does:

HTML Testing Discipline and Strategy

One of the reasons that GUI testing has been so problematic in the .jsp world is that the java scriptlets in those files often reach out into the overall application domain and touch code that ties them to the web container and the application server. For example, if you make a call from a .jsp page to a database gateway, or an entity bean, or some other structure that is tied to the database; then in order to test the .jsp you have to have the full enabling context running. Rails gets away with this because the enabling context is lightweight, portable, and disconnected from the web container, and the live database. Even so, rails applications are not always as decoupled as they should be.

In Rails, Java, or any other web context, the discipline should be to make sure that none of the scriptlets in the .jsp, .rhtml, etc. files know anything at all about the rest of the application. Rather, the controller code should load up data into simple objects and pass them to the scriptlets (typically in the attributes field of the HttpServletRequest object or its equivalent). The scriptlets can fiddle with the format of this data (e.g. data formats, money formats, etc.) but should not do any calculation, querying, or other business rule or database processing. Nor should the scriptlets navigate through the model objects or entities. Rather the controller should do all the navigating, gathering, and calculating and present the data to the scriptlets in a nice little package.

If you follow this simple design discipline, then your web pages can be generated completely outside of the web environment, and your tests can parse and inspect the html in a simple and friendly environment.

Conclusion

I’ll have more to say about RSpec in a future blog. BDD is an exciting twist on the syntax of testing, that has an effect far greater than the simple syntax shift would imply.

I hope this article has convinced you that the rails community has solved the problem of testing HTML generation. This solution can be easily extrapolated back to Java and .NET as future blogs in this series will show.

Clearly the problem of testing Javascript, and the ever more complex issues of Web2.0 and GTK are not addressed by this scheme. However, there are solutions for Javascript that we will investigate in future blogs in this series.

Finally, this technique does not test the integration and workflow of a whole application. Again, those are topics for later blogs.

I hope this kickoff blog has been informative. If you have a comment, question, or even a rant, please don’t hesitate to add a comment to this blog.

Comments

Leave a response