Title: Unit%20Testing%20in%20Ruby
1Unit Testing in Ruby
2Programming methodologies
- The grim facts
- The majority of large programming projects fail
- Projects that succeed are usually full of bugs
- Modifying and debugging programs usually
introduces yet more bugs - Hence, it is hazardous to modify a working
program - The time spent maintaining a program far exceeds
(10x?) the amount of time it took to write the
program - Programming methodologies are attempts to solve
these problems by properly organizing programs
and/or programmers - The current (and best) methodologies are the
agile methodologies - Agile means writing software that is easily
changed - XP (Exteme Programming) is the best known agile
methodology - XP tends to work best for small groups of
programmers
3Some ideas from agile programming
- There is no silver bullet, but agile
methodologies are the best we have at present - Most large programming projects fail, so...
- Write the simplest thing that can possibly
work. - Always have a working version, no matter how
little it does - Never add a feature until all known bugs have
been fixed - Any code that hasnt been tested is assumed to be
wrong - Have tests for all code, and keep it up to date
- Run tests frequentlyvery frequently
- Tests must be trivially easy to run, or you wont
bother running them
4Test suites
- Obviously you have to test your code to get it
working in the first place - You can do ad hoc testing (running whatever tests
occur to you at the moment), or - You can build a test suite (a thorough set of
tests that can be run at any time) - Disadvantages of a test suite
- Its a lot of extra programming
- This is true, but use of a good test framework
can help quite a bit - You dont have time to do all that extra work
- FalseExperiments repeatedly show that test
suites reduce debugging time more than the amount
spent building the test suite - Advantages of a test suite
- Reduces total number of bugs in delivered code
- Makes code much more maintainable and
refactorable - This is a huge win for programs that get actual
use!
5XP approach to testing
- In the Extreme Programming approach,
- Tests are written before the code itself
- If code has no automated test case, it is assumed
not to work - A test framework is used so that automated
testing can be done after every small change to
the code - This may be as often as every 5 or 10 minutes
- If a bug is found after development, a test is
created to keep the bug from coming back - Consequences
- Fewer bugs
- More maintainable code
- Continuous integrationDuring development, the
program always worksit may not do everything
required, but what it does, it does right
6Terminology
- A test fixture sets up the data (both objects and
primitives) that are needed to run tests - Example If you are testing code that updates an
employee record, you need an employee record to
test it on - A unit test is a test of a single class
- A test case tests the response of a single method
to a particular set of inputs - A test suite is a collection of test cases
- A test runner is software that runs tests and
reports results - An integration test is a test of how well classes
work together - JUnit provides some limited support for
integration tests
7Once more, in pictures
test suite
- A unit test tests the methods in a single class
- A test case tests (insofar as possible) a single
method - You can have multiple test cases for a single
method - A test suite combines unit tests
- The test fixture provides software support for
all this - The test runner runs unit tests or an entire test
suite - Integration testing (testing that it all works
together) is not well supported by JUnit
unit test (for one class)
test case (for one method)
another test case
test fixture
8The test runner
- The test runner runs all your tests
- If they all succeed, you get a green bar
- If any fail, you get a red barand links to the
tests that failed
9How not to write a unit test
- Unit tests must be fast and easy to runor you
wont run them - The only output you should need to look at, in
order to see that all tests passed, at is the
green bar - Of course, if you get a red bar, you need to
explore further - Dont do any output from your unit tests!
- Ideally, the methods you are testing should not
do any output - In most well-written programs, there is a
separation of concernsmethods either compute or
do output, but not both - It is possible to write unit tests for methods
that do output, but that is a slightly advanced
topic I wont cover here
10How to write a unit test class
- A unit test class is a class you write that
extends TestUnitTestCase - You will need the line require 'test/unit'
- Your test class will inherit the following
methods - def setup
- This a method that will be called before each of
your test methods - Typically, you will override this method and use
it to assign values to some instance variables
you need in testing - def teardown()
- This a method that will be called after each of
your test methods - Typically you will just ignore this method,
unless you need to close files - You will also write any number of test methods,
all of which have the form def test_Something - Something is usually, but not necessarily, the
name of the method you want to test - Inside each test method, you will do some
computations and call one or more assert methods
to test the results
11Available assertion methods
- assert boolean
- assert_equal expected, actual
- Uses
- assert_same expected, actual
- Uses equal?
- assert_not_equal expected, actual
- assert_not_same expected, actual
- assert nil object
- assert not_nil object
- assert_block block
- All these methods can take an additional message
argument - This is not a complete listing of the assert
methods - The first two methods are by far the most
commonly used
12Structure of a unit test
- require "test/unit"require "file_to_be_tested"c
lass CountTest lt TestUnitTestCase def
setup Perform initializations here end
def test_some_method Tests go here end
def teardown Release any resources (usually
not needed) endend
13Testing for exceptions
- Methods should throw exceptions if they are
called incorrectly - You can test whether a method throws an exception
when it ought to - def test_exceptions begin Call the
method that should throw an exception rescue
Exception or you can test for specific
exceptions return The exception
happened, so the test passes end
flunkend - Ruby also has assert_raise and assert_throws
methods, but I havent been able to make them work
14A complete example
- class Counter attr_reader value
- def initialize _at_value 0 end
- def increment n if n _at_value
1 else _at_value n0 end end
- def reset _at_value 0 end end
15The test class, part 1
- require "test/unit"require "counter"class
CountTest lt TestUnitTestCase - def setup _at_c Counter.new end
- def test_increment_with_no_args
assert_equal 0, _at_c.value _at_c.increment
assert_equal 1, _at_c.value _at_c.increment
assert_equal 2, _at_c.value end
- def test_increment_with_arg assert_equal 0,
_at_c.value _at_c.increment 3 assert_equal 3,
_at_c.value _at_c.increment 5 assert_equal 8,
_at_c.value end - def test_reset _at_c.increment
_at_c.increment 10 assert _at_c.value gt 0
_at_c.reset assert _at_c.value.zero? end
16The test class, part 2
- def test_exceptions begin
_at_c.increment 'four' return rescue
Exception end flunk endend of the
test class
17Test suites
- A test suite is a collection of unit tests
- In Ruby, all you have to do is require each test
file - Example suite (containing just the one unit test
- require 'ruby_tests
- Note In RadRails, running a test suite produces
only text output
18Test-Driven Development (TDD)
- It is difficult to add unit tests to an existing
program - The program probably wasnt written with testing
in mind - Its actually better to write the tests before
writing the code you want to test - This seems backward, but it really does work
better - When tests are written first, you have a clearer
idea what to do when you write the methods - Because the tests are written first, the methods
are necessarily written to be testable - Writing tests first encourages you to write
simpler, single-purpose methods - Because the methods will be called from more than
one environment (the real one, plus your test
class), they tend to be more independent of the
environment
19Recommended approach
- Write a test for some method you intend to write
- If the method is fairly complex, test only the
simplest case - Write a stub for the method
- Run the test and make sure it fails
- Replace the stub with code
- Write just enough code to pass the tests
- Run the test
- If it fails, debug the method (or maybe debug the
test) repeat until the test passes - If the method needs to do more, or handle more
complex situations, add the tests for these
first, and go back to step 3
20The End
If you don't unit test then you aren't a software
engineer, you are a typist who understands a
programming language.
--Moses
Jones
1. Never underestimate the power of one
little test. 2. There is no such thing as a
dumb test. 3. Your tests can often find
problems where you're not expecting them. 4.
Test that everything you say happens actually
does happen. 5. If it's worth documenting,
it's worth testing.
--Andy Lester