En Ruby''s Test:: Unit:: TestCase, ¿cómo anulo el método de inicialización?
unit-testing testunit (10)
Estoy luchando con Test :: Unit. Cuando pienso en pruebas unitarias, pienso en una prueba simple por archivo. Pero en el marco de Ruby, debo escribir:
class MyTest < Test::Unit::TestCase
def setup
end
def test_1
end
def test_1
end
end
Pero la instalación y el desmontaje se ejecutan para cada invocación de un método test_ *. Esto es exactamente lo que no quiero. Más bien, quiero un método de configuración que se ejecute solo una vez para toda la clase. Pero parece que no puedo escribir mi propio initialize () sin romper la inicialización de TestCase.
¿Es eso posible? ¿O estoy haciendo esto desesperadamente complicado?
¡Así se supone que debe funcionar!
Cada prueba debe estar completamente aislada del resto, por lo que los métodos setup
y tear_down
se ejecutan una vez para cada caso de prueba. Sin embargo, hay casos en los que puede desear más control sobre el flujo de ejecución. Luego puede agrupar los casos de prueba en suites .
En su caso, podría escribir algo como lo siguiente:
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
define un conjunto especial que proporciona una setup
y tear_down
método tear_down
que se ejecutan solo una vez antes y después de la ejecución del conjunto de casos de prueba que contiene.
El inconveniente de esto es que necesita decirle a Test :: Unit cómo ejecutar las pruebas en la unidad. En caso de que su unidad contenga muchos casos de prueba y necesite un decorador para solo uno de ellos, necesitará algo como esto:
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)
La documentación de documentación de Test :: Unit proporciona una buena explicación sobre cómo funcionan las suites.
Encontré este problema exacto y creé una subclase de Test::Unit::TestCase
para hacer exactamente lo que describes.
Esto es lo que se me ocurrió. Proporciona su propia setup
y métodos de teardown
que cuentan la cantidad de métodos en la clase que comienzan con ''prueba''. En la primera llamada para setup
, llama a global_setup
y en la última llamada a teardown
llama a 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
Crea tus casos de prueba de esta manera:
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
El error en esto es que no puede proporcionar su propia setup
por prueba y métodos de teardown
menos que use el método de setup :method_name
class (solo disponible en Rails 2.X?) Y si tiene un banco de pruebas o algo que solo ejecuta uno de los métodos de prueba, luego no se llamará a global_teardown
porque supone que todos los métodos de prueba se ejecutarán eventualmente.
Use TestSuite como @ romulo-a-ceccon descrito para preparaciones especiales para cada conjunto de pruebas.
Sin embargo, creo que debería mencionarse aquí que las pruebas unitarias deben ejecutarse en total aislamiento. Por lo tanto, el flujo de ejecución es setup-test-desmontaje, lo que debería garantizar que cada prueba no se vea afectada por nada de lo que hicieron las otras pruebas.
Creé una mezcla llamada SetupOnce. Aquí hay un ejemplo de cómo usarlo.
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
Y aquí está el código real; note que requiere otro módulo disponible desde el primer enlace en las notas al pie.
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
Notas al pie:
Bueno, logré básicamente lo mismo de una manera realmente fea y horrible, pero fue más rápido. :) Una vez que me di cuenta de que las pruebas se ejecutan alfabéticamente:
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
No es bonito, pero funciona :)
Para resolver este problema utilicé la construcción de configuración, con solo un método de prueba seguido. Este método de prueba llama a todas las demás pruebas.
Por ejemplo
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
+1 para la respuesta RSpec anterior por @ orion-edwards. Habría comentado sobre su respuesta, pero aún no tengo suficiente reputación para comentar las respuestas.
Uso test / unit y RSpec mucho y tengo que decir ... el código que todo el mundo ha estado publicando le falta una característica muy importante de before(:all)
que es: @instance variable support.
En RSpec, puedes hacer:
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
Las implementaciones de #startup
y #shutdown
centran sobre todo en asegurarse de que estos métodos solo se TestCase
una vez para toda la clase TestCase
, pero se TestCase
todas las variables de instancia utilizadas en estos métodos.
RSpec ejecuta su before(:all)
en su propia instancia de Object y todas las variables locales se copian antes de ejecutar cada prueba.
Para acceder a cualquier variable que se cree durante un método #startup
global, necesitarás:
- copie todas las variables de instancia creadas por
#startup
, como RSpec hace - defina sus variables en
#startup
en un ámbito al que pueda acceder desde sus métodos de prueba, p. ej.@@class_variables
o cree attr_accessors de nivel de clase que proporcionen acceso a@instance_variables
que cree dentro dedef self.startup
¡Solo mi $ 0.02!
¡FINALMENTE, la unidad de prueba ha implementado esto! ¡Woot! Si está utilizando v 2.5.2 o posterior, puede usar esto:
Test::Unit.at_start do
# initialization stuff here
end
Esto se ejecutará una vez cuando comiences tus pruebas. También hay callbacks que se ejecutan al comienzo de cada caso de prueba (inicio), además de los que se ejecutan antes de cada prueba (configuración).
http://test-unit.rubyforge.org/test-unit/en/Test/Unit.html#at_start-class_method
Sé que esta es una publicación bastante antigua, pero tuve el problema (y ya había escrito clases usando Tes / unit) y he respondido utilizando otro método, por lo que si puede ayudar ...
Si solo necesita el equivalente de la función de inicio, puede usar las variables de clase:
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
Como se menciona en el libro de Hal Fulton "The Ruby Way". Anula el método self.suite de Test :: Unit que permite que los casos de prueba de una clase se ejecuten como un conjunto de aplicaciones.
def self.suite
mysuite = super
def mysuite.run(*args)
MyTest.startup()
super
MyTest.shutdown()
end
mysuite
end
Aquí hay un ejemplo:
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