Wie kann ich den manuellen Aufwand für das Umschließen von Bibliotheken von Drittanbietern mit einem größeren Objektmodell reduzieren?

16

Wie der Autor dieser Frage aus dem Jahr 2012 und diese aus dem Jahr 2013 habe ich eine Bibliothek von Drittanbietern, die ich einbinden muss, um meine Anwendung ordnungsgemäß zu testen. Die Top-Antwort lautet:

Sie möchten immer Typen und Methoden von Drittanbietern hinter einer Schnittstelle einschließen. Dies kann mühsam und schmerzhaft sein. Manchmal können Sie einen Codegenerator schreiben oder ein Tool verwenden, um dies zu tun.

In meinem Fall ist die Bibliothek für ein Objektmodell vorgesehen und verfügt daher über eine größere Anzahl von Klassen und Methoden, die umbrochen werden müssten, damit diese Strategie erfolgreich ist. Abgesehen davon, dass dies nur "langweilig und schmerzhaft" ist, wird dies eine harte Barriere für das Testen.

In den 4 Jahren seit dieser Frage bin ich mir bewusst, dass Isolations-Frameworks einen langen Weg zurückgelegt haben. Meine Frage ist: Gibt es jetzt einen einfacheren Weg, den Effekt der vollständigen Umhüllung von Bibliotheken von Drittanbietern zu erzielen? Wie kann ich diesen Prozess schmerzfrei gestalten und den manuellen Aufwand reduzieren?


Meine Frage ist kein Duplikat der Fragen, auf die ich ursprünglich verwiesen habe, da es bei meiner Frage darum geht, den manuellen Aufwand beim Umwickeln zu verringern. Diese anderen Fragen stellen sich nur, ob das Verpacken Sinn macht, nicht, wie der Aufwand gering gehalten werden kann.

Tom Wright
quelle
Welche Programmiersprache und welche Art von Bibliothek sprechen Sie?
Doc Brown
@DocBrown C # und eine PDF-Manipulationsbibliothek.
Tom Wright
2
Ich habe einen Post auf Meta gestartet , um Unterstützung für das erneute Öffnen Ihrer Frage zu finden.
Doc Brown
Danke @DocBrown - Ich hoffe, dass es da draußen einige interessante Perspektiven gibt.
Tom Wright
1
Wenn wir in den nächsten 48 Stunden keine besseren Antworten bekommen, werde ich ein Kopfgeld darauf setzen.
Doc Brown

Antworten:

4

Vorausgesetzt, Sie suchen nicht nach einem spöttischen Framework, da es allgegenwärtig und leicht zu finden ist , gibt es ein paar Dinge, die Sie im Vorfeld beachten sollten:

  1. Es gibt "nie" etwas, was Sie "immer" tun sollten.
    Es ist nicht immer das Beste, eine Bibliothek eines Drittanbieters einzuschließen. Wenn Ihre Anwendung von einer Bibliothek abhängig ist oder buchstäblich aus einer oder zwei Kernbibliotheken besteht, verschwenden Sie keine Zeit damit, sie zusammenzufassen. Wenn sich die Bibliotheken ändern, muss sich Ihre Anwendung trotzdem ändern .
  2. Es ist in Ordnung, Integrationstests zu verwenden.
    Dies gilt insbesondere für Grenzen, die stabil sind, für Ihre Anwendung von Bedeutung sind oder nicht einfach verspottet werden können. Wenn diese Bedingungen erfüllt sind, Wickel und spöttische werden kompliziert und langwierig werden. In diesem Fall würde ich beides vermeiden: nicht einwickeln und nicht verspotten; Schreiben Sie einfach Integrationstests. (Wenn automatisiertes Testen ein Ziel ist.)
  3. Tools und Frameworks können die logische Komplexität nicht beseitigen.
    Grundsätzlich kann ein Werkzeug nur die Kesselplatte entlasten. Es gibt jedoch keinen automatisierbaren Algorithmus, um eine komplexe Schnittstelle zu erstellen und zu vereinfachen - geschweige denn, um die Schnittstelle X zu verwenden und an Ihre Bedürfnisse anzupassen. (Nur Sie kennen diesen Algorithmus!) Obwohl es zweifellos Tools gibt, die Thin Wrapper generieren können , würde ich vorschlagen, dass sie nicht bereits allgegenwärtig sind, da Sie am Ende immer noch nur intelligent und daher manuell codieren müssen. gegen die Schnittstelle, auch wenn es hinter einem Wrapper versteckt ist.

