TODO List

Invoke test method

Test 0: Bootstrap

   1 # test_case_test.rb
   2 test = MockTestCase.new("test_method")
   3 puts test.was_run
   4 test.test_method
   5 puts test.was_run

Make it compile.

   1 # test_case_test.rb
   2 class MockTestCase
   3   attr_reader :was_run
   4 
   5   def initialize(name)
   6     @was_run = false
   7   end
   8 
   9   def test_method
  10   end
  11 end
  12 
  13 ...

Run tests and see it fail:

false
false

Make it pass.

   1 # test_case_test.rb
   2 ...
   3   def test_method
   4     @was_run = true
   5   end
   6 ...

Yeah, green bar!

false
true

Test 1: Run Runs (Test 0 -> Test 1)

   1 # test_case_test.rb
   2 ...
   3 test = MockTestCase.new("test_method")
   4 puts test.was_run
   5 test.run
   6 puts test.was_run

It compiles and passes with a simple FakeItTillYouMakeIt.

   1 # test_case_test.rb
   2 class MockTestCase
   3 ...
   4   def run
   5     test_method
   6   end
   7 end

With our tests green, we refactor to make it real.

   1 # test_case_test.rb
   2 class MockTestCase
   3   def initialize(name)
   4     @name = name
   5     ...
   6   end
   7 ...
   8   def run
   9     send @name
  10   end
  11 end

"Wait a second, aren't you just testing your mock?"

"Right you are, astute one."

Let's move our production code out of our mock.

   1 # test_case.rb
   2 class TestCase
   3   def initialize(name)
   4     @name = name
   5   end
   6 
   7   def run
   8     send @name
   9   end
  10 end

   1 # test_case_test.rb
   2 require 'test_case'
   3 
   4 class MockTestCase< TestCase
   5   attr_reader :was_run
   6 
   7   def initialize(name)
   8     super(name)
   9     @was_run = false
  10   end
  11 
  12   def test_method
  13     @was_run = true
  14   end
  15 end

If you're like me, you're compelled run your test between every little step and true/false doesn't feel like a passing test.

false
true

Using our freshly built framework will fix that.

   1 # test_case_test.rb
   2 ...
   3 class TestCaseTest < TestCase
   4   def test_running
   5     test = MockTestCase.new("test_method")
   6     raise "test_method shouldn't run" if test.was_run
   7     test.run
   8     raise "test_method didn't run" unless test.was_run
   9   end
  10 end
  11 
  12 TestCaseTest.new("test_running").run

Hmm, the expected results are now blank.

I am getting itchy, I want to see something fail.

   1 # test_case_test.rb
   2 ...
   3     #test.run
   4 ...

test_case_test.rb:21:in `test_running': test_method didn't run (RuntimeError)
        from ./test_case.rb:7:in `send'
        from ./test_case.rb:7:in `run'
        from test_case_test.rb:25

I uncomment the offending line and rerun the test; ahhhh my test is passing.

It is time to check-in and cross an item off our TODO list.

TODO List

Report collected results

It bugs me that my results are blank. Let work on Report collected results.

Test 2: Collect Results

   1 # test_case_test.rb
   2 ...
   3 class TestCaseTest < TestCase
   4 ...
   5   def test_results
   6     test = MockTestCase.new('test_method')
   7     result = test.run
   8     expected = "1 run, 0 failed"
   9     actual = result.summary
  10     raise "#{expected} != #{actual}" unless expected == actual
  11   end
  12 end
  13 
  14 TestCaseTest.new("test_running").run
  15 TestCaseTest.new("test_results").run

Making it compile.

   1 # test_result.rb
   2 
   3 class TestResult
   4   def summary
   5   end
   6 end

   1 # test_case.rb
   2 require 'test_result'
   3 
   4 class TestCase
   5 ...
   6   def run
   7     send @name
   8     TestResult.new
   9   end
  10 end

Now we see the failing test:

test_case_test.rb:29:in `test_results': 1 run, 0 failed !=  (RuntimeError)
        from ./test_case.rb:9:in `send'
        from ./test_case.rb:9:in `run'
        from test_case_test.rb:34

