[RUBY] Dynamisches Schreiben iterativer Testfälle mit test / unit (Test :: Unit)

Code aus der Theorie

Da Ruby auch für Klasse und Methode dynamischen Code generieren kann, kann dieser wie folgt geschrieben werden.

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

Dies ist gleichbedeutend mit dem Schreiben wie folgt.

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

Testcode ist DAMP von DRY

Es wird oft gesagt, dass sich der Testcode auf ** DAMP (beschreibende und aussagekräftige Sätze) ** und nicht auf DRY (nicht wiederholen) konzentrieren sollte.

Ich stimme dieser Angelegenheit grundsätzlich zu. Der Testcode sollte eine "Spezifikation" sein, und in diesem Sinne erscheint der gleiche Ausdruck viele Male in einer "in einer natürlichen Sprache geschriebenen Spezifikation", was aufgrund der Priorisierung der Klarheit unvermeidbar ist. Es versteht sich jedoch von selbst, dass das Gleichgewicht in allem wichtig ist.

Das Problem hier ist nun "Verifizierungsverarbeitung für eine große Datenmenge". Zum Beispiel gibt es so etwas

Wenn Sie es ehrlich umsetzen

Lassen Sie uns den Umzug des Blogs ins Visier nehmen. Wenn Sie ehrlich überprüfen, ob das Blog, in das Sie wechseln, mehr als ein Byte Inhalt im Tag "" enthält, werden Sie Folgendes sehen:

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

Es ist subtil, dies auf "def test_1000" zu schreiben.

Wiederholte Implementierung mit Iterator

Da sich die Überprüfungsbedingung (assert_compare) nicht ändert, kann ich mir einen Plan vorstellen, die URL-Liste zu einem Iterator (Array) zu machen und sie wiederholt zu testen. Dies wird jedoch aus dem später beschriebenen Grund nicht empfohlen.

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

% w Notation ist praktisch. Nun, es sieht auf den ersten Blick gut aus. Das Problem tritt jedoch tatsächlich auf, wenn die Bestätigung fehlschlägt. Unten ist das Ergebnis der Ausführung, aber ** Ich weiß nicht, welche URL fehlgeschlagen ist **.

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

Dies liegt daran, dass die Einheit des Tests jede Methode ist (def test_content_length im obigen Beispiel).

Es ist besser, Ruby dynamisch erstellen zu lassen, als Code zu generieren

Für jeden Iterator (Array) möchten wir für jeden Test eine Methode erstellen und darin testen. Gibt es keine andere Wahl, als es ehrlich durch Kopieren umzusetzen? Gibt es keine andere Wahl, als Code mit Hidemarus Makro zu generieren? Wenn Sie auf die Idee kommen, möchte ich Sie an die dynamische Codegenerierung erinnern, die die schwarze Magie (Metaprogrammierung) von Ruby ist.

Wie eingangs erwähnt, kann Ruby Code dynamisch generieren. Klasse und Methode sind keine Ausnahme. Sie können damit dynamisch nacheinander Testmethoden für den Inhalt des Iterators erstellen.

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

Dieser Code entspricht dem folgenden Code, der der einfachen Implementierung entspricht.

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

Im folgenden Beispiel wird der Ort des Fehlers als "Fehler: test_2 ()" angegeben.

$ 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 hat einen Ursprung von 0. Ein Fehler in test_2 bedeutet einen Fehler, wenn der Wert "ARR [2]" ist. Sie sollten diesen Wert daher überprüfen.

Es ist auch möglich, mit --name anzugeben

Wenn die Reihenfolge der im Iterator gespeicherten Werte garantiert ist, wird auch die Angabe mit "--name" unterstützt. Wenn Sie beispielsweise nur den Wert von "ARR [1]" testen möchten, geben Sie Folgendes an.

$ bundle exec ruby test_multiple.rb --name test_1

Implementierungspunkte

Der Schlüssel ist die dynamische Klassengenerierung durch "Class.new" und die dynamische Methodengenerierung durch "define_method". Wenn Sie daran interessiert sind, suchen Sie bitte nach "Ruby Black Magic".

Achten Sie darauf, es nicht zu übertreiben

Behauptung Dies gilt, wenn die Bedingungen einheitlich sind. Im Gegenteil, wenn sich die anzuwendende Behauptung in Abhängigkeit vom Eingabewert unterscheidet (= bedingte Verzweigung auftritt), ist es besser, sie ehrlich als DAMP zu implementieren.

Nachwort

Ich bin ein Amateur auf diesem Gebiet, daher weiß ich nicht, ob das zu mir passt.

EoT

Recommended Posts

Dynamisches Schreiben iterativer Testfälle mit test / unit (Test :: Unit)
JUnit 5: Wie man Testfälle in enum schreibt
So schreiben Sie einen Komponententest für Spring Boot 2
So führen Sie einen Komponententest für Spring AOP durch
Ich möchte einen Unit Test schreiben!
[SpringBoot] So schreiben Sie einen Controller-Test
So schreiben Sie Testcode mit Basic-Zertifizierung
Wie schreibe ich Rails
Wie schreibe ich Docker-Compose
Wie schreibe ich Mockito
So schreiben Sie eine Migrationsdatei
Wie man guten Code schreibt
Wie schreibe ich einen Java-Kommentar
[Refactoring] So schreiben Sie Routing
Einführung in Micronaut 2 ~ Unit Test ~
Unit-Test der Architektur mit ArchUnit
Wie schreibe ich Junit 5 organisiert
Wie schreibe ich Rails Seed
Wie schreibe ich Rails Routing
So autorisieren Sie mit graphql-ruby
Testen des Einschlusses von Bildern bei Verwendung von ActiveStorage und Faker
So schreiben Sie eine Abfrageoption bei Verwendung von gem ruby-firebase (Denkmal)
Java # 6 studieren (Wie man Blöcke schreibt)
[R Spec on Rails] So schreiben Sie Testcode für Anfänger von Anfängern
[Rails] Wie schreibe ich eine Ausnahmebehandlung?
So schreiben Sie eine Java-Variablendeklaration
So schreiben Sie leicht verständlichen Code [Zusammenfassung 3]
Ich habe getestet, wie man Rubys Test / Gerät und Janken's Code verwendet.
[RSpec] Unit Test (mit gem: factory_bot)
So erstellen Sie CloudStack mit Docker
So führen Sie einen Vertrag mit web3j aus
So sortieren Sie eine Liste mit Comparator
Führen Sie RSpec ein und schreiben Sie den Unit-Test-Code
[Rails] So laden Sie Bilder mit Carrierwave hoch
[Basic] So schreiben Sie ein Dockerfile Selbstlernend ②
Java Artery - Einfach zu verwendende Unit-Test-Bibliothek
[Java] So berechnen Sie das Alter mit LocalDate
Zusammenfassung zum Schreiben von Anmerkungsargumenten
[Einführung in Java] So schreiben Sie ein Java-Programm
Schreiben Sie Code, der schwer zu testen ist
So testen Sie den privaten Bereich mit JUnit
So implementieren Sie die Image-Veröffentlichung mithilfe von Schienen
So schreiben Sie den Spring AOP Point Cut Specifier
So fügen Sie Symbole mit Font awesome ein