TODO List
- Invoke test method
- Run multiple tests
- Report collected results
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:25I 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
Invoke test method
- Run multiple tests
- Report collected results
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:34I 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
Invoke test method
- Run multiple tests
Report collected results
- Report failed tests
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:60And 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
Invoke test method
- Run multiple tests
Report collected results
- Report failed tests
- Replace raise with assert_equals
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:59That 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
Invoke test method
- Run multiple tests
Report collected results
Report failed tests
- Replace raise with assert_equals
- Report failed details
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
Invoke test method
Run multiple tests
Report collected results
Report failed tests
- Replace raise with assert_equals
- Report failed details
- Create suite from a test case class
- Consider removing duplication in tests
