模块 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

公共类方法

at_exit(&hook) 点击切换源代码

注册一个在运行测试后运行的钩子。要注册多个钩子,请多次调用此方法。

这是一个示例测试用例

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

  • at_exit

@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
at_start(&hook) 点击切换源代码

注册一个在运行测试之前运行的钩子。要注册多个钩子,请多次调用此方法。

这是一个示例测试用例

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

这是一个调用顺序

  • at_start

  • 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
run=(have_run) 点击切换源代码

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
run?() 点击切换源代码

测试是否已经运行?

@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
run_at_exit_hooks() 点击切换源代码

@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
run_at_start_hooks() 点击切换源代码

@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