Es gibt jedoch Taktiken, die Sie in vielen Sprachen anwenden können, um nicht direkt auf eine Klasse zu verweisen. In einigen Fällen können Sie eine Schnittstelle oder einen Thin Wrapper "vortäuschen", die bzw. der nicht existiert. In C # würde ich zum Beispiel eine von zwei Routen gehen:

  1. Verwenden Sie eine Factory und implizite Typisierung .

Mit dieser kleinen Kombination können Sie den Aufwand vermeiden, eine komplexe Klasse vollständig zu verpacken:

// "factory"
class PdfDocumentFactory {
  public static ExternalPDFLibraryDocument Build() {
    return new ExternalPDFLibraryDocument();
  }
}

// code that uses the factory.
class CoreBusinessEntity {
  public void DoImportantThings() {
    var doc = PdfDocumentFactory.Build();

    // ... i have no idea what your lib does, so, I'm making stuff but.
    // but, you can do whatever you want here without explicitly
    // referring to the library's actual types.
    doc.addHeader("Wee");
    doc.getAllText().makeBiggerBy(4).makeBold().makeItalic();
    return doc.exportBinaryStreamOrSomething();
  }
}

Wenn Sie vermeiden können, diese Objekte als Mitglieder zu speichern, entweder durch einen "funktionaleren" Ansatz oder durch Speichern in einem Wörterbuch (oder was auch immer ), bietet dieser Ansatz den Vorteil einer Typprüfung zur Kompilierungszeit, ohne dass Ihre Kerngeschäftseinheiten dies genau wissen müssen Mit welcher Klasse arbeiten sie?

Alles, was erforderlich ist, ist, dass die von Ihrer Factory zurückgegebene Klasse zur Kompilierungszeit tatsächlich die Methoden enthält, die Ihr Geschäftsobjekt verwendet.

  1. Verwenden Sie die dynamische Eingabe .

Dies entspricht der Verwendung der impliziten Typisierung , ist jedoch mit einem anderen Kompromiss verbunden: Sie verlieren Überprüfungen des Kompilierungstyps und erhalten die Möglichkeit, externe Abhängigkeiten anonym als Klassenmitglieder hinzuzufügen und Abhängigkeiten einzufügen .

class CoreBusinessEntity {
  dynamic Doc;

  public void InjectDoc(dynamic Doc) {
    Doc = doc;
  }

  public void DoImortantThings() {
    Doc.addHeader("Wee");
    Doc.getAllText().makeBiggerBy(4).makeBold().makeItalic();
    return Doc.exportBinaryStreamOrSomething();
  }
}

Bei beiden Taktiken müssen Sie, wenn es darum geht, sich zu verspotten ExternalPDFLibraryDocument, wie ich bereits sagte, noch einiges an Arbeit leisten - aber es ist Arbeit, die Sie sowieso erledigen müssen . Und mit dieser Konstruktion haben Sie es vermieden, mühsam Hunderte von dünnen kleinen Wrapper-Klassen zu definieren. Sie haben die Bibliothek einfach benutzt, ohne sie direkt anzusehen - zum größten Teil.

Nach alledem gibt es drei wichtige Gründe, warum ich immer noch erwägen würde, eine Bibliothek eines Drittanbieters explizit einzuschließen - keiner davon würde auf die Verwendung eines Tools oder Frameworks hinweisen:

  1. Die spezifische Bibliothek ist nicht intrinsische an die Anwendung.
  2. Es wäre sehr teuer zu tauschen, ohne es einzuwickeln.
  3. Ich mag die API selbst nicht.

Wenn ich in all diesen drei Bereichen keine Level-Bedenken habe, unternehmen Sie keine nennenswerten Anstrengungen, um das Ganze zusammenzufassen. Und wenn Sie in allen drei Bereichen Bedenken haben, hilft ein automatisch generierter dünner Wrapper nicht wirklich.

Wenn Sie sich entschieden haben, eine Bibliothek zusammenzufassen, besteht die effizienteste und effektivste Verwendung Ihrer Zeit darin , Ihre Anwendung anhand der von Ihnen gewünschten Schnittstelle zu erstellen . nicht gegen eine vorhandene API.

