2008-11-01 8 views
26

Estoy luchando con Test :: Unit. Cuando pienso en pruebas unitarias, pienso en una prueba simple por archivo. Sin embargo, en el marco de Ruby, que en cambio debe escribir:En Ruby's Test :: Unit :: TestCase, ¿cómo puedo anular el método de inicialización?

class MyTest < Test::Unit::TestCase 
    def setup 
    end 

    def test_1 
    end 

    def test_1 
    end 
end 

pero la instalación y desmontaje de ejecución 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 esto posible? ¿O estoy haciendo esto desesperadamente complicado?

+0

dos métodos de ensayo con el mismo nombre conduce al primer método no se está ejecutando. Podrías poner un error en la primera prueba, y las pruebas aún pasarían. Un efecto secundario de la programación de cortar y pegar. –

+0

Sí, y es fácil. Esto finalmente se implementa en TestUnit. Ver mi publicación waaaay en esta página. – jpgeek

Respuesta

9

¡Así se supone que funciona!

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 se 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)) 

El TestDecorator define una suite especial que proporciona un método setup y tear_down la que sólo se ejecutan una vez antes y después de la ejecución del conjunto de los Ensayos casos que contiene.

El inconveniente de esto es que debe indicar Prueba :: Unidad cómo ejecutar las pruebas en la unidad. En el caso de que su unidad contiene muchos casos de prueba y que necesita un decorador de sólo uno de ellos que necesita 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 ofrece una buena explicación sobre cómo funcionan las suites.

+0

¿por qué recibo un error "Constante no inicializada Test :: Unit :: TestSuite"? – Alexandre

1

Me encontré con 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 sus propios métodos setup y teardown que cuentan el número de métodos en la clase que comienzan con 'prueba'. En la primera llamada a setup que llama global_setup y en la última llamada a teardown que llama

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 

Crear casos de prueba como esta:

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 fallo en esto es que no se puede proporcione sus propios métodos setup y teardown por prueba a menos que use el método de clase setup :method_name (solo disponible en Rails 2.X?) y si tiene un conjunto de pruebas o algo que solo ejecuta uno de los métodos de prueba, entonces ganó el No se llama porque supone que todos los métodos de prueba se ejecutarán eventualmente.

0

Utilice TestSuite como @ romulo-a-ceccon descrito para preparaciones especiales para cada suite 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.

0

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/

24

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.

def self.suite 
    mysuite = super 
    def mysuite.run(*args) 
     MyTest.startup() 
     super 
     MyTest.shutdown() 
    end 
    mysuite 
end 

Aquí se muestra 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 
+0

¡Gracias por una gran respuesta! –

+0

Lamentablemente, la segunda respuesta no funciona con la versión actual de Test :: Unit, al menos no se ejecuta en RubyMine en Windows 7. Cortar y pegar el código en RubyMine y ejecutarlo, iniciar y cerrar cada ejecución _twice_, not _once_: ( Solo lo estoy usando para registrar el inicio y el final de un caso de prueba (usando Logger), así puedo ver qué caso de prueba produjo qué bit del registro, así que puedo vivir a regañadientes con eso, pero otras personas podrían no poder. – digitig

+0

¿Qué pasa si quiero hacer algo similar en ActionController :: TestCase? –

2

Bueno, he logrado básicamente de la misma manera en una manera muy fea y horrible, pero era 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 

Aint bastante, pero funciona :)

2

Para resolver este problema he utilizado el constructo de configuración, con el método de una sola prueba siguieron. 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 
0

+1 para la RSpec respuesta anterior por @ Orion-Edwards. Habría comentado sobre su respuesta, pero aún no tengo suficiente reputación para comentar las respuestas.

Puedo utilizar test/unit y RSpec mucho y tengo que decir ... el código que todo el mundo ha estado publicando no se encuentra una característica importante muy de before(:all) que es: @instance apoyo variable.

En RSpec, que puede 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 sobre todo se centran en asegurarse de que estos métodos sólo se les llama una vez para toda la clase TestCase, pero ninguna de las variables de instancia utilizan en estos métodos se perderían!

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 las variables que se crean durante un método global #startup, que tendría que o bien:

  • copia todas las variables de instancia creados por #startup, como RSpec hace
  • definen las variables en #startup en un alcance al que puede acceder desde sus métodos de prueba, por ej. @@class_variables o crear attr_accessors de nivel de clase que dan acceso a la @instance_variables que se crea en el interior de def self.startup

Sólo mi $ 0,02!

7

¡FINALMENTE, la unidad de prueba lo ha implementado! ¡Woot! Si está utilizando v 2.5.2 o posterior, sólo puede utilizar esto:

Test::Unit.at_start do 
    # initialization stuff here 
end 

Esto se ejecutará una vez al iniciar sus pruebas fuera. 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

2

Sé que esto es bastante una entrada antigua, pero tenía el problema (y ya había escrito clases usando Tes/unidad) y el ave respondido con cualquier otro método, por lo que si puede ayudar ...

Si sólo necesita el equivalente de la función de arranque, se pueden utilizar 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 
+0

He leído todas las respuestas aquí. Este es el único que tiene un sentido 100% claro para mí: los otros son tan oscuros y complejos y hace que mi pequeño cerebro carente de caffeeine quiera vomitar. –

Cuestiones relacionadas