Ist es in Ordnung, Methoden einzuführen, die nur bei Unit-Tests verwendet werden?

12

Vor kurzem habe ich eine Factory-Methode getestet. Die Methode bestand darin, entweder ein einfaches Objekt oder ein Objekt in einem Dekorateur zu erstellen. Das dekorierte Objekt kann von einer von mehreren Arten sein, die alle StrategyClass erweitern.

In meinem Test wollte ich überprüfen, ob die Klasse des zurückgegebenen Objekts den Erwartungen entspricht. Das ist einfach, wenn normale Objekte zurückgegeben werden, aber was ist zu tun, wenn sie in einem Dekorateur verpackt sind?

Ich codiere in PHP, damit ich ext/Reflectioneine Klasse von umschlossenen Objekten finden kann, aber es schien mir, dass es überkomplizierte Dinge sind und den TDD-Regeln in gewisser Weise widersprechen.

Stattdessen habe ich beschlossen, ein getClassName()Objekt einzuführen , das den Klassennamen des Objekts zurückgibt, wenn es von StrategyClass aufgerufen wird. Wenn es jedoch vom Dekorator aufgerufen wird, gibt es den Wert zurück, der von derselben Methode in dem dekorierten Objekt zurückgegeben wird.

Code zur Verdeutlichung:

interface StrategyInterface {
  public function getClassName();
}

abstract class StrategyClass implements StrategyInterface {
  public function getClassName() {
    return \get_class($this);
  }
}

abstract class StrategyDecorator implements StrategyInterface {

  private $decorated;

  public function __construct(StrategyClass $decorated) {
    $this->decorated = $decorated;
  }

  public function getClassName() {
    return $this->decorated->getClassName();
  }

}

Und ein PHPUnit-Test

 /**
   * @dataProvider providerForTestGetStrategy
   * @param array $arguments
   * @param string $expected
   */
  public function testGetStrategy($arguments, $expected) {

    $this->assertEquals(
      __NAMESPACE__.'\\'.$expected,  
      $this->object->getStrategy($arguments)->getClassName()
    )
  }

//below there's another test to check if proper decorator is being used

Mein Punkt hier ist: Ist es in Ordnung, solche Methoden einzuführen, die keinen anderen Nutzen haben, als Unit-Tests einfacher zu machen? Irgendwie fühlt es sich für mich nicht richtig an.

Mchl
quelle
Möglicherweise wird diese Frage mehr Aufschluss über Ihre Frage geben, programmers.stackexchange.com/questions/231237/… . Ich glaube, dies hängt von der Verwendung ab und davon, ob die Methoden beim Testen und Debuggen von Anwendungen, die Sie entwickeln, sehr hilfreich sind. .
Clement Mark-Aaba

Antworten:

13

Mein Gedanke ist nein, Sie sollten nichts tun, nur weil es für die Testbarkeit benötigt wird. Viele Entscheidungen, die von Menschen getroffen werden, wirken sich positiv auf die Testbarkeit aus, und die Testbarkeit kann sogar der Hauptvorteil sein, sollte jedoch eine gute Entwurfsentscheidung in Bezug auf andere Aspekte sein. Dies bedeutet, dass einige gewünschte Eigenschaften nicht testbar sind. Ein anderes Beispiel ist, wenn Sie wissen möchten, wie effizient eine Routine ist, zum Beispiel, wenn Ihre Hashmap einen gleichmäßig verteilten Bereich von Hash-Werten verwendet - nichts in der externen Schnittstelle kann Ihnen das sagen.

Anstatt zu überlegen, ob ich die richtige Strategieklasse erhalte, überlege ich, ob die Klasse, die ich erhalte, das leistet, was diese Spezifikation zu testen versucht. Es ist schön, wenn Sie die internen Wasserleitungen testen können, aber nicht müssen. Drehen Sie einfach den Knopf und sehen Sie, ob Sie heißes oder kaltes Wasser bekommen.

Jeremy
quelle
+1 OP beschreibt "Clear Box" -Einheitentests, keine TDD-Funktionstests
Steven A. Lowe
1
Ich sehe den nächsten Punkt, obwohl ich es ein bisschen ablehne, StrategyClass-Algorithmen zu testen, wenn ich testen möchte, ob die Factory-Methode ihren Job macht. Diese Art der Trennung bricht IMHO. Ein weiterer Grund, warum ich dies vermeiden möchte, besteht darin, dass diese bestimmten Klassen in Datenbanken ausgeführt werden. Das Testen dieser Klassen erfordert daher zusätzliches Mocking / Stubbing.
Mchl
Andererseits und im Lichte dieser Frage: programmers.stackexchange.com/questions/86656/…, wenn wir "TDD-Tests" von "Unit-Tests" unterscheiden, wird dies vollkommen in Ordnung (obwohl immer noch die Datenbank-Installation: P)
Mchl
Wenn Sie die Methoden hinzufügen, werden sie Teil des Vertrags mit Ihren Benutzern. Sie werden mit Programmierern enden, die Ihre Nur-Test-Funktionen aufrufen und basierend auf den Ergebnissen verzweigen. Im Allgemeinen bevorzuge ich es, so wenig wie möglich von der Klasse zu belichten.
BillThor
5

