ruby unit-testing testunit

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:

  1. http://redcorundum.blogspot.com/2006/06/mixing-in-class-methods.html

  2. http://infovore.org/archives/2006/08/02/getting-a-class-object-in-ruby-from-a-string-containing-that-classes-name/


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 de def 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