= Clean Tests

Author::  Dave Copeland (mailto:davetron5000 at g mail dot com)
Copyright:: Copyright (c) 2012 by Dave Copeland
License:: Distributes under the Apache License, see LICENSE.txt in the source distro

Get your Test::Unit test cases readable and fluent, without RSpec, magic, or crazy meta-programming.

This library is a set of small, simple tools to make your Test::Unit test cases easy to understand.  This isn't a massive change in how you write tests, but simply some helpful things will make your tests easier to read.

* {Source}[http://www.github.com/davetron5000/clean_test]
* {RDoc}[http://davetron5000.github.com/clean_test/rdoc]

The main problems this library solves are:

* Understanding what part of a test method is setup, test, and evaluation
* Understanding what elements of a test are relevant to the test, and which are arbitrary placeholders
* Removing the requirement that your tests are method names

== Install

    gem install clean_test

Or, with bundler:

    gem "clean_test", :require => false

== Overview

    class Circle
      attr_reader :name
      attr_reader :radius

      def initialize(radius,name)
        @radius = radius
        @name = name
      end

      def area
        @radius * @radius * 3.14
      end

      def to_s
        "circle of radius #{radius}, named #{name}"
      end

    end

    require 'clean_test/test_case'

    class CircleTest < Clean::Test::TestCase
      test_that "area is computed correctly" {
        Given {
          @circle = Circle.new(10,any_string)
        }
        When {
          @area = @circle.area
        }
        Then {
          assert_equal 314,@area
        }
      }

      test_that "to_s includes the name" {
        Given { 
          @name = "foo"
          @circle = Circle.new(any_int,@name)
        }
        When {
          @string = @circle.to_s
        }
        Then {
          assert_match /#{@name}/,@string
        }
      }
    end

What's going on here?

* We can clearly see which parts of our test are setting things up (stuff inside +Given+), which parts are executing the code we're testing (stuff in +When+) and which parts are evalulating the results (stuff in +Then+)
* We can see which values are relevant to the test - only those that are literals.  In the first test, the +name+ of our circle is not relevant to the test, so instead of using a dummy value like <tt>"foo"</tt>, we use +any_string+, which makes it clear that the value <i>does not matter</i>.  Similarly, in the second test, the radius is irrelevant, so we use +any_int+ to signify that it doesn't matter.
* Our tests are clearly named and described with strings, but we didn't need to bring in active support.
* A side effect of this structure is that we use instance vars to pass data between Given/When/Then blocks. This means that instance vars "jump out" as important variables to the test; non-instance vars "fade away" into the background.

But, don't fret, this is not an all-or-nothing proposition.  Use whichever parts you like.  Each feature is in a module that you can include as needed, or you can do what we're doing here and extend Clean::Test::TestCase to get everything at once.

== More Info

* Clean::Test::TestCase is the base class that gives you everything
* Clean::Test::GivenWhenThen provides the Given/When/Then construct
* Clean::Test::TestThat provides +test_that+
* Clean::Test::Any provides the +any_string+ and friends.

== Questions you might have

=== Why?

I'm tired of unreadable tests.  Tests should be good, clean code, and it shoud be easy to see what's being tested.  This is especially important when there is a lot of setup required to simulate something.

I also don't believe we need to resort to a lot of metaprogramming tricks just to get our tests in this shape.  RSpec, for example, creates strange constructs for things that are much more straightforward in plain Ruby.  I like Test::Unit, and with just a bit of helper methods, we can make nice, readable tests, using just Ruby.

=== But the test methods are longer!

And?  I don't mind a test method that's a bit longer if that makes it easy to understand.  Certainly, a method like this is short:


    def test_radius
      assert_equal 314,Circle.new(10).radius
    end

But, we rarely get such simple methods *and* this test method isn't very modifiable; everything is on one line and it doesn't encourage re-use.  We can do better.

=== What about mocks?

Mocks create an interesting issue, because the "assertions" are the mock expectations you setup before you call the method under test.  This means that the "then" side of things is out of order.

    class CircleTest < Test::Unit::Given::TestCase
      test_that "our external diameter service is being used" do
        Given {
          @diameter_service = mock()
          @diameter_service.expects(:get_diameter).with(10).returns(400)
          @circle = Circle.new(10,@diameter_service)
        }
        When  {
          @diameter = @circle.diameter
        }
        Then {
          // assume mocks were called
        }
      end
    end

This is somewhat confusing.  We could solve it using two blocks provided by this library, +the_test_runs+, and +mocks_shouldve_been_called+, like so:

    class CircleTest < Test::Unit::Given::TestCase
      test_that "our external diameter service is being used" do
        Given {
          @diameter_service = mock()
        }
        When the_test_runs
        Then {
          @diameter_service.expects(:get_diameter).with(10).returns(400)
        }
        Given {
          @circle = Circle.new(10,@diameter_service)
        }
        When  {
          @diameter = @circle.diameter
        }
        Then mocks_shouldve_been_called
      end
    end

Although both <tt>the_test_runs</tt> and <tt>mocks_shouldve_been_called</tt> are no-ops,
they allow our tests to be readable and make clear what the assertions are that we are making.

Yes, this makes our test a bit longer, but it's *much* more clear.

=== What about block-based assertions, like +assert_raises+

Again, things are a bit out of order in a class test case, but you can clean this up without this library or any craziness, by just using Ruby:

    class CircleTest < Clean::Test::TestCase

      test_that "there is no diameter method" do
        Given {
          @circle = Circle.new(10)
        }
        When {
          @code = lambda { @circle.diameter }
        }
        Then {
          assert_raises(NoMethodError,&@code)
        }
      end
    end

=== My tests require a lot of setup, so I use contexts in shoulda/RSpec.  What say you?

Duplicated setup can be tricky.  A problem with heavily nested contexts in Shoulda or RSpec is that it can be hard to piece together what all the "Givens" of a particular test actually are.  As a reaction to this, a lot of developers tend to just duplicate setup code, so that each test "stands on its own".  This makes adding features or changing things difficult, because it's not clear what duplicated code is the same by happenstance, or the same because it's *supposed* to be the same.

To deal with this, we simply use Ruby and <i>method extraction</i>.  Let's say we have a +Salutation+ class that takes a +Person+ and a +Language+ in its constructor, and then provides methods to "greet" that person

    class Salutation
      def initialize(person,language)
        raise "person required" if person.nil?
        raise "language required" if language.nil?
      end

      # ... methods
    end

To test this class, we always need a non-nil person and language.  We might end up with code like this:

    class SalutationTest << Clean::Test::TestCase
      test_that "greeting works" do
        Given {
          person = Person.new("David","Copeland",:male)
          language = Language.new("English","en")
          @salutation = Salutation.new(person,language)
        }
        When { 
          @greeting = @salutation.greeting
        }
        Then {
          assert_equal "Hello, David!",@salutation.greeting
        }
      end

      test_that "greeting works for no first name" do
        Given {
          person = Person.new(nil,"Copeland",:male)
          language = Language.new("English","en")
          @salutation = Salutation.new(person,language)
        }
        When { 
          @greeting = @salutation.greeting
        }
        Then {
          assert_equal "Hello, Mr. Copeland!",@salutation.greeting
        }
      end
    end

In both cases, the language is the same, and the person is slightly different.  Method extraction:

    class SalutationTest << Clean::Test::TestCase
      test_that "greeting works" do
        Given {
          @salutation = Salutation.new(male_with_first_name("David"),english)
        }
        When { 
          @greeting = @salutation.greeting
        }
        Then {
          assert_equal "Hello, David!",@salutation.greeting
        }
      end

      test_that "greeting works for no first name" do
        Given {
          @salutation = Salutation.new(male_with_no_first_name("Copeland"),english)
        }
        When { 
          @greeting = @salutation.greeting
        }
        Then {
          assert_equal "Hello, Mr. Copeland!",@salutation.greeting
        }
      end

    private
      def male_with_first_name(first_name)
        Person.new(first_name,any_string,:male)
      end

      def male_with_no_first_name(last_name)
        Person.new(nil,last_name,:male)
      end

      def english; Language.new("English","en"); end
    end

=== What did that have to do with this gem?

Nothing.  That's the point.  You have the power already.  That being said, +Given+ and friends can take a symbol representing the name of a method to call, in lieu of a block:

    class SalutationTest << Clean::Test::TestCase
      test_that "greeting works" do
        Given :english_salutation_for,male_with_first_name("David")
        When { 
          @greeting = @salutation.greeting
        }
        Then {
          assert_equal "Hello, David!",@salutation.greeting
        }
      end

      test_that "greeting works for no first name" do
        Given :english_salutation_for,male_with_no_first_name("Copeland")
        When { 
          @greeting = @salutation.greeting
        }
        Then {
          assert_equal "Hello, Mr. Copeland!",@salutation.greeting
        }
      end

    private
      def male_with_first_name(first_name)
        Person.new(first_name,any_string,:male)
      end

      def male_with_no_first_name(last_name)
        Person.new(nil,last_name,:male)
      end

      def english_salutation_for(person)
        @salutation = Salutation.new(person,Language.new("English","en"))
      end
    end


=== Why Any instead of Faker?

Faker is used by Any under the covers, but Faker has two problems:

* We aren't _faking_ values, we're using _arbitrary_ values.  There's a difference semantically, even if the mechanics are the same
* Faker requires too much typing to get arbitrary values.  I'd rather type +any_string+ than <tt>Faker::Lorem.words(1).join(' ')</tt>

=== What about Factory Girl?

Again, FactoryGirl goes through metaprogramming hoops to do something we can already do in Ruby: call methods.  Factory Girl also places factories in global scope, making tests more brittle.  You either have a ton of tests depending on the same factory or you have test-specific factories, all in global scope.  It's just simpler and more maintainable to use methods and modules for this.  To re-use "factories" produced by simple methods, just put them in a module.

Further, the +Any+ module is extensible, in that you can do stuff like <tt>any Person</tt>, but you can, and should, just use methods.  Any helps out with primitives that we tend to use a lot: numbers and strings.  It's just simpler and, with less moving parts, more predictable.  This means you spend more time on your tests than on your test infrastructure.

=== Any uses random numbers and strings.  Tests aren't repeatable!

You can make them repeatable by explicitly setting the random seed to a literal value.  Also, including Any will record
the random seed used and output it.  You can then set +RANDOM_SEED+ in the environment to re-run he tests using that seed.

Keep in mind that if _any_ value will work, random values shouldn't be a problem.

=== What about not using the base class?

To use Any on its own:

    require 'clean_test/any'

    class MyTest < Test::Unit::TestCase
      include Clean::Test::Any
    end

To use GivenWhenThen on its own:

    require 'clean_test/given_when_then'

    class MyTest < Test::Unit::TestCase
      include Clean::Test::GivenWhenThen
    end

To use TestThat on its own:

    require 'clean_test/test_that'

    class MyTest < Test::Unit::TestCase
      include Clean::Test::TestThat
    end