I like FakeItTillYouMakeIt here.

   1 # test_result.rb
   2 ...
   3   def summary
   4     "1 run, 0 failed"
   5   end

Both tests are passing (I think).

Now let's actualize the implementation (refactor to remove the silly hard coded solution).

   1 # test_result.rb
   2 ...
   3   def initialize
   4     @run_count = 1
   5   end
   6 
   7   def summary
   8     "#{@run_count} run, 0 failed"
   9   end
  10 ...

We need more refactoring to get rid of that hard coded one.

   1 # test_case.rb
   2 ...
   3   def run
   4     result = TestResult.new
   5     result.test_started
   6     send @name
   7     result
   8   end
   9 ...

   1 # test_result.rb
   2 ...
   3   def initialize
   4     @run_count = 0
   5   end
   6 
   7   def test_started
   8     @run_count += 1
   9   end
  10 ...

Tests are still passing, yeah. Hmm, what should I do with that ugly hard coded zero?

Test 3: Failed Results

It is not obvious to me how to move forward so I think I will triangulate (fancy word for writing another test to force you to remove hard coded solution).

   1 # test_case_test.rb
   2 ...
   3 class TestCaseTest < TestCase
   4 ...
   5    def test_failed_result
   6     test = MockTestCase.new('test_broken_method')
   7     result = test.run
   8     expected = "1 run, 1 failed"
   9     actual = result.summary
  10     raise "#{expected} != #{actual}" unless expected == actual
  11   end
  12 ...
  13 TestCaseTest.new("test_failed_result").run

Getting it to compile requires changing the mock.

   1 # test_case_test.rb
   2 class MockTestCase < TestCase
   3   ...
   4   def test_broken_method
   5     raise 'broken method'
   6   end
   7 end

In order to get this test running I have to do two things. I don't want to do that. So I comment out this test.

   1 # test_case_test.rb
   2 ...
   3 #TestCaseTest.new("test_failed_result").run

I will add Report failed tests to my list. Check in my code and cross off Report collected results. Ah, that feels good.

TODO List

Report Failed Tests

Test 4: Failed Results on a TestResult

I can eliminate code from my mock and shorten my tests if my failure test is a results test (to simply the Kata, I am keeping it in the same test file). I like that.

   1 # test_case_test.rb
   2 
   3 class TestResultTest < TestCase
   4   def test_failed_result
   5     result = TestResult.new
   6     result.test_started
   7     result.test_failed
   8     expected = "1 run, 1 failed"
   9     actual = result.summary
  10     raise "#{expected} != #{actual}" unless expected == actual
  11   end
  12 end
  13 ...
  14 TestResultTest.new('test_failed_result').run

We make it compile.

   1 # test_result.rb
   2 ...
   3   def test_failed
   4   end
   5 ...

See the failing test.

