:jasonrudolph => :blog

puts Blog.new(”nonsense”)

Archive for July, 2008

Testing Anti-Patterns: The Ugly Mirror

Posted by Jason Rudolph on 30th July 2008

When you’re able to write a test, nay, a spec, that not only verifies your code’s functionality, but also clearly communicates its intent, you’ve got a real win on your hands. It’s a win when you’re first writing out your test cases as you’re TDD’ing your way to the solution. It’s a win to the person providing the peer review of your code. It’s a win for you (again) when you have to revisit (and relearn) the code six weeks from now. And it’s a win for the guy that has to touch this code yet again when you’re long gone. [1] But when you occasionally find yourself staring at a spec that looks exactly like the code under test, there’s surprisingly little win left to enjoy.

Consider, for example, the following Ruby Struct tasked with building a friendly string representation of a user’s name and e-mail address.

  1. User = Struct.new(:first_name, :last_name, :email) do
  2.   def to_s
  3.     "#{last_name}, #{first_name} <#{email}>"
  4.   end
  5. end


I ran across code similar to this example during a recent code review, and when I got to the corresponding test …

  1. require "test/unit"
  2.  
  3. class UserTest < Test::Unit::TestCase
  4.   def test_to_s_includes_name_and_email
  5.     user = User.new("John", "Smith", "jsmith@example.com")
  6.     assert_equal "#{user.last_name}, #{user.first_name} <#{user.email}>", user.to_s
  7.   end  
  8. end


… I couldn’t help but feel like I was staring right back at the very code that was supposedly being tested.

Ahhhhhhhh! [2]

This ugly mirror of the production code leaves much to be desired. Sure, thanks to the name of the test, we get the general idea that to_s will output some combination of the user’s name and e-mail address. But when we look at the assertion on line 6, we’re forced to mentally reverse engineer the code in order to see through to the underlying requirement. When it comes to quickly and clearly communicating the intent of the underlying code, this test falls far short of its potential. As it’s implemented above, we’re probably better off reviewing the production code itself than bothering to look at the test at all.

Of course, it doesn’t have to be this way, but it’s not unusual to stumble across this kind of test. In fact, it’s remarkably common to see this kind of test when the test is written after the production code is implemented. It’s as if once a developer has written the code to perform a task, the guts of the code can’t be unseen, and as a consequence, the tests often end up reflecting those inner workings instead of the desired end result.

No Mental Juggling Necessary

On the other hand, when we’re developing test-first, we start out with the end-user requirements in mind, and it’s easy to make sure our tests communicate those requirements.

  1. require "test/unit"
  2.  
  3. class UserTest < Test::Unit::TestCase
  4.   def test_to_s_includes_name_and_email
  5.     user = User.new("John", "Smith", "jsmith@example.com")
  6.     assert_equal "Smith, John <jsmith@example.com>", user.to_s
  7.   end  
  8. end


When we improve the test to focus on the end result, we can look at the test and instantly see the requirements (and so can all the people that will live with our code long into the future). Instead of reflecting the ingredients, the test now reflects the end product. And this allows us to have greater confidence that our code is doing the right thing as well.

Whether it’s strings, dates, timestamps, or numeric calculations, any time we can assert on a literal value, our test will be better off because of it. [3] The less logic that’s in our assertion, the fewer chances we have for that logic to be wrong, and the less logic we have to dig through to grok what’s being tested in the first place.

Notes

[1] And if you’re writing a library or framework, the tests may be a win for the users (or potential users) of your code as well. My colleague Muness Alrubaie is often seen skipping right over the docs and heading straight for the tests when he wants to check out some new open source code. (After all, assuming they pass, the tests don’t lie.) And if there are no tests, or if the tests fail to cleanly express the underlying functionality, you can rest assured that he won’t be looking at that project for long.

[2] Image courtesy of Pablo Baslini (flickr.com/98621082@N00)


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

Thanks to Greg Vaughn for reading drafts of this post.

Tags: , | 1 Comment »

Noteworthy Nonsense - July 25, 2008

Posted by Jason Rudolph on 25th July 2008

Tags: , , , , | No Comments »

iPhone App Store Now Live

Posted by Jason Rudolph on 10th July 2008

As of 8:55 EDT, there’s no direct link to the store just yet, but you can “hack” your way in. Just search the iTunes store for the free iTunes Remote app named simply, “Remote”. Click Get App, wait for it to download, and voilà, there’s the App Store in the sidebar.

20080710 App Store Thumb

Sweet!

Tags: | 2 Comments »

Testing Anti-Patterns: Underspecification

Posted by Jason Rudolph on 8th July 2008

Last week we discussed the perils of overspecification, and while we saw that it’s clearly possible for a test suite to do too much, it’s far more common for it to do too little.

Green Architecture

Suppose we’re building an application for an online retailer, and they decide that they want to provide free shipping on all orders with a minimum price of $25.00. (Where do they come up with this stuff?!) Armed with these requirements, we set off to develop the logic to determine whether a given order qualifies for this new offer. (We’ll pick on some half-baked Ruby code for this example, but we could certainly extrapolate the same ideas to any language we might choose.)

