Ist es eine schlechte Praxis, wenn Unit-Tests voneinander abhängig sind?

9

Nehmen wir an, ich habe eine Art Unit-Test wie diesen:

let myApi = new Api();

describe('api', () => {

  describe('set()', () => {
    it('should return true when setting a value', () => {
      assert.equal(myApi.set('foo', 'bar'), true);
    });
  });

  describe('get()', () => {
    it('should return the value when getting the value', () => {
      assert.equal(myApi.get('foo'), 'bar');
    });
  });

});

So, jetzt habe ich 2 Unit-Tests. Man setzt einen Wert in einer API. Der andere testet, um sicherzustellen, dass der richtige Wert zurückgegeben wird. Der 2. Test ist jedoch vom ersten abhängig. Sollte ich im .set()2. Test vor dem einen Test hinzufügen get(), um sicherzustellen, dass der 2. Test von nichts anderem abhängig ist?

Sollte ich in diesem Beispiel auch myApifür jeden Test instanziieren , anstatt ihn vor den Tests einmal auszuführen?

Jake Wilson
quelle

Antworten:

15

Ja, es ist eine schlechte Praxis. Unit-Tests müssen unabhängig voneinander ausgeführt werden, aus den gleichen Gründen, aus denen Sie eine andere Funktion benötigen, um unabhängig voneinander ausgeführt zu werden: Sie können sie als unabhängige Unit behandeln.

Sollte ich im 2. Test vor dem get () eine .set () -Methode hinzufügen, um sicherzustellen, dass der 2. Test von nichts anderem abhängig ist?

Ja. Wenn es sich jedoch nur um Bare-Getter- und Setter-Methoden handelt, enthalten sie kein Verhalten, und Sie sollten sie wirklich nicht testen müssen, es sei denn, Sie haben den Ruf, Dinge so zu fetten, dass der Getter / Setter Kompiliert, setzt oder erhält das falsche Feld.

Robert Harvey
quelle
In meinem Beispiel myApiist beispielsweise ein instanziiertes Objekt. Sollte ich myApibei jedem Komponententest erneut angeben ? Oder kann die Wiederverwendung zwischen den Tests dazu führen, dass der Test falsch positive Ergebnisse liefert usw. Und ja, mein Beispiel ist eine vereinfachte Getter / Setter-Sache, aber in Wirklichkeit wäre es offensichtlich viel komplizierter.
Jake Wilson
1
@ JakeWilson Es gibt ein Buch namens Pragmatic Unit Testing, das die Grundlagen des Unit Testing behandelt, wie man vermeidet, dass Tests sich gegenseitig stören usw.
rwong
Ein weiteres Beispiel: Wenn Sie JUnit verwenden, wird die Reihenfolge Ihrer Funktion nicht durch die Reihenfolge definiert, in der Sie sie in die Klasse geschrieben haben. @JakeWilson Wenn Ihre API zustandslos ist, können Sie sie wiederverwenden, wenn Sie sie nicht erneut installieren.
Walfrat
2

Versuchen Sie, für jeden Test der Arrangement-Act-Assert-Struktur zu folgen.

  1. Ordnen Sie Ihre Objekte usw. an und versetzen Sie sie in einen bekannten Zustand (eine Testvorrichtung). Manchmal enthält diese Phase Behauptungen, die zeigen, dass Sie sich tatsächlich in dem Zustand befinden, in dem Sie sich zu befinden glauben.
  2. Handeln Sie also: Führen Sie das Verhalten aus, das Sie testen.
  3. Stellen Sie sicher, dass Sie das erwartete Ergebnis erhalten haben.

Ihre Tests machen sich nicht die Mühe, zuerst einen bekannten Status zu erstellen, daher sind sie für sich genommen bedeutungslos.

Außerdem testen Unit-Tests nicht unbedingt nur eine einzige Methode - Unit-Tests sollten eine Unit testen. Normalerweise ist diese Einheit eine Klasse. Einige Methoden mögen get()nur in Kombination mit anderen Sinn machen.

Das Testen von Gettern und Setzern ist sinnvoll, insbesondere in dynamischen Sprachen (nur um sicherzustellen, dass sie tatsächlich vorhanden sind). Um einen Getter zu testen, müssen wir zuerst einen bekannten Wert angeben. Dies kann durch den Konstruktor oder durch einen Setter geschehen. Oder anders gesehen: Das Testen des Getters ist implizit in Tests des Setters und des Konstruktors enthalten. Und der Setter, kehrt er immer zurück trueoder nur, wenn der Wert geändert wurde? Dies kann zu folgenden Tests führen (Pseudocode):

describe Api:

  it has a default value:
    // arrange
    api = new Api()
    // act & assert
    assert api.get() === expected default value

  it can take custom values:
    // arrange & act
    api = new Api(42)
    // act & assert
    assert api.get() === 42

  describe set:

    it can set new values:
      // arrange
      api = new Api(7)
      // act
      ok = api.set(13)
      // assert
      assert ok === true:
      assert api.get() === 13

    it returns false when value is unchanged:
      // arrange
      api = new Api(57)
      // act
      ok = api.set(57)
      // assert
      assert ok === false
      assert api.get() === 57

Die Wiederverwendung des Zustands aus einem früheren Test würde unsere Tests ziemlich zerbrechlich machen. Das Neuanordnen der Tests oder das Ändern des genauen Werts in einem Test kann dazu führen, dass anscheinend nicht verwandte Tests fehlschlagen. Die Annahme eines bestimmten Status kann auch Fehler verbergen, wenn Tests bestanden werden, die tatsächlich fehlschlagen sollten. Um dies zu verhindern, haben einige Testläufer die Möglichkeit, die Testfälle in zufälliger Reihenfolge auszuführen.

Es gibt jedoch Fälle, in denen wir den vom vorherigen Test bereitgestellten Status wiederverwenden. Insbesondere wenn das Erstellen eines Testgeräts viel Zeit in Anspruch nimmt, kombinieren wir viele Testfälle zu einer Testsuite. Obwohl diese Tests anfälliger sind, sind sie jetzt möglicherweise noch wertvoller, da sie schneller und häufiger durchgeführt werden können. In der Praxis ist das Kombinieren von Tests immer dann wünschenswert, wenn die Tests eine manuelle Komponente umfassen, wenn eine große Datenbank benötigt wird oder wenn Zustandsmaschinen getestet werden.

amon
quelle