Anders ausgedrückt, beachten Sie den klassischen Rat: Verschieben Sie jede Entscheidung, die Sie treffen können. Erstellen Sie zuerst den "Kern" Ihrer Anwendung. Code gegen Schnittstellen, die irgendwann das tun, was Sie wollen, was irgendwann von "peripheren Dingen" erfüllt wird, die es noch nicht gibt. Überbrücken Sie die Lücken nach Bedarf.

Diese Anstrengung mag sich nicht zeitsparend anfühlen . Wenn Sie jedoch das Gefühl haben, einen Wrapper zu benötigen, ist dies der effizienteste Weg, dies sicher zu tun .

Denk darüber so.

Sie müssen Code für diese Bibliothek in einer dunklen Ecke Ihres Codes verwenden - auch wenn dieser abgeschlossen ist. Wenn Sie die Bibliothek während des Testens verspotten, ist ein manueller Aufwand unvermeidlich - auch wenn sie eingepackt ist. Dies bedeutet jedoch nicht, dass Sie diese Bibliothek im Großteil Ihrer Anwendung direkt mit ihrem Namen bestätigen müssen .

TLDR

Wenn es sich lohnt, die Bibliothek zu verpacken, verwenden Sie Taktiken , um weitverbreitete direkte Verweise auf die Bibliothek Ihres Drittanbieters zu vermeiden. Verwenden Sie jedoch keine Abkürzungen, um dünne Wrapper zu generieren. Erstellen Sie zuerst Ihre Geschäftslogik, achten Sie auf Ihre Schnittstellen und entwickeln Sie Ihre Adapter nach Bedarf organisch .

Und wenn es darum geht, haben Sie keine Angst vor Integrationstests. Sie sind etwas unübersichtlicher, bieten aber immer noch Beweise für den Arbeitscode und können immer noch leicht dazu gebracht werden, Regressionen in Schach zu halten.

Svidgen
quelle
2
Dies ist ein weiterer Beitrag, in dem die Frage nicht beantwortet wurde - explizit nicht "Wann soll verpackt werden?", Sondern "Wie kann der manuelle Aufwand verringert werden?".
Doc Brown
1
Entschuldigung, aber ich denke, Sie haben den Kern der Frage übersehen. Ich verstehe nicht, wie Ihre Vorschläge den manuellen Aufwand beim Verpacken verringern könnten . Angenommen, die betreffende Bibliothek hat ein komplexes Objektmodell als API mit einigen Dutzend Klassen und Hunderten von Methoden. Die API ist in Ordnung wie für den Standardgebrauch, aber wie kann man sie für Unit-Tests mit weniger Aufwand verpacken?
Doc Brown
1
TLDR; Wenn Sie die Bonuspunkte möchten, sagen Sie uns etwas, was wir noch nicht wissen ;-)
Doc Brown
1
@ DocBrown Ich habe den Punkt nicht verpasst. Aber ich denke, Sie haben den Punkt meiner Antwort verpasst. Mit dem richtigen Aufwand sparen Sie viel Arbeit. Es gibt Auswirkungen auf das Testen - aber das ist nur ein Nebeneffekt. Wenn Sie ein Tool zum automatischen Generieren eines dünnen Wrappers um eine Bibliothek verwenden, müssen Sie weiterhin Ihre Kernbibliothek um die API einer anderen Person herum erstellen und Mocks erstellen. Dies ist eine Menge Aufwand, der nicht vermieden werden kann, wenn Sie darauf bestehen, die Bibliothek nicht zu verwenden Ihrer Tests.
Svidgen
1
@DocBrown Das ist absurd. "Hat diese Klasse von Problemen eine Klasse von Werkzeugen oder Ansätzen?" Ja. Natürlich tut es das. Defensive Codierung und nicht dogmatisch über Unit-Tests ... wie meine Antwort sagt. Wenn Sie haben eine automagically erzeugte dünne Hülle haben, welchen Wert würde es schaffen !? ... Es würde Ihnen nicht erlauben, Abhängigkeiten zum Testen zu injizieren, Sie müssen dies immer noch manuell tun. Und es würde Ihnen nicht erlauben, die Bibliothek auszutauschen, weil Sie immer noch gegen die API der Bibliothek codieren ... es ist jetzt nur indirekt.
Svidgen
9

Testen Sie diesen Code nicht. Schreiben Sie stattdessen Integrationstests. In einigen Fällen Unit - Tests, verspotten, ist mühsam und schmerzhaft. Machen Sie Schluss mit Unit-Tests und schreiben Sie Integrationstests, die tatsächlich zu Lieferantenanfragen führen.

