Tuesday, March 4, 2014

Better Design Yields Awesome Fast Tests

Before all the hype of Behavior Driven Development (BDD) and Test Driven Development (TDD), I had a lot of success with the following development technique:

  1. Create Unit Test to test Application (We'll take an API based application as our example.)
  2. Implement simple API
  3. Verify Unit Test works
  4. Add more tests and build Test Suite


I called an API and asserted that the state of response based on inputs.

That was in a compiled language.

Then I started developing in Ruby, which is a dynamic language.

I learned a few things:

  • Unit Tests are more important for Ruby than Java b/c a complied language will catch interface issues early
  • Integration Tests are more important when using mocks and stubs
  • Though coding is much more enjoyable in Ruby/Rails than Java, compilation time was replaced by the time it took to run the Rails test suite


BDD Testing Didn't Make Sense

I found that every time I wrote tests for a Rails app, I had to duplicate all the objects with mocks and stubs (and the tests still took too long).

While I still don't appreciate the fact that I must use a different DSL, rspec, (which is constantly changing) to implement effective tests, I have come to accept rspec as the lingua franca of test DSLs for Rails apps.

It's just annoying to not be able to use the more recent features of rspec when working on a legacy, at this time Rails 3.x app.

Things Get Better

Eventually, I learned to not only deal with the rigorous testing requirements, but to take that pain as an incentive to design better applications.

As you find things difficult to test, you should alter your design (not your tests).

Test First Development

Here, you alter the way to test to alleviate the pain, i.e., the time it takes to run your tests.

You use Spork or Zeus to speed up your testing environment.

Without such tools, running spec_helper takes too long.

With these tools, the test environment, e.g., the Rails environment, including database connections for ActiveRecord gets loaded in the background.

Test Driven Development

With TDD, you improve the way you design your application, which in turn makes your tests run faster.

Dependency Injection

DI allows us to mock all the time consuming operations and to isolate testing to the interface of the object under test.

This typically means that we'll keep not only our controllers thin, but also our models.

We'll introduce the use of Service Objects, with flexible initializers, making it easy to swap out the persistence logic.

We'll use mocks for testing and the real thing, e.g., ActiveRecord for production.

We'll avoid using datbase operations, and isolate ourselves from 3rd party APIs.

We'll Use a Factory.build rather than a Factory.create to build in memory data relations.

Instead of running Spork or Zeus ask yourself, why does this take so long?

What is the biggest 3rd party thing I'm depending on? "Rails"

Escape from Rails Dependency

Use Ruby Mixins or Delegation.

Use Delegation when using a service is beneficial; Otherwise, use a Mixin.

Summary

Do This

  • Isolate your App from 3rd Party Frameworks and Libraries
  • Don't let controller actions talk to ActiveRecord (wrap calls to db or 3rd party libraries)
  • Use Controllers to route and pass data into something else (don't use Finders)
  • Use Cucumber (or some other integration test suite) to test for inclusion of modules
  • If its not an ActiveRecord model, then only call scopes or hand-built scopes (wrap to avoid hash construction conflicts)
  • Use Dependency Injection
  • Delegate quickly
  • Write your application in Ruby and let the Rails code call it

Get This

  • Easier Maintenance
  • Faster Tests
  • Higher Cohesion

References

http://martinfowler.com/articles/mocksArentStubs.html
http://balinterdi.com/blog/page/2/

No comments:

Post a Comment