test_case_test.rb:53:in `test_failed_result': 1 run, 1 failed != 1 run, 0 failed
 (RuntimeError)
        from ./test_case.rb:11:in `send'
        from ./test_case.rb:11:in `run'
        from test_case_test.rb:60

And make it pass.

   1 # test_result.rb
   2 ...
   3   def initialize
   4     @run_count = 0
   5     @failed_count = 0
   6   end
   7 ...
   8   def test_failed
   9     @failed_count += 1
  10   end
  11 
  12   def summary
  13     "#{@run_count} run, #{@failed_count} failed"
  14   end
  15 ...

We are green again, very nice!

I am bother by my 3 line assert. I won't think about it, I will just write it on my TODO list.mechanism.

TODO List

Before I cross out my failed item I better put my test back.

   1 # test_case_test.rb
   2 ...
   3 TestCaseTest.new("test_failed_result").run

Oops it failed!

test_case_test.rb:16:in `test_broken_method': broken method (RuntimeError)
        from ./test_case.rb:11:in `send'
        from ./test_case.rb:11:in `run'
        from test_case_test.rb:38:in `test_failed_result'
        from ./test_case.rb:11:in `send'
        from ./test_case.rb:11:in `run'
        from test_case_test.rb:59

That is an easy fix.

   1 # test_case.rb
   2 ...
   3   def run
   4     result = TestResult.new
   5     result.test_started
   6     begin
   7       send @name
   8     rescue
   9       result.test_failed
  10     end
  11     result
  12   end
  13 
  14 ...

We better call summary or we won't know when a test fails.

   1 # test_case_test.rb
   2 ...
   3 puts TestCaseTest.new('test_running').run.summary
   4 puts TestCaseTest.new('test_results').run.summary
   5 puts TestResultTest.new('test_failed_result').run.summary
   6 puts TestCaseTest.new("test_failed_result").run.summary

Time to check in and cross off another item on our TODO list. Progress feels great. Losing the failure details might cause me pain. I'll put it on the list.

TODO List

Running Multiple Tests

Test 5: Collect to Suite

Let's tackle running multiple tests. That should get rid of some of that ugly duplication.

   1 # test_case_test.rb
   2   def test_suite
   3     suite = TestSuite.new
   4     result = TestResult.new
   5     suite.add(MockTestCase.new('test_method'))
   6     suite.add(MockTestCase.new('test_broken_method'))
   7     suite.run result
   8     expected = "2 run, 1 failed"
   9     actual = result.summary
  10     raise "#{expected} != #{actual}" unless expected == actual
  11   end

And to get it to pass we will do ObviousImplementation.

   1 # test_case.rb
   2 class TestCase
   3 ...
   4   def run(result = TestResult.new)
   5     # result = TestResult.new This line removed.
   6     result.test_started
   7 ...
   8 class TestSuite
   9   def initialize
  10     @tests = []
  11   end
  12 
  13   def add(test)
  14     @tests << test
  15   end
  16 
  17   def run(result)
  18     @tests.each do |item|
  19       item.run(result)
  20     end
  21     result
  22   end
  23 end

With all our tests passing, we should use our suite.

   1 # test_case_test.rb
   2 ...
   3 suite = TestSuite.new
   4 suite.add TestCaseTest.new('test_running')
   5 suite.add TestCaseTest.new('test_results')
   6 suite.add TestResultTest.new('test_failed_result')
   7 suite.add TestCaseTest.new("test_suite")
   8 result = TestResult.new
   9 suite.run result
  10 puts result.summary

Our tests run looks better and we removed duplication. Oh happy day!

4 run, 0 failed

Now that the tests pass, we can finish up our design. All run methods should take a result and have no explicit returns.

First I fix the tests (I like to run the tests after every change).

   1 # test_case_test.rb
   2 ...
   3   def test_running
   4     test = MockTestCase.new("test_method")
   5     raise "test_method shouldn't run" if test.was_run
   6     test.run TestResult.new
   7 ...
   8   def test_results
   9     test = MockTestCase.new('test_method')
  10     result = TestResult.new
  11     test.run(result)
  12 ...
  13   def test_failed_result
  14     test = MockTestCase.new('test_broken_method')
  15     result = TestResult.new
  16     test.run(result)
  17 ...

The tests still run.

4 run, 0 failed

I finish the refactor by removing the default parameter.

   1 # test_case.rb
   2 class TestCase
   3 ...
   4   def run(result)
   5 ...

Yeah, the tests still run.

4 run, 0 failed

Run multiple test is completed. Yes! It is time to check in. As part of our reflection, I think the test suite should be auto filled from a test case. Also, the duplication in the test could be removed.

TODO List

RubyXUnitKata (last edited 2010-06-23 00:10:12 by ZhonJohansen)