It’s a straightforward request, so we’ll crank out a quick method to encapsulate the logic …

  1. MIN_FREE_SHIPPING_PRICE = 25.0  
  2.  
  3. def free_shipping?(total_order_price)
  4.   total_order_price > MIN_FREE_SHIPPING_PRICE
  5. end


… and add a few simple tests to make sure we’re getting the desired results (all the while being careful to avoid any overspecification, of course).

  1. def test_free_shipping_returns_true_for_order_above_min_price
  2.   assert free_shipping?(MIN_FREE_SHIPPING_PRICE + 1)
  3. end
  4.  
  5. def test_free_shipping_returns_false_for_order_below_min_price
  6.   assert !free_shipping?(MIN_FREE_SHIPPING_PRICE - 1)
  7. end


And just like that, we have a passing test suite …

  1. $ ruby underspecification_test.rb
  2. Loaded suite underspecification_test
  3. Started
  4. ..
  5. Finished in 0.00029 seconds.
  6.  
  7. 2 tests, 2 assertions, 0 failures, 0 errors


… and 100% code coverage …

100% Test Coverage

… so there’s certainly the temptation to declare victory.

Mission (Not Exactly) Accomplished

But what about the customer that just spent the last 20 minutes concocting the perfect order? You know, the one that’s exactly $25.00 and not a penny more. (Sure, he needs professional help, but do you really want to get on the wrong side of someone with that kind of determination and time on their hands?) If we’re looking for our test suite to tell us how the application should respond to a $25.00 order, we’re out of luck. This underspecification means that our test suite not only fails to communicate how the application should behave in this scenario, our test suite also fails to give us any confidence that the application will do the right thing (whatever that may be).

Edge Cases Matter

Since we’re calling it a “minimum” price, it sure sounds like a $25.00 order should qualify for free shipping, so we should add a test to specify that behavior.

  1. def test_free_shipping_returns_true_for_order_equal_to_min_price
  2.   assert free_shipping?(MIN_FREE_SHIPPING_PRICE)
  3. end


And when we run this test, we should get some good insight into whether we’re likely to have that rather obsessive customer hunting us down in his copious free time.

  1. $ ruby underspecification_test.rb
  2. Loaded suite underspecification_test
  3. Started
  4. ..F
  5. Finished in 0.007163 seconds.
  6.  
  7.   1) Failure:
  8. test_free_shipping_returns_true_for_order_equal_to_min_price(UnderspecificationTest) [underspecification_test.rb:16]:
  9. <false> is not true.
  10.  
  11. 3 tests, 3 assertions, 1 failures, 0 errors


Ouch! As is often the case, the code fails on a boundary condition. Of course, the problem (once identified) is easily fixed by replacing the incorrect operator (>) with the correct one (>=). It’s a trivial change, but under what circumstances would we identify this problem in the first place? Why not call it a day as soon as the first two tests are passing? We already had 100% test coverage [1], and we certainly don’t get any extra credit from our coverage analysis tool for adding this new test case. Geoffrey Wiseman sums up this situation nicely in his commentary on making effective use of coverage analysis:

Coverage [reports] are great at telling you when you don’t have enough tests. They’re terrible at telling you that you have enough, that they’re good enough, etc.

The standard test-driven development approach serves us well: write a test, watch it fail, and then write the code to make it pass. But there’s a next step as well: we need to ask which tests are still missing. We need to apply that critical thought. As soon as we finish writing the first two tests above, we should immediately start looking for the “abusive” tests (as Eric Sink lovingly refers to them) that we can write to make our code fail. Where are the edge cases? Where are the exception cases? What should happen if we pass in a negative value? A nil value? Our test suite should be specific in its demands of our code.

Use It Wisely

A green test suite and high code coverage only means that we’ve satisfied the functionality that’s currently specified by our tests. We can (and should) rely on the coverage report to spot the code that isn’t yet exercised by our test suite, but we can’t stop there.

In Waltzing with Bears, Tom DeMarco and Timothy Lister remind us that “while it’s possible to specify a product ambiguously, it is not possible to build a product ambiguously.” The applications we build are always doing something in the corner cases. In the exception cases. But are they doing the right thing. And without proper tests (i.e., executable specifications), how can we be sure?

Notes

[1] If you recall from our recent discussion on code coverage types, this is the part where you ask, “Exactly what kind of coverage are we talking about here?” Well, despite the fact that rcov only reports line coverage analysis, we can still deduce fairly quickly that this particular code has 100% line coverage, 100% branch coverage, and 100% path coverage.


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

Thanks to Aaron Bedra and Greg Vaughn for reading drafts of this post.

Tags: , , | 2 Comments »

5 Resources for Getting Up to Speed on Rails 2.1

Posted by Jason Rudolph on 8th July 2008

Rails 2.1 has been out for a little over a month now, and I’ve had several people ask me to recommend resources for getting up to speed on its new moves. So, in no particular order, here goes …

And if you’re upgrading an existing app, here’s a bonus resource for you. Be sure to check it out …

There you have it. Get crackin’. There will be a quiz.

And if you want to show these guys some appreciation for all the work they’ve put into these resources - come on, you do, right? - be sure to recommend them on workingwithrails. They surely deserve it!

Tags: | 1 Comment »

Testing Anti-Patterns: Overspecification

Posted by Jason Rudolph on 1st July 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.

Tags: , | 2 Comments »