Ich gehe davon aus, dass Sie manchmal Ihren Quellcode ein wenig überarbeiten müssen, um ihn testbarer zu machen. Es ist nicht ideal und sollte keine Entschuldigung dafür sein, die Benutzeroberfläche mit Funktionen zu überfrachten, die nur zum Testen verwendet werden sollen. Daher ist Moderation hier im Allgemeinen der Schlüssel. Sie möchten auch nicht in der Situation sein, dass die Benutzer Ihres Codes plötzlich die Testschnittstellenfunktionen für normale Interaktionen mit Ihrem Objekt verwenden.

Mein bevorzugter Weg, um damit umzugehen (und ich muss mich entschuldigen, dass ich nicht zeigen kann, wie das in PHP gemacht wird, da ich hauptsächlich in C-Stil-Sprachen programmiere), ist, die 'Test'-Funktionen auf eine Weise bereitzustellen, die sie nicht sind der Außenwelt durch das Objekt selbst ausgesetzt, kann aber von abgeleiteten Objekten zugegriffen werden. Zu Testzwecken würde ich dann eine Klasse ableiten, die die Interaktion mit dem Objekt handhabt, das ich tatsächlich testen möchte, und den Komponententest veranlassen, dieses bestimmte Objekt zu verwenden. Ein C ++ - Beispiel würde ungefähr so ​​aussehen:

Produktionsartenklasse:

class ProdObj
{
  ...
  protected:
     virtual bool checkCertainState();
};

Testhelferklasse:

class TestHelperProdObj : public ProdObj
{
  ...
  public:
     virtual bool checkCertainState() { return ProdObj::checkCertainState(); }
};

Auf diese Weise sind Sie zumindest in der Lage, die Funktionen des Typs "Test" in Ihrem Hauptobjekt nicht offen zu legen.

Timo Geusch
quelle
Ein interessanter Ansatz. Ich müsste sehen, wie ich das anpassen könnte
Mchl
3

Als ich vor ein paar Monaten meinen neu gekauften Geschirrspüler aufstellte, kam eine Menge Wasser aus dem Schlauch, und mir wurde klar, dass dies wahrscheinlich darauf zurückzuführen ist, dass er in der Fabrik, aus der er kam, ordnungsgemäß getestet wurde. Es ist nicht ungewöhnlich, Befestigungslöcher und ähnliches an Maschinen zu sehen, die nur zum Testen in einer Montagelinie vorhanden sind.

Testen ist wichtig, wenn nötig, fügen Sie einfach etwas hinzu.

Aber probieren Sie einige der Alternativen aus. Ihre reflexionsbasierte Option ist gar nicht so schlecht. Sie könnten einen geschützten virtuellen Zugriffsmechanismus für das haben, was Sie benötigen, und eine abgeleitete Klasse zum Testen und Bestätigen erstellen. Vielleicht können Sie Ihre Klasse aufteilen und eine resultierende Blattklasse direkt testen. Das Ausblenden der Testmethode mit einer Compiler-Variablen in Ihrem Quellcode ist ebenfalls eine Option (ich kenne PHP kaum, nicht sicher, ob das in PHP möglich ist).

In Ihrem Kontext können Sie entscheiden, nicht die richtige Zusammensetzung innerhalb des Dekorateurs zu testen, sondern das erwartete Verhalten der Dekoration zu testen. Dies würde den Fokus vielleicht etwas mehr auf das erwartete Systemverhalten richten und weniger auf die technischen Spezifikationen (was bringt Ihnen das Dekorationsmuster aus funktionaler Sicht?).

Joppe
quelle
1

Ich bin ein absoluter TDD-Neuling, aber es scheint von der hinzugefügten Methode abzuhängen. Soweit ich TDD verstehe, sollen Ihre Tests die Erstellung Ihrer API bis zu einem gewissen Grad vorantreiben.

Wenn es in Ordnung ist:

  • Solange die Methode die Kapselung nicht unterbricht und einem Zweck dient, der in Einklang mit der Verantwortung des Objekts steht.

Wenn es nicht in Ordnung ist:

  • Wenn die Methode als etwas erscheint, das niemals nützlich wäre oder in Bezug auf andere Schnittstellenmethoden keinen Sinn ergibt, kann sie inkohärent oder verwirrend sein. An diesem Punkt würde es mein Verständnis dieses Objekts trüben.
  • Jeremys Beispiel: "... wenn Sie wissen möchten, wie effizient eine Routine ist, verwendet Ihre Hashmap zum Beispiel einen gleichmäßig verteilten Bereich von Hashwerten - nichts in der externen Schnittstelle kann Ihnen das sagen."
Welpe
quelle