Rich Web Experience

JSFOne

Private Events

Blogs

View all Blogs >>
  • Alex Miller

    Sr. Engineer with Terracotta Inc.

    Just a small plug for a nice paper by my favorite CS prof Ronald Loui called “In Pra more»

  • Richard Monson-Haefel

    VP of Developer Relations, Curl Inc.

    more»

  • Michael Nygard

    Agile technology leader and dynamicist

    O'Reilly is creating a new line of "community-authored" books. One of them is called "97 Thing Every Software Architect Should... more»

  • Ted Neward

    Enterprise, Virtual Machine and Language Wonk

    For those of you who were at the Cinncinnati NFJS show, please continue on to the next blog entry in your reader--you've already heard this.... more»

  • Jared Richardson

    Agile coach and co-author of Ship It

    Last week I was talking with a friend about a common ailment on development teams today. And it seems to be getting worse. Perhaps you've more»

  • Scott Leberknight

    Chief Architect at Near Infinity

    With all the hype this year about cloud computing and things like Amazon EC2/S3 as well as Google App Engine and Bigtable, you can feel it... more»

  • Jason Rudolph

    Author of Getting Started with Grails

    As we’ve seen over the last several weeks, it’s remarkably easy for code to earn the badge of 100% more»

  • Kenneth Kousen

    President of Kousen IT, Inc.

    In September, I’m very happy to be giving a couple of presentations at the more»

  • Stuart Halloway

    CEO of Relevance

    This is Part Two of a series of articles on Java.next. In Part Two, I will look at how Java.next languages interoperate with Java. more»

  • Howard Lewis Ship

    Creator of Tapestry and HiveMind

    According to Neal Gafter, the story for closures i more»

  • Erik Doernenburg

    Principal Consultant @ Thoughtworks

    The Spring framework has become ubiquitous in the Java world, and there are a large number of to more»

  • Neal Ford

    Application Architect at ThoughtWorks, Inc.

    It came to my attention recently that I had made a bad assumption about the Prod uctive Programmer book. My under more»

  • Mike Levin

    Software Developer specializing in Web2.0 websites

    more»

  • Matt Raible

    Creator of AppFuse and author of Spring Live

    The EhCache project appears to be having a very busy summer. EhCache 1.5.0 (a major new version) was rele more»

  • Pratik Patel

    Enterprise Architect

    In preparation for my upcoming No Fluff Just Stuff session in more»

  • Ryan Shriver

    Business and Technology Consulting

    more»

  • Mark Johnson

    Director of Consulting at CGI

    At the Columbus NFJS show held on July 25-27th during one of the BOF sessions Dave Bock, Scott Davis and I discussed unit tests vs functional... more»

  • Craig Walls

    Author of Spring in Action

    Just a short blog entry for today to let you know that I'll be speaking at the JavaM UG meeting in Dallas a wee more»

  • Joseph Nusairat

    Author of Beginning JBoss Seam & Co-Author of Beginning Groovy & Grails

    Well i am assuming Apress has the most random site in the world at times.But today only they have our recent book, Beginning Groovy & Grai more»

  • Venkat Subramaniam

    Founder of Agile Developer, Inc.

    I received a copy of "Beginning Groovy and Grails—From Novice to Professional" book by Apress written by more»

  • Andrew Glover

    Co-author of "Continuous Integration"

    Web Component Testing Screencast- my friend Rod Coffin demonstrates some interesting aspects re more»

  • Jeff Brown

    G2One Director Of North American Operations - Groovy and Grails Developer

    We are really excited to have a 3 day Groovy/Grails training event coming up in Chicago later this month. The training dates are August... more»

  • Brian Pontarelli

    Brian Pontarelli - founder of Inversoft

    I went to the 37 Signals event last night sponsored by CPB. The speake more»

  • Graeme Rocher

    Project Lead of the Grails Project & CTO of G2One

    I'll be giving a talk on the state of Grails at the London Groovy+Grails user group meeting on the 31st of July. more»

  • Nathaniel Schutta

    Author, speaker, software engineer focused on user interface design.

    I remember the first time I flew for business - I was working for a small consulting company and I was heading to Chicago for a few days of... more»

  • Keith Donald

    Lead of Spring Web and Creator of Spring Web Flow

    I am pleased to announce that Developing Rich Web Applications with Spring, a three-day bootcamp lead by SpringSource engineers on web... more»

  • Pramod Sadalage

    Co-author of "Refactoring Databases:Evolutionary Database Development"

    When creating a Foreign Key constraint on the database as shown below ALTER TABLE BOOK ADD (CONSTRAINT FK_BOOK_ more»

  • Vladimir Vivien

    Software Engineer / Consultant

    Judging from the list of features that will be included in NetBeans 6.5, more»

  • David Bock

    Principal Consultant, CodeSherpas Inc.

    I just spent this weekend speaking at the Ag ile IT Exchange conference i more»

  • Kirk Knoernschild

    Software Developer & Mentor

    I’ve published a summary of the OSGi survey results on the APS blog more»

  • Brian Goetz

    Author of Java Concurrency in Practice

    This surprised the heck out of me.  We recently finished a new TV room down in the basement.  We have a 50″ plasma TV, mounted on the... more»

  • Jason Harwig

    Senior Software Engineer at Near Infinity

    I was reading a blog entry at more»

  • Pete Behrens

    Organizational Agility Coach

    Marti nig & Associates Methods & Tools group recentl more»

  • John Heintz

    Principal Consultant with New Aspects of Software

    This post is to mostly keep track of the numerous blog threads going on about IDLs and schemas for REST. I find myself with more to say that... more»

  • Brian Sam-Bodden

    Java author, Ruby geek and Open Source Advocate

    In this installment we are going to build the Dashboard page of the Tempo application. T more»

  • Mark Fisher

    Spring Integration Lead

    In my recent post, I had mentio more»

  • Ron Bodkin

    Chief Software Architect, Quantcast

    I'm looking forward to speaking at The Rich Web Experience conference in San Jose next month. The event runs from September 7th through 9th.... more»

  • Mark Goodwin

    Web Application Security Specialist

    We've already looked at one of the two big problems posed by anti DNS pinning on Java applets; because there's rebinding on the applet and... more»

  • Scott Davis

    Author of "Groovy Recipes" & TDD Expert

    Every time I see a live show at the Denver Botanic more»

  • Romain Guy

    Java User Interface expert.

    more»

  • Ramnivas Laddad

    Author of AspectJ in Action, Principal at SpringSource

    InfoQ.com has published my AOP myths and realities talk recorded at a No Fluff Just Stuff conference. InfoQ.com founded by Floyd Marine more»

  • David Geary

    Author of Graphic Java and co-author of Core JSF

    The 2006 NFJS tour kicked off t more»

  • Kito Mann

    Editor-in-chief of JSF Central and the author of JSF in Action

    This podcast is an interview between JSFCentral editor-in-chief Kito D. Mann and Dan Allen, an independent software consultant, author, and... more»

  • Jason Hunter

    Author of Java Servlet Programming

    I just posted the JDOM 1.1 release for download. This release includes about 20 improvements and bug fixes. more»

Testing Anti-Patterns: Overspecification

Posted by: Jason Rudolph on 07/01/2008

Tests increasingly serve multiple roles in today’s projects. They help us design APIs through test-driven development. They provide confidence that new changes aren’t breaking existing functionality. They offer an executable specification of the application. But can we ever get to a point where we have too much testing?

Enough is Enough

Consider the following test that you might come across in an application with a less-than-ideal test suite. (We’ll pick on some badly-written Rails code for this example, but the ideas we’ll discuss are certainly not unique to just Rails or Ruby or even just to dynamic languages. Unfortunately, these problems are quite universal.)

  1. require File.dirname(__FILE__) + ‘/../test_helper’
  2.  
  3. class ProductsControllerTest < ActionController::TestCase
  4.   def test_something
  5.     product = Product.create(:name => "Frisbee", :price => 5.00)
  6.     get :show, :id => product.id
  7.     assert_response :success
  8.     product = assigns(:product)
  9.     assert_not_nil product
  10.     assert product.valid?
  11.     assert product.name == "Frisbee"
  12.     assert product.price == 5.00
  13.   end
  14. end


Whoa! That sure is a lot going on in a single test case. What exactly is it that we’re trying to test here? Let’s take a step back and see if we can figure it out. [1]

  • Line 5 - We start off by creating a new product. We give the product a name and a price and call #create. Since we’re in a Rails app, without looking elsewhere in the code, we can make an educated guess that Product is an ActiveRecord subclass and that calling #create will persist the product to the database.
  • Line 6 - We send an HTTP GET request to a #show method and pass along the ID of the product we just created. Since we’re in ProductsControllerTest, we can safely infer that we’re calling the #show action in the ProductsController class.
  • Line 7 - We assert that the controller action responded with HTTP status code 200 (i.e., success).
  • Lines 8-9 - We look for an object stored in the assigns hash (i.e., the hash of variables that will be available to the view template) with a key of :product and we assert that it’s not nil.
  • Line 10 - We verify that the product object satisfies all the product validation rules (though we can’t know what those rules are without taking a look at the Product class).
  • Lines 11-12 - We assert that the attribute values we provided when creating the product (in line 5) match the attribute values stored in the object we fetched from the assigns hash.

For a class called ProductsControllerTest, it sure feels like we’re doing a fair bit more than just testing the code we’d expect to find in ProductsController (assuming ProductsController conforms to the traditional responsibilities of a controller). Let’s take a look at the controller code to better understand exactly what it is that we’re testing.

  1. class ProductsController < ApplicationController
  2.   def show
  3.     @product = Product.find(params[:id])
  4.   end
  5. end


It turns out that our controller code is notably simpler than our test case would have led us to believe. The #show action does indeed stick to the expected behavior of a controller, and in this case, it’s able to provide that behavior in a single line of code (despite the 8 lines of test code currently employed above). That single line satisfies all of the expectations of the #show action: look up the product with the given ID and place the product object in an instance variable for use by the view.

If the #show action isn’t responsible for knowing how to successfully save a new product record into the database, why does our test case attempt to provide the data for creating a new product? What will happen to our test case when someone adds a new validation rule requiring that all products also include a quantity attribute? Unfortunately, the test as it’s currently written will break. And while a failing test is often a welcome warning that our changes have inadvertently broken a part of our application, it’s far from desirable for a model-level rule related to creating new records to cause a failure in a controller test related simply to displaying existing records. In short, we’ve given our test case too much responsibility, and in doing so, we’ve made it fragile.

We can see from the #show action above that not only is the controller not responsible for knowing how to create a valid product, it’s also not responsible for ensuring that a product’s attributes are properly populated when the record is read from the database. However, if we take another look at the last few lines of the test case, we might think otherwise. That brings us to the second problem with this particular test: it’s a rotten source of documentation for the code being tested. As we increasingly move toward tests as specifications of our application’s behavior, it’s vital that those specifications clearly communicate the expected behavior. As it’s currently implemented, the overspecification in this test case leaves the reader having to do way too much work to figure out the true expectations of the code being tested.

Communicate Essence

Let’s take another pass at writing a test for the #show action, this time with an eye toward removing the fragility of the previous test case and increasing the value of the test as a specification.

  1. require File.dirname(__FILE__) + ‘/../test_helper’
  2.  
  3. class ProductsControllerTest < ActionController::TestCase
  4.   def test_should_show_product
  5.     product = create_product
  6.     get :show, :id => product.id
  7.     assert_response :success
  8.     assert_equal product, assigns(:product)
  9.   end
  10. end


In this implementation, we’ve abstracted away the logic for creating a new product in line 5. We’ve defined a helper method for use by any and all tests in our application that have a need to create a new product. If and when the rules for successfully creating a new product change, we’ll update the #create_product method, and we won’t have to touch the code in ProductsController or ProductsControllerTest at all. [2]

As for the four assertions that appeared at the end of our first attempt at this test case, we’ve replaced those assertions with a single (stronger) assertion. Where we previously asserted that the assigns hash held a non-nil product, that the product was valid, and that its attributes matched the attributes used at the beginning of the test, we now simply verify that the product object in the assigns hash is equal to the product object that we created at the beginning of the test. That single line more accurately and more concisely expresses our expectation: that the product whose ID we provide in the request to the #show action is the same object that’s made available to the view.

By focusing our test case on the essence of the code under test, we’ve ended up with less test code to maintain. And with the extraneous setup and assertions removed, the remaining test code provides a clearer and less fragile specification of the behavior being tested. [3]

May I Take Your Order, Please?

In the previous example, we might have detected the overspecification by the significant mismatch between the lines of test code and the lines of production code, or seeing model-specific assertions in a controller-specific test may have caught our eye. But, overspecification comes in more subtle forms as well. Consider the following method provided by ActiveRecord::Base for fetching the list of columns that hold domain-specific content from a model class in Rails.

  1. # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
  2. # and columns used for single table inheritance have been removed.
  3. def content_columns
  4.   @content_columns ||= columns.reject { |c| c.primary || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
  5. end


To test this method, we’ll need a sample ActiveRecord model class. Let’s use the Topic model, which is backed by the following table.

  1. create_table :topics, :force => true do |t|
  2.   t.string   :title
  3.   t.string   :author_name
  4.   t.string   :author_email_address
  5.   t.datetime :written_on
  6.   t.time     :bonus_time
  7.   t.date     :last_read
  8.   t.text     :content
  9.   t.boolean  :approved, :default => true
  10.   t.integer  :replies_count, :default => 0
  11.   t.integer  :parent_id
  12.   t.string   :type
  13. end


In our first attempt at testing the #content_columns method, we might come up something similar to this:

  1. # (Naive) First Attempt
  2. def test_content_columns
  3.   content_columns      = Topic.content_columns
  4.   content_column_names = content_columns.map {|column| column.name}
  5.   assert_equal %w(title author_name author_email_address written_on bonus_time last_read content approved), content_column_names
  6. end


Can you spot the overspecification? To give you a hint, compare that test to the actual test employed in the ActiveRecord test suite:

  1. # The Real Deal
  2. def test_content_columns
  3.   content_columns        = Topic.content_columns
  4.   content_column_names   = content_columns.map {|column| column.name}
  5.   assert_equal 8, content_columns.length
  6.   assert_equal %w(title author_name author_email_address written_on bonus_time last_read content approved).sort, content_column_names.sort
  7. end


If you’ve ever written a test for something that returns an ordered list of items where the actual order either doesn’t matter or isn’t guaranteed, you’ve probably been bitten by this breed of overspecification, and there’s a good chance your solution matched the solution used in the ActiveRecord test code above. Because the #content_columns method doesn’t guarantee that the columns will be returned in any particular order, it’s inappropriate (and fragile) for our test to specify a certain order.

While the actual ActiveRecord solution above works, it too could be better. First, since we know that the two arrays in line 6 won’t be equal unless they have the same length, we can safely remove the extraneous length assertion in line 5. Second, when we see the calls to #sort on line 6, we’re forced to pause (if even for just a moment) to think about why it might be important to sort the two arrays. We don’t really want to sort the arrays; we’re just using that technique to get around the fact that order doesn’t matter to us, but it does matter when Ruby compares two arrays for equality. Since we really only want to assert that the arrays have the same elements, why not do exactly that?

  1. # Don’t Make Me Think
  2. def test_content_columns
  3.   content_columns        = Topic.content_columns
  4.   content_column_names   = content_columns.map {|column| column.name}
  5.   assert_same_elements %w(title author_name author_email_address written_on bonus_time last_read content approved), content_column_names
  6. end


By using an assertion like Shoulda’s assert_same_elements method, our test can clearly and concisely express the expected behavior.

Use It Wisely

Overspecification comes in many flavors, and the examples above in no way represent a comprehensive list. As developers, we frequently strive to write as little code as possible to accomplish the task at hand. When it comes to writing tests, we should very much keep that goal in mind as well. Good tests communicate the essence of the scenario being tested and nothing more. While I doubt a project will ever suffer from too much testing, it can certainly suffer from tests that specify too much.

Notes

[1] If this test seems absurd to you, good! Your ability to detect this type of overspecification will serve you well.

[2] The actual implementation of the #create_product method isn’t particularly pertinent in this discussion. (In the Rails space, there’s certainly no shortage of options.)

[3] With all the excessive thoroughness that went into overspecifying the the current behavior of the #show method, is it possible that we’ve been too distracted to identify other functionality that’s still missing from that method? In the next post, we’ll take a look at overspecification’s more lethargic cousin: underspecification.


This series is taken from the How To Fail With 100% Test Coverage talk. Check the schedule for a talk near you.

Thanks to Justin Gehtland, Muness Alrubaie, and Glenn Vanderburg for reading drafts of this post.


be the first to rate this blog


About Jason Rudolph

Jason Rudolph is a Principal at Relevance, a leading consultancy and training organization specializing in Ruby, Rails, Groovy, and Grails, and integrating them into enterprise environments. Jason has more than nine years of experience in developing software solutions for domestic and international clients of all sizes, including start-ups, Dow 30 companies, and government organizations.

Jason is the author of the highly-praised book, Getting Started with Grails, and speaks frequently at software conferences and user groups. Jason also contributes regularly to the open source community, both as an early committer to Grails, and also as a committer to the Streamlined framework and numerous other Ruby and Rails projects.

Jason holds a degree in Computer Science from the University of Virginia. You can find Jason online at http://jasonrudolph.com.

More About Jason »