我正在与Test :: Unit挣扎。当我想到单元测试时,我想到了每个文件的一个简单测试。但是在Ruby的框架中,我必须写一下:
class MyTest < Test::Unit::TestCase
def setup
end
def test_1
end
def test_1
end
end
但是每次调用test_ *方法时都会运行setup和teardown。这正是我不想要的。相反,我想要一个只为整个类运行一次的设置方法。但我似乎无法在不破坏TestCase初始化的情况下编写自己的initialize()。
这可能吗?还是我让这无可救药地变得复杂?
答案 0 :(得分:25)
正如Hal Fulton的书“The Ruby Way”中所提到的那样。 他重写了Test :: Unit的self.suite方法,它允许类中的测试用例作为套件运行。
def self.suite
mysuite = super
def mysuite.run(*args)
MyTest.startup()
super
MyTest.shutdown()
end
mysuite
end
以下是一个例子:
class MyTest < Test::Unit::TestCase
class << self
def startup
puts 'runs only once at start'
end
def shutdown
puts 'runs only once at end'
end
def suite
mysuite = super
def mysuite.run(*args)
MyTest.startup()
super
MyTest.shutdown()
end
mysuite
end
end
def setup
puts 'runs before each test'
end
def teardown
puts 'runs after each test'
end
def test_stuff
assert(true)
end
end
答案 1 :(得分:9)
它应该如何运作!
每个测试应与其余测试完全隔离,因此每个测试用例都会执行一次setup
和tear_down
方法。但是,有些情况下,您可能希望更多地控制执行流程。然后,您可以将测试用例分组到 suites 。
在您的情况下,您可以编写如下内容:
require 'test/unit'
require 'test/unit/ui/console/testrunner'
class TestDecorator < Test::Unit::TestSuite
def initialize(test_case_class)
super
self << test_case_class.suite
end
def run(result, &progress_block)
setup_suite
begin
super(result, &progress_block)
ensure
tear_down_suite
end
end
end
class MyTestCase < Test::Unit::TestCase
def test_1
puts "test_1"
assert_equal(1, 1)
end
def test_2
puts "test_2"
assert_equal(2, 2)
end
end
class MySuite < TestDecorator
def setup_suite
puts "setup_suite"
end
def tear_down_suite
puts "tear_down_suite"
end
end
Test::Unit::UI::Console::TestRunner.run(MySuite.new(MyTestCase))
TestDecorator
定义了一个特殊套件,它提供了setup
和tear_down
方法,该方法在运行它包含的一组测试用例之前和之后只运行一次。
这样做的缺点是你需要告诉 Test :: Unit 如何在单元中运行测试。如果您的单元包含许多测试用例,并且您只需要一个装饰器,那么您需要这样的东西:
require 'test/unit'
require 'test/unit/ui/console/testrunner'
class TestDecorator < Test::Unit::TestSuite
def initialize(test_case_class)
super
self << test_case_class.suite
end
def run(result, &progress_block)
setup_suite
begin
super(result, &progress_block)
ensure
tear_down_suite
end
end
end
class MyTestCase < Test::Unit::TestCase
def test_1
puts "test_1"
assert_equal(1, 1)
end
def test_2
puts "test_2"
assert_equal(2, 2)
end
end
class MySuite < TestDecorator
def setup_suite
puts "setup_suite"
end
def tear_down_suite
puts "tear_down_suite"
end
end
class AnotherTestCase < Test::Unit::TestCase
def test_a
puts "test_a"
assert_equal("a", "a")
end
end
class Tests
def self.suite
suite = Test::Unit::TestSuite.new
suite << MySuite.new(MyTestCase)
suite << AnotherTestCase.suite
suite
end
end
Test::Unit::UI::Console::TestRunner.run(Tests.suite)
Test::Unit documentation文档提供了有关套件如何工作的良好解释。
答案 2 :(得分:7)
最后,测试单元实现了这一点!活泉! 如果您使用的是v 2.5.2或更高版本,则可以使用:
Test::Unit.at_start do
# initialization stuff here
end
当您开始测试时,这将运行一次。除了在每次测试(设置)之前运行的回调之外,还有在每个测试用例(启动)开始时运行的回调。
http://test-unit.rubyforge.org/test-unit/en/Test/Unit.html#at_start-class_method
答案 3 :(得分:2)
嗯,我以一种非常丑陋和可怕的方式完成了基本相同的方式,但它更快。 :)一旦我意识到测试是按字母顺序运行的:
class MyTests < Test::Unit::TestCase
def test_AASetup # I have a few tests that start with "A", but I doubt any will start with "Aardvark" or "Aargh!"
#Run setup code
end
def MoreTests
end
def test_ZTeardown
#Run teardown code
end
它很漂亮,但它有效:)
答案 4 :(得分:2)
为了解决这个问题,我使用了setup结构,只使用了一种测试方法。这一个测试方法正在调用所有其他测试。
例如
class TC_001 << Test::Unit::TestCase
def setup
# do stuff once
end
def testSuite
falseArguments()
arguments()
end
def falseArguments
# do stuff
end
def arguments
# do stuff
end
end
答案 5 :(得分:2)
我知道这是一个相当古老的帖子,但我遇到了问题(并且已经使用Tes / unit编写过课程)并且使用其他方法回答了问题,所以如果它有帮助...
如果您只需要等效的启动函数,则可以使用类变量:
class MyTest < Test::Unit::TestCase
@@cmptr = nil
def setup
if @@cmptr.nil?
@@cmptr = 0
puts "runs at first test only"
@@var_shared_between_fcs = "value"
end
puts 'runs before each test'
end
def test_stuff
assert(true)
end
end
答案 6 :(得分:1)
我遇到了这个确切的问题并创建了一个Test::Unit::TestCase
的子类来完成您所描述的内容。
这就是我想出的。它提供了自己的setup
和teardown
方法,用于计算类中以'test'开头的方法数。在第一次拨打setup
时,系统会调用global_setup
,并在最后一次拨打teardown
时拨打global_teardown
class ImprovedUnitTestCase < Test::Unit::TestCase
cattr_accessor :expected_test_count
def self.global_setup; end
def self.global_teardown; end
def teardown
if((self.class.expected_test_count-=1) == 0)
self.class.global_teardown
end
end
def setup
cls = self.class
if(not cls.expected_test_count)
cls.expected_test_count = (cls.instance_methods.reject{|method| method[0..3] != 'test'}).length
cls.global_setup
end
end
end
像这样创建测试用例:
class TestSomething < ImprovedUnitTestCase
def self.global_setup
puts 'global_setup is only run once at the beginning'
end
def self.global_teardown
puts 'global_teardown is only run once at the end'
end
def test_1
end
def test_2
end
end
除非您使用setup
类方法(仅适用于Rails 2.X),否则您无法提供自己的每次测试teardown
和setup :method_name
方法。 ?)如果你有一个测试套件或只运行其中一种测试方法的东西,那么global_teardown
将不会被调用,因为它假定所有测试方法最终都会运行。
答案 7 :(得分:0)
使用TestSuite作为@ romulo-a-ceccon描述每个测试套件的特殊准备。
但是我认为应该在这里提到单元测试是完全隔离的。因此,执行流程是setup-test-teardown,它应该保证每个测试都不受其他测试所做的任何干扰。
答案 8 :(得分:0)
我创建了一个名为SetupOnce的mixin。这是使用它的一个例子。
require 'test/unit'
require 'setuponce'
class MyTest < Test::Unit::TestCase
include SetupOnce
def self.setup_once
puts "doing one-time setup"
end
def self.teardown_once
puts "doing one-time teardown"
end
end
这是实际的代码;请注意,它需要脚注中第一个链接提供的另一个模块。
require 'mixin_class_methods' # see footnote 1
module SetupOnce
mixin_class_methods
define_class_methods do
def setup_once; end
def teardown_once; end
def suite
mySuite = super
def mySuite.run(*args)
@name.to_class.setup_once
super(*args)
@name.to_class.teardown_once
end
return mySuite
end
end
end
# See footnote 2
class String
def to_class
split('::').inject(Kernel) {
|scope, const_name|
scope.const_get(const_name)
}
end
end
脚注:
答案 9 :(得分:0)
我经常使用test / unit 和 RSpec,我不得不说......每个人都发布的代码缺少{em>非常的重要功能{{ 1}}这是:@instance变量支持。
在RSpec中,你可以这样做:
before(:all)
describe 'Whatever' do
before :all do
@foo = 'foo'
end
# This will pass
it 'first' do
assert_equal 'foo', @foo
@foo = 'different'
assert_equal 'different', @foo
end
# This will pass, even though the previous test changed the
# value of @foo. This is because RSpec stores the values of
# all instance variables created by before(:all) and copies
# them into your test's scope before each test runs.
it 'second' do
assert_equal 'foo', @foo
@foo = 'different'
assert_equal 'different', @foo
end
end
和#startup
的实现首先集中在确保只为整个#shutdown
类调用这些方法一次,但这些方法中使用的任何实例变量都是丢失!
RSpec在其自己的Object实例中运行其TestCase
,并在运行每个测试之前复制所有局部变量。
要访问在全局before(:all)
方法期间创建的任何变量,您需要:
#startup
创建的所有实例变量,如RSpec #startup
中的变量定义到您可以从测试方法中访问的范围,例如。 #startup
或创建类级别的attr_accessors,以提供您在@@class_variables
@instance_variables
的访问权限
只需我0.02美元!