Minimalist testing for Ruby applications

Last four months I have been developing web and backend applications for fun and profit without Rails - using the light weight Sinatra framework. Besides technical solutions there is also an important philosophical attitude I have learned in that Sinatra community.

In opposite to Rails they completely decline the cargo cult (also mentioned by Koz in a slightly different context) where one writes a line or two, he has seen in the README of the plugin and expects something magical to happen. And often it does not happen or happens in an unexpected way.

By contrast, in Sinatra community, if one develops a chunk of functionality, he thinks can be helpful for others, instead of creating a plugin or gem one publishes a gist at github so others can copy, paste and use it as inspiration for implementing the 5 percent of functionality they really need.

I found out that for me the RSpec approach does not work. The tests should give me more confidence, that everything works, especially for refactoring. Through all the magic and extensive metaprogramming RSpec creates more uncertainty for me instead.

Test::Unit is on the other side a bit “underkill” but it is easy to extended it with features I am missing:

  • use full sentences to name the tests
  • declare tests as pending or non-critical
  • enable the same level of abstraction in the test implementation and provide an overview of a test

If folded in the editor, a test looks like following, providing an overview of what is going on in the test:

-class SignupTest < FunctionalTestCase 

-  test 'registration workflow' do
+    prepare 'a customer with wrong bank details' do
+    expect 'errors only after the offline check' do
+    expect 'update should clean errors and bank details check identifier' do
   end
 end

This single test steps can be also printed out during the test execution if you set the environment variable VERBOSE_TEST=true.

For comparision here is the full source code of the integration test:

class SignupTest < FunctionalTestCase 

  test 'registration workflow' do
    prepare 'a customer with wrong bank details' do
      put '/customers/inva', create_customer_xml(
        'account' => '123456',
        'bank-number' => '36010043'
      ).to_xml, {'HTTP_X_ON_BEHALF_OF' => 'functional test'}
      assert_equal 202, last_response.status
    end

    expect 'errors only after the offline check' do
      assert_equal 0, Customer.find_by_customer_id('inva').customer_errors.size
      
      Customer.check_and_promote_all

      a = Customer.find_by_customer_id('inva')
      assert_equal 1, a.customer_errors.size
      assert_match /invalid/, a.customer_errors.first.message
    end
    
    expect 'update should clean errors and bank details check identifier' do
      get '/customer/inva', {}, {'HTTP_X_ON_BEHALF_OF' => 'functional test'}

      new_doc = response_doc()
      new_doc.select_single_node('//bank-name').set_text('Other Bank')
      put '/customer/inva', 
        new_doc.to_xml, 
        {'HTTP_X_ON_BEHALF_OF' => 'functional test'}
      a = customer.find_by_customer_id('inva')
      assert_equal 202, last_response.status
      assert_equal 0, a.customer_errors.size
      assert a.schufa_request_identifier.blank?, 'request identifier is not cleaned.'
    end

  end
end

So my testing infrastructure mainly consists of

  • the excellent Rack::Test facility, is also standard for new Rails versions
  • my Test::Unit extension, grab it on github
  • my project-specific test_helper, which can programatically create entities filled with test data and contains project-specific assertion helpers

Comments

blog comments powered by Disqus