模块 Test::Unit
# Test::Unit
- Ruby Unit
测试框架
## 简介
单元测试正在各个领域兴起,这很大程度上归功于它是 XP 的核心实践。虽然 XP 很棒,但单元测试已经存在很长时间了,并且一直是个好主意。但是,好的单元测试的关键之一不仅是编写测试,还要拥有测试。有什么区别?好吧,如果你只是编写一个测试并将其丢弃,你就无法保证稍后不会发生任何改变而破坏你的代码。另一方面,如果你拥有测试(显然你必须先编写它们),并尽可能频繁地运行它们,你就会慢慢建立起一道墙,如果有什么东西被破坏,你会立即知道。这时,单元测试达到了它的最高效用。
进入 Test::Unit
,一个用于 Ruby 中单元测试的框架,通过使其易于编写和拥有测试来帮助你设计、调试和评估你的代码。
## 注意事项
Test::Unit
已经从 Lapidary 发展而来并取代了它。
## 反馈
我喜欢(并尽力实践)XP,所以我重视早期发布、用户反馈和简洁、简单、富有表现力的代码。在我所做的一切中总有改进的余地,Test::Unit
也不例外。请让我知道你对 Test::Unit
的看法,以及你希望看到哪些扩展/更改/改进等。如果你发现错误,请尽快告诉我;告知我错误的最好方法之一是提交一个新的测试来捕获它 :-) 此外,我很乐意听到你在 Test::Unit
方面的任何成功案例,并且非常感谢你可能添加的任何文档。我的联系信息如下。
## 联系方式
* [GitHub issues on test-unit/test-unit](https://github.com/test-unit/test-unit/issues): If you have any issues, please report them to here. * [GitHub pull requests on test-unit/test-unit](https://github.com/test-unit/test-unit/pulls): If you have any patches, please report them to here. * [ruby-talk mailing list](https://ruby-lang.org.cn/en/community/mailing-lists/): If you have any questions, you can ask them here.
## 致谢
我想感谢…
Matz,感谢他创造了一门伟大的语言!
Masaki Suketa,感谢他在 RubyUnit 上的工作,它在很长一段时间内满足了 Ruby 世界的迫切需求。我也感谢他在完善 Test::Unit
和正确实现 RubyUnit 兼容层方面的帮助。他允许 Test::Unit
取代 RubyUnit 的慷慨之举继续挑战我,让我更愿意放弃我自己的权利。
Ken McKinlay,感谢他对单元测试的兴趣和工作,以及他愿意就此进行对话。他在指出 RubyUnit 兼容层中的一些漏洞方面也提供了很大的帮助。
Dave Thomas,感谢他最初的想法,促成了极其简单的 “require ‘test/unit’”,以及他的代码,通过允许从命令行选择测试来进一步改进它。此外,如果没有 RDoc,Test::Unit
的文档会比现在更糟糕得多。
感谢所有在错误报告、功能想法、鼓励继续等方面提供帮助的人。成为 Ruby 社区的一员真是一种荣幸。
感谢 RoleModel Software 的伙计们,他们容忍我每次用 Java 编写代码时都会重复说:“但在 Ruby 中会容易得多!”
感谢我的创造者,感谢他赋予我生命,并赋予它更丰富的意义。
## 许可证
Test::Unit
版权归 Nathaniel Talbott 所有,© 2000-2003 年。它是自由软件,并根据 Ruby 许可证分发。请参阅 COPYING 文件。
例外:lib/test/unit/diff.rb 的版权归 Kouhei Sutou 所有,© 2008-2010 年,以及 Python 软件基金会 © 2001-2008 年。它是自由软件,并根据 Ruby 许可证和/或 PSF 许可证分发。请参阅 COPYING 文件和 PSFL 文件。
## 担保
本软件按“原样”提供,不提供任何明示或暗示的保证,包括但不限于对适销性和特定用途适用性的暗示保证。
## 作者
Nathaniel Talbott。版权所有 © 2000-2003 年,Nathaniel Talbott
# 用法
单元测试背后的总体思路是,你编写一个针对测试夹具的测试方法,该方法对你的代码进行某些断言。这些测试方法被捆绑到一个测试套件中,并且可以在开发人员想要的任何时候运行。运行的结果收集在测试结果中,并通过一些 UI
显示给用户。所以,让我们分解一下,看看 Test::Unit
如何提供这些必要的组成部分。
## 断言
这些是框架的核心。可以将断言视为对预期结果的陈述,例如“我断言 x 应该等于 y”。如果在执行断言时,结果证明它是正确的,则什么都不会发生,生活是美好的。另一方面,如果你的断言结果为假,则会传播带有相关信息的错误,以便你可以返回并使你的断言成功,并且,生活再次变得美好。有关当前断言的解释,请参阅 Test::Unit::Assertions
。
显然,这些断言必须在知道它们并可以使用它们的通过/失败值做一些有意义的事情的上下文中调用。此外,将一堆相关测试(每个测试都由一个方法表示)收集到一个知道如何运行它们的公共测试类中也很方便。测试将与它们正在测试的代码分开在一个单独的类中,原因有几个。首先,它允许你的代码保持整洁,避免测试代码的干扰,使其更容易维护。其次,它允许在部署时剥离测试,因为它们实际上是为了你,开发人员,而你的用户不需要它们。第三,也是最重要的一点,它允许你为你的测试设置一个公共的测试夹具。
什么是测试夹具?好吧,测试不是在真空中进行的;相反,它们是针对它们正在测试的代码运行的。通常,一组测试将针对一组公共数据运行,该数据也称为夹具。如果它们全部捆绑到同一个测试类中,它们可以共享该数据的设置和拆卸,从而消除不必要的重复,并使其更容易添加相关测试。
Test::Unit::TestCase
将一组测试方法包装在一起,并允许你轻松地为每个测试设置和拆卸相同的测试夹具。这是通过覆盖 setup 和/或 teardown 来完成的,它们将在每个运行的测试方法之前和之后调用。TestCase
还知道如何将你的断言结果收集到 Test::Unit::TestResult
中,然后可以将其报告给你… 但是我跑题了。要编写测试,请按照以下步骤操作
-
确保
Test::Unit
在你的库路径中。 -
在你的测试脚本中 require ‘test/unit’。
-
创建一个继承
Test::Unit::TestCase
的类。 -
向你的类添加一个以 “test” 开头的方法。
-
在你的测试方法中进行断言。
-
(可选)定义 setup 和/或 teardown 来设置和/或拆卸你的公共测试夹具。
-
现在你可以像运行任何其他 Ruby 脚本一样运行你的测试… 试一下看看!
一个非常简单的测试可能如下所示(注释掉 setup 和 teardown 以表明它们是完全可选的)
require 'test/unit' class MyTest < Test::Unit::TestCase # def setup # end # def teardown # end def test_fail assert(false, 'Assertion was false.') end end
## 测试
运行器
所以,现在你有了这个很棒的测试类,但你仍然需要一种方法来运行它并查看运行期间发生的任何失败。有一些测试运行器;控制台测试运行器、GTK+ 测试运行器等等。如果你 require ‘test/unit’ 并简单地运行该文件,则会自动为你调用控制台测试运行器。要使用另一个运行器,只需将默认测试运行器 ID 设置为 Test::Unit::AutoRunner
require 'test/unit' Test::Unit::AutoRunner.default_runner = "gtk2"
## 测试
套件
随着给定项目的单元测试越来越多,一次运行一个测试变得非常麻烦,并且还会引入由于忘记运行测试而忽略失败测试的可能性。突然之间,TestRunners 可以接收任何对象,该对象返回一个 Test::Unit::TestSuite
作为对 suite 方法的响应,这变得非常方便。TestSuite
反过来可以包含其他 TestSuite 或单个测试(通常由 TestCase
创建)。换句话说,你可以轻松地将一组 TestCases 和 TestSuites 包装在一起。
Test::Unit
通过在 require ‘test/unit’ 时自动包装它们来为你做更多的事情。这意味着什么?这意味着你可以像这样编写上面的测试用例
require 'test/unit' require 'test_myfirsttests' require 'test_moretestsbyme' require 'test_anothersetoftests'
Test::Unit
非常智能,可以找到 ObjectSpace 中存在的所有测试用例,并将它们包装到一个套件中。然后,它使用控制台 TestRunner 运行动态套件。
## 配置文件
Test::Unit
将当前工作目录中的 ‘test-unit.yml’ 读取为 Test::Unit
的配置文件。它可以包含以下配置
-
配色方案定义
-
要使用的测试运行器
-
测试运行器选项
-
要使用的测试收集器
除配色方案定义外,所有这些都由命令行选项指定。
以下是一些示例配色方案定义
color_schemes: inverted: success: name: red bold: true failure: name: green bold: true other_scheme: ...
以下是配色方案定义的语法
color_schemes: SCHEME_NAME: EVENT_NAME: name: COLOR_NAME intensity: BOOLEAN bold: BOOLEAN italic: BOOLEAN underline: BOOLEAN ... ...
SCHEME_NAME : 配色方案的名称
EVENT_NAME : 可选值包括 [success, failure, pending, omission, notification, error] 中的一个
COLOR_NAME : 可选值包括 [black, red, green, yellow, blue, magenta, cyan, white] 中的一个
BOOLEAN : true 或 false
您可以使用以下配置使用上述“反转”颜色方案
runner: console console_options: color_scheme: inverted color_schemes: inverted: success: name: red bold: true failure: name: green bold: true
## 问题?
我非常希望从各个级别的 Ruby 从业者那里获得关于本文档(或任何其他文档)中错别字、语法错误、不清晰的陈述、遗漏要点等的反馈。
常量
- VERSION
公共类方法
注册一个在运行测试后运行的钩子。要注册多个钩子,请多次调用此方法。
这是一个示例测试用例
Test::Unit.at_exit do # ... end class TestMyClass1 < Test::Unit::TestCase class << self def shutdown # ... end end def teardown # ... end def test_my_class1 # ... end def test_my_class2 # ... end end class TestMyClass2 < Test::Unit::TestCase class << self def shutdown # ... end end def teardown # ... end def test_my_class1 # ... end def test_my_class2 # ... end end
这是一个调用顺序
-
TestMyClass1#test_my_class1
-
TestMyClass1#teardown
-
TestMyClass1#test_my_class2
-
TestMyClass1#teardown
-
TestMyClass1.shutdown
-
TestMyClass2#test_my_class1
-
TestMyClass2#teardown
-
TestMyClass2#test_my_class2
-
TestMyClass2#teardown
-
TestMyClass2.shutdown
@example
Test::Unit.at_exit do puts "Exit!" end
@yield 一个在运行测试后运行的代码块。 @yieldreturn [void] @return [void]
@since 2.5.2
# File test-unit-3.6.7/lib/test/unit.rb, line 501 def at_exit(&hook) @@at_exit_hooks << hook end
注册一个在运行测试之前运行的钩子。要注册多个钩子,请多次调用此方法。
这是一个示例测试用例
Test::Unit.at_start do # ... end class TestMyClass1 < Test::Unit::TestCase class << self def startup # ... end end def setup # ... end def test_my_class1 # ... end def test_my_class2 # ... end end class TestMyClass2 < Test::Unit::TestCase class << self def startup # ... end end def setup # ... end def test_my_class1 # ... end def test_my_class2 # ... end end
这是一个调用顺序
-
TestMyClass1.startup
-
TestMyClass1#setup
-
TestMyClass1#test_my_class1
-
TestMyClass1#setup
-
TestMyClass1#test_my_class2
-
TestMyClass2#setup
-
TestMyClass2#test_my_class1
-
TestMyClass2#setup
-
TestMyClass2#test_my_class2
@example
Test::Unit.at_start do puts "Start!" end
@yield 一个在运行测试之前运行的代码块。 @yieldreturn [void] @return [void]
@since 2.5.2
# File test-unit-3.6.7/lib/test/unit.rb, line 414 def at_start(&hook) @@at_start_hooks << hook end
当 Test::Unit
已经运行时设置为 true。如果设置为 true,Test::Unit
将不会在退出时自动运行。
@deprecated 请改用 {Test::Unit::AutoRunner.need_auto_run=}.
# File test-unit-3.6.7/lib/test/unit.rb, line 328 def run=(have_run) AutoRunner.need_auto_run = (not have_run) end
测试是否已经运行?
@deprecated 请改用 {Test::Unit::AutoRunner.need_auto_run?} .
# File test-unit-3.6.7/lib/test/unit.rb, line 335 def run? not AutoRunner.need_auto_run? end
@api private
# File test-unit-3.6.7/lib/test/unit.rb, line 506 def run_at_exit_hooks @@at_exit_hooks.each do |hook| hook.call end end
@api private
# File test-unit-3.6.7/lib/test/unit.rb, line 419 def run_at_start_hooks @@at_start_hooks.each do |hook| hook.call end end