Beachten Sie, dass diese Tests nach der Bereitstellung als automatisierte Aktivität nach der Bereitstellung ausgeführt werden sollten. Sie werden nicht als Teil von Komponententests oder als Teil des Erstellungsprozesses ausgeführt.

Manchmal ist ein Integrationstest in bestimmten Teilen Ihrer Anwendung besser als ein Komponententest. Die Reifen, die man durchlaufen muss, um den Code "testbar" zu machen, können manchmal nachteilig sein.

Jon Raynor
quelle
2
Das erste, was ich dachte, als ich Ihre Antwort las, war "unrealistisch für PDF, da ein Vergleich der Dateien auf Binär-Ebene nicht anzeigt, was sich geändert hat oder ob die Änderung problematisch ist". Dann habe ich diesen älteren SO-Beitrag gefunden , der anzeigt, dass er tatsächlich funktionieren könnte (also +1).
Doc Brown
@DocBrown vergleicht das Tool im SO-Link nicht die PDFs in einer Binärebene. Es vergleicht die Struktur und den Inhalt der Seiten. Interne Struktur des PDF-
Dokuments
1
@linuxunil: ja, ich weiß, wie ich schrieb, zuerst dachte ich ... aber dann fand ich die oben erwähnte Lösung.
Doc Brown
oh ... ich verstehe ... vielleicht hat mich das "es könnte tatsächlich funktionieren" im Finale deiner Antwort verwirrt.
Linuxunil
1

Soweit ich weiß, konzentriert sich diese Diskussion auf Möglichkeiten für die Automatisierung der Wrappererstellung, anstatt die Idee und die Implementierungsrichtlinie zu verpacken. Ich werde versuchen, die Idee zu abstrahieren, da es hier bereits viel zu tun gibt.

Ich sehe, dass wir mit .NET-Technologien spielen, daher haben wir leistungsstarke Reflexionsfähigkeiten in unseren Händen. Sie können in Betracht ziehen:

  1. Tool wie .NET Wrapper Class Generator . Ich habe dieses Tool nicht verwendet und weiß, dass es auf einem älteren Technologie-Stack ausgeführt wird, aber für Ihren Fall würde es vielleicht passen. Natürlich sollten Codequalität, Unterstützung für Abhängigkeitsinversion und Schnittstellensegregation separat untersucht werden. Vielleicht gibt es andere Tools wie dieses, aber ich habe nicht viel gesucht.
  2. Schreiben Sie Ihr eigenes Tool, das in der referenzierten Assembly nach öffentlichen Methoden / Schnittstellen sucht und das Mapping und die Codegenerierung durchführt. Ein Beitrag zur Community wäre mehr als willkommen!
  3. Wenn .NET kein Fall ist ... schauen Sie vielleicht hier .

Ich bin der Meinung, dass eine solche Automatisierung einen Ausgangspunkt darstellt, aber keine endgültige Lösung. Manuelles Refactoring des Codes ist erforderlich ... oder im schlimmsten Fall ein Redesign, da ich dem, was svidgen geschrieben hat, vollkommen zustimme:

Erstellen Sie Ihre Anwendung anhand der gewünschten Schnittstelle. nicht gegen eine Drittanbieter-API.

tom3k
quelle
Ok, zumindest eine Idee, wie das Problem behandelt werden könnte . Aber nicht basierend auf eigenen Erfahrungen für diesen Anwendungsfall, nehme ich an?
Doc Brown
Die Frage basiert auf meinen eigenen Erfahrungen im Bereich Software Engineering (Wrapper, Reflection, Di)! In Bezug auf die Werkzeuge - ich habe das nicht verwendet, wie im Asnwer angegeben.
tom3k
0

Befolgen Sie diese Richtlinien, wenn Sie Wrapper-Bibliotheken erstellen:

  • Stellen Sie nur eine kleine Teilmenge der Drittanbieter-Bibliothek zur Verfügung, die Sie gerade benötigen (und erweitern Sie sie bei Bedarf).
  • Halten Sie die Hülle so dünn wie möglich (keine Logik gehört dorthin).
Pansen Georgiev
quelle
1
Ich frage mich nur, warum Sie 3 positive Stimmen für einen Beitrag erhalten haben, der den Punkt der Frage verfehlt.
Doc Brown