Monday, November 3, 2008

Test::Unit design

A while ago I posted a couple of diagrams which I used to make sense of JUnit design. Almost at the same of drawing those ones I took a look at implementation of Test::Unit and RSpec frameworks. As a consequence here is a simplified overview of Test::Unit design.

Design in general

testunit overview

  • Given a TestSuit, TestRunner creates TestRunnerMediator, registers itself as TestRunnerMediator listener and then asks TestRunnerMediator to execute tests.
  • TestRunnerMediator creates TestResult, registers itself as TestResult listener and then relays request to execute tests to TestSuite.
  • TestSuite contains TestCase instances for each test method and upon request runs each of them.
  • TestCases actually execute test code (including setup and teardown methods) and notify TestResult on assertions, errors and failures. These events are propagated to TestRunner which actually provides some feedback to user, for example, write results to console.

All notifications are implemented using Observer module, so MediatorListener and TestResultListener interfaces in the diagram show roles played by classes in collaboration rather than some kind of interfaces in terms of the language.

How it actually works

testunit autorunner

Since the above description is somewhat abstract here is how Test::Unit is actually used from scripts if you ran TestCase as a standalone script:

  • When you require test/unit module in a test script, Unit adds a block to be executed when script is about to exit through the Kernel::at_exit method. This block asks the AutoRunner class to execute tests.
  • AutoRunner creates OptionsParser which reads parameters from command line and based on them creates TestRunner. Also AutoRunner chooses and creates Collector instance which finds tests and packs them into a TestSuite.
  • AutoRunner passes TestSuite to TestRunner and asks it to run tests.
  • Given a TestSuit, TestRunner creates TestRunnerMediator… etc

Should you want to see available TestRunners, Collectors or other options, the AutoRunner is probably the best place to get started. Another interesting place to go (it’s obvious and can be easily found from Test::Unit documentation but anyway) is Test::Unit::Assertions module, which contains available assertions.

If you don’t want to use AutoRunner, there is another way to execute tests which is to manually create TestRunner and pass to it a TestSuite (in this case Unit module won’t register block to execute at exit and AutoRunner won’t be used). It looks like this (code snippet is taken from Test::Unit rdoc):

require ‘test/unit/ui/console/testrunner’

Test::Unit::UI::Console::TestRunner.run(TCMyTest)

P.S. there is also a bit confusing (at least for me) RUnit package bundled with Ruby. I couldn’t find any authoritative information about how it’s related to Test::Unit. Judging from the code, it seems to be deprecated API left for backward compatibility which shouldn’t be used in new code.

0 comments: