2008-11-01 22 views
26

Je suis aux prises avec Test :: Unit. Quand je pense aux tests unitaires, je pense à un test simple par fichier. Mais dans le cadre de Ruby, je dois écrire à la place:Dans Test :: Unit :: TestCase de Ruby, comment remplacer la méthode initialize?

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

    def test_1 
    end 

    def test_1 
    end 
end 

Mais la configuration et la gestion pour chaque invocation désassemblage d'une méthode test_ de *. C'est exactement ce que je ne veux pas. Au contraire, je veux une méthode d'installation qui ne fonctionne qu'une seule fois pour toute la classe. Mais je n'arrive pas à écrire mon propre initialize() sans casser l'initialisation de TestCase.

Est-ce possible? Ou est-ce que je rends cela désespérément compliqué?

+0

Deux méthodes d'essai avec le même nom conduit à la première méthode ne en cours d'exécution. Vous pourriez mettre un flunk dans le premier test, et les tests passeraient toujours. Un effet secondaire de la programmation couper-coller. –

+0

Oui, et c'est facile. Ceci est finalement implémenté dans TestUnit. Voir mon message waaaay sur cette page. – jpgeek

Répondre

9

C'est comme ça que ça doit marcher!

Chaque test doit être complètement isolé du reste, de sorte que les méthodes setup et tear_down sont exécutées une fois pour chaque cas de test. Il y a des cas, cependant, où vous pouvez vouloir plus de contrôle sur le flux d'exécution. Vous pouvez ensuite grouper les cas de test dans suites.

Dans votre cas, vous pouvez écrire quelque chose comme ce qui suit:

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

Le TestDecorator définit une suite spéciale qui fournit une méthode setup et tear_down qui exécuté qu'une seule fois avant et après le fonctionnement de l'ensemble de test- cas qu'il contient.

L'inconvénient de ceci est que vous devez dire Test :: Unité comment exécuter les tests dans l'unité. Dans le cas où votre appareil contient de nombreux cas-tests et vous avez besoin d'un décorateur pour un seul d'entre eux vous aurez besoin de quelque chose comme ceci:

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 documentation fournit une bonne explication sur le fonctionnement des suites.

+0

Pourquoi ai-je une erreur "constante non initialisée Test :: Unit :: TestSuite"? – Alexandre

1

J'ai rencontré ce problème exact et créé une sous-classe de Test::Unit::TestCase pour faire exactement ce que vous décrivez.

Voici ce que j'ai trouvé. Il fournit ses propres méthodes setup et teardown qui comptent le nombre de méthodes de la classe commençant par 'test'. Le premier appel à setup appelle global_setup et le dernier appel à teardown appelle

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 

Créez votre cas de test comme celui-ci:

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 

La faute en est que vous ne pouvez pas fournissez vos propres méthodes setup et teardown par test, sauf si vous utilisez la méthode de classe setup :method_name (uniquement disponible dans Rails 2.X?) et si vous avez une suite de tests ou quelque chose qui exécute uniquement l'une des méthodes de test, alors le a gagné Ne pas être appelé parce qu'il suppose que toutes les méthodes de test seront éventuellement exécutées.

0

Utilisez TestSuite comme @ romulo-a-ceccon décrit pour les préparations spéciales pour chaque suite de tests.

Cependant, je pense qu'il devrait être mentionné ici que les tests unitaires doivent être exécutés dans une isolation totale. Ainsi, le flux d'exécution est setup-test-teardown qui devrait garantir que chaque test ne soit pas perturbé par tout ce que les autres tests ont fait.

0

J'ai créé un mixin appelé SetupOnce. Voici un exemple de l'utiliser.

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 

Et voici le code actuel; Notez qu'il nécessite un autre module disponible à partir du premier lien dans les notes de bas de page.

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 

Notes:

  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

Comme mentionné dans le livre de Hal Fulton "The Ruby Way". Il remplace la méthode self.suite de Test :: Unit qui permet aux cas de test d'une classe de s'exécuter en tant que suite.

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

Voici un exemple:

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

Merci pour votre réponse! –

+0

Malheureusement, la deuxième réponse ne fonctionne pas avec la version actuelle de Test :: Unit, au moins ne fonctionne pas dans RubyMine sous Windows 7. Couper et coller le code dans RubyMine et l'exécuter, démarrer et arrêter chaque exécution _twice_, pas _once_: ( Je ne l'utilise que pour enregistrer le début et la fin d'un cas de test (en utilisant Logger), donc je peux voir quel cas de test a produit quel bit du log, donc je peux vivre avec réticence, mais d'autres personnes ne pas être en mesure de. – digitig

+0

que faire si je veux faire quelque chose de semblable dans ActionController :: TestCase? –

2

Eh bien, j'ai accompli essentiellement de la même façon d'une manière vraiment laide et horrible, mais il était plus rapide. :) Une fois que je me suis aperçu que les tests sont exécutés par ordre alphabétique:

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 

Il aint assez, mais il fonctionne :)

2

Pour résoudre ce problème je la construction d'installation, avec une seule méthode d'essai suivi. Cette méthode de test appelle tous les autres tests.

Par exemple

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 pour la RSpec réponse ci-dessus par @ orion-edwards. J'aurais commenté sa réponse, mais je n'ai pas encore assez de réputation pour commenter les réponses.

-je utiliser test/unit et RSpec beaucoup et je dois dire ... le code que tout le monde a été un manque affiche est très caractéristique importante de before(:all) qui est: @instance support variable.

En RSpec, vous pouvez faire:

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 

Les implémentations de #startup et #shutdown surtout se concentrer sur faire en sorte que ces méthodes ne s'appelle une fois pour toute la classe TestCase, mais toutes les variables d'instance utilisées dans ces les méthodes seraient perdues!

RSpec exécute ses before(:all) dans sa propre instance d'Object et toutes les variables locales sont copiées avant chaque test.

Pour accéder à toutes les variables qui sont créées au cours d'une méthode #startup globale, vous devez soit:

  • copie toutes les variables d'instance créées par #startup, comme RSpec-t
  • définir vos variables #startup dans une portée que vous pouvez accéder à partir de vos méthodes de test, par exemple. @@class_variables ou créer attr_accessors niveau de classe qui donnent accès à la @instance_variables que vous créez à l'intérieur de def self.startup

Just my 0,02 $!

7

ENFIN, l'appareil à tester a cette mise en œuvre! Woot! Si vous utilisez v ou plus tard 2.5.2, vous pouvez simplement utiliser ceci:

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

Cela exécutera une fois lorsque vous démarrez vos tests hors tension. Il y a aussi des rappels qui s'exécutent au début de chaque test (démarrage), en plus de ceux qui s'exécutent avant chaque test (installation).

http://test-unit.rubyforge.org/test-unit/en/Test/Unit.html#at_start-class_method

2

Je sais que c'est tout à fait un ancien poste, mais j'avais la question (et avait des classes déjà écrit au SEV/unité) et ave répondu en utilisant une autre méthode, si elle peut aider ...

Si vous avez seulement besoin de l'équivalent de la fonction de démarrage, vous pouvez utiliser les variables de classe:

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

J'ai lu toutes les réponses ici. C'est le seul qui me fasse 100% de sens clair - les autres sont tellement obscurs et complexes et font que mon cerveau manque de cerveau et veut vomir. –