Puisque Ruby peut générer du code dynamique même pour la classe et la méthode, il peut être écrit comme suit.
ARR = [true, false, true]
Class.new(Test::Unit::TestCase) do
ARR.each_with_index do |e, idx|
define_method "test_#{idx}" do
assert e
end
do
end
Ceci est synonyme d'écriture comme suit.
ARR = [true, false, true]
class AnyTestCase < Test::Unit::TestCase
def test_0
assert ARR[0]
end
def test_1
assert ARR[1]
end
def test_2
assert ARR[2]
end
end
On dit souvent que le code de test devrait se concentrer sur ** DAMP (Phrases descriptives et significatives) ** plutôt que sur DRY (Ne vous répétez pas).
Je suis fondamentalement d'accord avec cette question. Le code de test doit être une "spécification", et dans ce sens, la même expression apparaît plusieurs fois dans une "spécification écrite dans un langage naturel", ce qui est inévitable en raison de la priorité accordée à la clarté. Cependant, il va sans dire que l'équilibre est important dans tout.
Maintenant, le problème ici est "le traitement de vérification pour une grande quantité de données". Par exemple, il y a quelque chose comme ça
Ciblons le déplacement du blog.
Si vous vérifiez honnêtement que le blog vers lequel vous vous déplacez contient plus d'un octet de contenu dans la balise <article>
, vous verrez:
require 'test/unit'
require 'httpclient'
require 'nokogiri'
class WebTestCase < Test::Unit::TestCase
def setup
@c = HTTPClient.new
end
def test_0
doc = Nokogiri::HTML(@c.get("https://example.com/entry/one").body)
assert_compare 1, "<", doc.css('article').text.length
end
def test_1
doc = Nokogiri::HTML(@c.get("https://example.com/entry/two").body)
assert_compare 1, "<", doc.css('article').text.length
end
def test_2
doc = Nokogiri::HTML(@c.get("https://example.com/entry/three").body)
assert_compare 1, "<", doc.css('article').text.length
end
end
Il est subtil d'écrire ceci jusqu'à def test_1000
.
Étant donné que la condition de vérification (assert_compare) ne change pas, je peux penser à un plan pour faire de la liste d'URL un itérateur (tableau) et la tester à plusieurs reprises, mais cela n'est pas recommandé pour la raison décrite plus loin.
require 'test/unit'
require 'httpclient'
require 'nokogiri'
URLS = %w(
https://example.com/entry/one
https://example.com/entry/two
https://example.com/entry/three
)
class WebTestCase < Test::Unit::TestCase
def setup
@c = HTTPClient.new
end
def test_content_length
URLS.each do |url|
doc = Nokogiri::HTML(@c.get(url).body)
assert_compare 1, "<", doc.css('article').text.length
end
end
end
La notation% w est pratique. Eh bien, cela semble bon à première vue. Cependant, le problème se produit en fait lorsque l'assertion échoue. Vous trouverez ci-dessous le résultat de l'exécution, mais ** je ne sais pas quelle URL a échoué **.
$ bundle exec ruby test_using_array.rb
Started
F
=========================================================================================================================================================================================
test_using_array.rb:25:in `test_content_length'
test_using_array.rb:25:in `each'
24: def test_content_length
25: URLS.each do |url|
26: doc = Nokogiri::HTML(@c.get(url).body)
=> 27: assert_compare 1, "<", doc.css('article').text.length
28: end
29: end
30: end
test_using_array.rb:27:in `block in test_content_length'
Failure: test_content_length(WebTestCase):
<1> < <0> should be true
<1> was expected to be less than
<0>.
=========================================================================================================================================================================================
Finished in 0.0074571 seconds.
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 tests, 3 assertions, 1 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
0% passed
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
C'est parce que l'unité du test est chaque méthode (def test_content_length
dans l'exemple ci-dessus).
Pour chaque itérateur (tableau), nous voulons créer une méthode pour chaque test et la tester. Après tout, n'y a-t-il pas d'autre choix que de l'implémenter honnêtement en copiant? N'y a-t-il pas d'autre choix que de générer du code avec la macro de Hidemaru? Quand vous en venez à l'idée, j'aimerais que vous vous souveniez de la génération de code dynamique qui est la magie noire (métaprogrammation) de Ruby.
Comme mentionné au début, Ruby peut générer du code de manière dynamique. La classe et la méthode ne font pas exception. Vous pouvez l'utiliser pour créer dynamiquement des méthodes de test une par une pour le contenu de l'itérateur.
URLS = %w(
https://example.com/entry/one
https://example.com/entry/two
https://example.com/entry/three
)
Class.new(Test::Unit::TestCase) do
def setup
@c = HTTPClient.new
end
URLS.each_with_index do |url, idx|
define_method "test_#{idx}" do
doc = Nokogiri::HTML(@c.get(url).body)
assert_compare 1, "<", doc.css('article').text.length
end
end
end
Ce code est équivalent à ce qui suit, qui est identique à l'implémentation simple.
URLS = %w(
https://example.com/entry/one
https://example.com/entry/two
https://example.com/entry/three
)
class AnyTestCase < Test::Unit::TestCase
def setup
@c = HTTPClient.new
end
def test_0
doc = Nokogiri::HTML(@c.get(URLS[0]).body)
assert_compare 1, "<", doc.css('article').text.length
end
def test_1
doc = Nokogiri::HTML(@c.get(URLS[1]).body)
assert_compare 1, "<", doc.css('article').text.length
end
def test_2
doc = Nokogiri::HTML(@c.get(URLS[2]).body)
assert_compare 1, "<", doc.css('article').text.length
end
end
Dans l'exemple suivant, l'emplacement de l'échec est spécifié comme «Échec: test_2 ()».
$ bundle exec ruby test_using_black_magic.rb
Loaded suite test_using_black_magic
Started
..F
=========================================================================================================================================================================================
test_using_black_magic.rb:27:in `block (3 levels) in <main>'
Failure: test_2():
<1> < <0> should be true
<1> was expected to be less than
<0>.
=========================================================================================================================================================================================
Finished in 0.007288 seconds.
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
3 tests, 3 assertions, 1 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
66.6667% passed
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
# each_with_index
a une origine 0. Un échec dans test_2 signifie un échec lorsque la valeur est ʻARR [2] `, vous devriez donc vérifier cette valeur.
--name
Si l'ordre des valeurs stockées dans l'itérateur est garanti, il prendra également en charge la spécification avec --name
. Par exemple, si vous voulez tester uniquement sur la valeur de ʻARR [1] `, spécifiez comme suit.
$ bundle exec ruby test_multiple.rb --name test_1
La génération dynamique de classe avec Class.new
et la génération de méthode dynamique avec define_method
sont les clés.
Si cela vous intéresse, recherchez "Ruby Black Magic".
assertion Ceci est valable lorsque les conditions sont uniformes. Au contraire, si l'assertion à appliquer diffère en fonction de la valeur d'entrée (= un branchement conditionnel se produit), il est préférable de l'implémenter honnêtement comme DAMP.
Je suis un amateur dans ce domaine, donc je ne sais pas si cela me convient.
EoT
Recommended Posts