Unit testing is a postdebugging testing technique that lets you try bits of your program in order to verify that they work as expected. Some programmers use unit testing habitually in addition to or even instead of interactive debugging; other programmers use it rarely or never. Entire books have been written on the techniques and methodologies of unit testing, and I will only cover its fundamentals here.
The basic idea of unit testing is that you can write a number of “assertions” stating that certain results should be obtained as the consequence of certain actions. For example, you might assert that the return value of a specific method should be 100, that it should be a Boolean, or that it should be an instance of a specific class. If, when the test is run, the assertion proves to be correct, it passes the test; if it is incorrect, the test fails.
Here’s an example, which will fail if the getVal
method of the object, t
, returns any value other than 100:
assert_equal(100, t.getVal)
But you can’t just pepper your code with assertions of this sort. There are precise rules to the game. First you have to require the test/unit file. Then you need to derive a test class from the TestCase class, which is found in the Unit
module, which is itself in the Test
module:
class MyTest < Test::Unit::TestCase
Inside this class you can write one or more methods, each of which constitutes a test containing one or more assertions. The method names must begin with test
(so methods called test1
or testMyProgram
are okay, but a method called myTestMethod
isn’t). The following method contains a test that makes the single assertion that the return value of TestClass.new(100).getVal
is 1,000:
def test2 assert_equal(1000,TestClass.new(100).getVal) end
And here is a complete (albeit simple) test suite in which I have defined a TestCase class called MyTest that tests the class, TestClass. Here (with a little imagination!), TestClass may be taken to represent a whole program that I want to test:
test1.rb
require 'test/unit' class TestClass def initialize( aVal ) @val = aVal * 10 end def getVal return @val end end class MyTest < Test::Unit::TestCase def test1 t = TestClass.new(10) assert_equal(100, t.getVal) assert_equal(101, t.getVal) assert(100 != t.getVal) end def test2 assert_equal(1000,TestClass.new(100).getVal) end end
This test suite contains two tests: test1
(which contains three assertions) and test2
(which contains one). To run the tests, you just need to run the program; you don’t have to create an instance of MyClass. You will see a report of the results that states there were two tests, three assertions, and one failure:
1) Failure: test1(MyTest) [C:/bookofruby/ch18/test1.rb:19]: <101> expected but was <100>. 2 tests, 3 assertions, 1 failures, 0 errors
In fact, I made four assertions. However, assertions following a failure are not evaluated in a given test. In test1
, this assertion fails:
assert_equal(101, t.getVal)
Having failed, the next assertion is skipped. If I now correct this failed assertion (asserting 100 instead of 101), this next assertion will also be tested:
assert(100 != t.getVal)
But this too fails. This time, the report states that four assertions have been evaluated with one failure:
2 tests, 4 assertions, 1 failures, 0 errors, 0 skips
Of course, in a real-life situation, you should aim to write correct assertions, and when any failures are reported, it should be the failing code that is rewritten—not the assertion!
For a slightly more complex example of testing, see the test2.rb program (which requires a file called buggy.rb). This is a small adventure game that includes the following test methods:
test2.rb
def test1 @game.treasures.each{ |t| assert(t.value < 2000, "FAIL: #{t} t.value = #{t.value}" ) } end def test2 assert_kind_of( TestMod::Adventure::Map, @game.map) assert_kind_of( Array, @game.map) end
Here the first method, test1
, performs an assert
test on an array of objects passed into a block, and it fails when a value
attribute is not less than 2,000. The second method, test2
, tests the class types of two objects using the assert_kind_of
method. The second test in this method fails when @game.map
is found to be of the type TestMod::Adventure::Map
rather than Array
as is asserted.
The code also contains two more methods named setup
and teardown
. When defined, methods with these names will be run before and after each test method. In other words, in test2.rb, the following methods will run in this order:
1. | 2. | 3. |
4. | 5. | 6. |
This gives you the opportunity of reinitializing any variables to specific values prior to running each test or, as in this case, re-creating objects to ensure that they are in a known state as in the following example:
def setup @game = TestMod::Adventure.new end def teardown @game.endgame end
18.217.150.123