Test::Unit – Ruby单元测试框架
介绍:
单元测试是XP的核心部分。XP是伟大的,单元测试已出现了很长一段时间,而且它是一个很好的思想。好的单元测试的关键部分不是写测试代码,而是要测试。两者有什么不同吗?当然,如果你只是写测试代码而不用它,那么你以后对代码的修改将不会得到保证。换句话说,如果你已经测试(当然你首先要写测试代码),然后经常运行它们,那么你慢慢地建立了一个好习惯,虽然你不能马上知道它的益处。
进入单元测试,则Ruby内的用于单元测试的框架会帮助你设计,调试和评估你的代码,而你要做的就是写测试代码并运行它。
用法:
单元测试的隐含思想是让你写一个测试方法,该方法为你的代码写些断言,然后进行测试工作。大多数这些测试方法被打包到一个测试单元中,并可在开发者需要的任何时候运行。运行的结果被收集在测试结果中并通过一些UI显示给用户。让我们先看看Test::Unit提供了哪些必需的部分。
断言:
框架的中心思想是要将断言做为期望输出的语句,也就是,“我断言这个x应该等于y”。当断言被执行时,如果它返回是正确的,不会有事发生。换句话说,如果你的断言失败,则错误信息会反馈给你,以便你返回修改直到你的断言成功。对断言的详细解释,可参考Test::Unit::Assertions。
测试方法与测试模具
明显地,这些断言必须在上下文环境中被调用,而这个上下文环境知道它们,并可以用它们的pass/fail值来做些事情。同样,它也可以很方便地收集相关的测试,每个测试用一个方法表示,然后放在一个知道如何运行它们的类中。有三个原因,要将测试同它们要进行测试的代码分开存放。首先,它可使你的测试代码整洁并易于管理。其次,它允许分离测试代码,因为它们只对你有用,而对用户没用。第三,它允许你设置通用的测试模具来重复测试。
什么是测试模具?测试不存活在真空中;而是依赖于它们所要测试的代码。通常,测试集依赖于一组数据,也可称为模具。如果它们被打包在同一测试类中,它们或以共享这些数据的设置与解除,即setup与teardown方法。这可去除不必要的重复,并使模具可以容易地添加其它测试。
Test::Unit::TestCase包装测试方法在容器内,并允许你很容易地为每个测试设置和解除相同测试模具。这都是通过覆写setup或teardown方法来做到,它们将在每个测试方法运行前及运行后被调用。TestCase也知道如何收集你在Test::Unit::TestResult中的断言结果,然后它将报告显示给你。想写一个测试,得依循下面步骤:
l 确保Test::Unit在你的库路径上。
l 在测试脚本中请求‘test/unit’。
l 创建Test::Unit::TestCase类的子类。
l 为你的类添加以"test"开头的方法。
l 在你的方法内使用断言。
l 可按需要定义setup或/及teardown来设置你的测试模具。
l 现在你就可以运行你的测试了。
一个相对简单的测试看起来可能是这样(setup和teardown通常不用给出,它们可选的):
require 'test/unit'
class TC_MyTest < Test::Unit::TestCase
# def setup
# end
# def teardown
# end
def test_fail
assert(false, 'Assertion was false.')
end
end
测试运行器
现在,你有了测试类,但是你还需要一种方式来运行它,并观察在运行期间可能发生的任何失败。它就是Test::Unit::UI::Console::TestRunner(或是其它的,如Test::Unit::UI:GTK::TestRunner)。如果你在源文件请求了’test/unit’并运行了这个文件,则控制台测试运行器是自动被调用给你的。若是想使用其它运行器或手工管理运行器,可简单地调用它的run类方法并传递一个对象给它,这个对象应处理Test::Unit::TestSuite的suite消息。传递到你的TestCase类(这个类有一个suite方法)内也这么简单。看起来一个实现应该是样的:
require 'test/unit/ui/console/testrunner'
Test::Unit::UI::Console::TestRunner.run(TC_MyTest)
测试单元
工程内的测试单元会越来越多,它终会变得在运行时缓慢。同时它也会让你得到潜在的测试失败,因为太多了你忘记了运行它。而易于管理的TestRunner可接受任何对象,只要这个对象有与Test::Unit::TestSuite相对应的suite方法。TestSuite可以依次包含其它的TestSuite或单独的测试(通常由TestSuites创建)。换句话说,你可以很容易地包装一组TestCase和TestSuite,像这样:
require 'test/unit/testsuite'
require 'tc_myfirsttests'
require 'tc_moretestsbyme'
require 'ts_anothersetoftests'
class TS_MyTests
def self.suite
suite = Test::Unit::TestSuite.new
suite << TC_MyFirstTests.suite
suite << TC_MoreTestsByMe.suite
suite << TS_AnotherSetOfTests.suite
return suite
end
end
Test::Unit::UI::Console::TestRunner.run(TS_MyTests)
现在,这还有点笨重,即使Test::Unit已经为我们做了很多,当你请求’test/unit’时自动地为你包装了这些。这是什么意思?它的意思是你应该像这个例子一样写测试案例:
require 'test/unit'
require 'tc_myfirsttests'
require 'tc_moretestsbyme'
require 'ts_anothersetoftests'
Test::Unit很聪明,它足以找到所有的ObjectSpace内的测试案例并为你将它们包装在一个单元内。然后它使用控制台运行器运行动态单元。
Ruby 內建 Unit Testing Framework, Test::Unit, 使用起來也蠻簡單的.
- 寫你的測試 class 記得從 Test::Unit::TestCase 繼承
require 'test/unit'
class MyFriend < Test::Unit::TestCase
def setup
@friends = ['dell', 'apple', 'sony']
end
def teardown
end
def test_add
assert_instance_of(Array, @friends, "The @friends must be Array")
assert_equal(3, @friends.size, "The size is not 3")
@friends << 'acer'
assert_equal(4, @friends.size, "The size is not 4")
end
def test_remove
assert_equal(3, @friends.size, "The size is not 3")
@friends.delete_if {|n| n == 'apple'}
assert_equal(2, @friends.size, "The size is not 2")
end
end
- 現在有了 test class, 你會想要測試他的執行結果.
require 'test/unit/ui/console/testrunner'
Test::Unit::UI::Console::TestRunner.run(MyFriend)