Come posso ridurre lo sforzo manuale per avvolgere librerie di terze parti con un modello a oggetti più grande?


16

Come l'autore di questa domanda del 2012 e questa del 2013 , ho una libreria di terze parti che devo avvolgere per testare correttamente la mia domanda. La risposta principale afferma:

Volete sempre avvolgere tipi e metodi di terze parti dietro un'interfaccia. Questo può essere noioso e doloroso. A volte puoi scrivere un generatore di codice o usare uno strumento per farlo.

Nel mio caso, la libreria è per un modello a oggetti e di conseguenza ha un numero maggiore di classi e metodi che dovrebbero essere racchiusi affinché questa strategia abbia successo. Al di là del semplice "noioso e doloroso", questo diventa un duro ostacolo ai test.

Nei 4 anni successivi a questa domanda, sono consapevole che i sistemi di isolamento hanno fatto molta strada. La mia domanda è: esiste ora un modo più semplice per ottenere l'effetto del wrapping completo delle librerie di terze parti? Come posso eliminare il dolore da questo processo e ridurre lo sforzo manuale?


La mia domanda non è un duplicato delle domande a cui inizialmente mi ero legata, poiché la mia riguarda la riduzione dello sforzo manuale di confezionamento. Quelle altre domande chiedono solo se il confezionamento ha un senso, non come lo sforzo può essere ridotto.


Di quale linguaggio di programmazione e di che tipo di biblioteca stai parlando?
Doc Brown,

@DocBrown C # e una libreria di manipolazione PDF.
Tom Wright,

2
Ho iniziato un post su meta per trovare supporto per riaprire la tua domanda.
Doc Brown,

Grazie @DocBrown - Spero che ci siano alcune prospettive interessanti là fuori.
Tom Wright,

1
Quando non avremo risposte migliori nelle prossime 48 ore, darò una taglia su questo.
Doc Brown,

Risposte:


4

Supponendo che non stai cercando un quadro beffardo, perché sono ultra-ubiquitari e facili da trovare , ci sono alcune cose che vale la pena notare in anticipo:

  1. Non c'è "mai" qualcosa che dovresti "sempre" fare.
    Non è sempre meglio avvolgere una libreria di terze parti. Se l'applicazione è intrinsecamente dipendente da una libreria o se è letteralmente costruita attorno a una o due librerie principali, non perdete tempo a impacchettarla. Se le librerie cambiano, l'applicazione dovrà comunque cambiare .
  2. È corretto utilizzare i test di integrazione.
    Ciò è particolarmente vero attorno a limiti stabili, intrinseci alla propria applicazione o che non possono essere facilmente derisi. Se tali condizioni sono soddisfatte, il confezionamento e la derisione saranno complicati e noiosi. In tal caso, eviterei entrambi: non avvolgere e non deridere; basta scrivere test di integrazione. (Se il test automatico è un obiettivo.)
  3. Strumenti e framework non possono eliminare la complessità logica.
    In linea di principio, uno strumento può essere ridotto solo sulla piastra della caldaia. Ma non esiste un algoritmo automatizzabile per prendere un'interfaccia complessa e renderla semplice - figuriamoci prendere l'interfaccia X e adattarla alle proprie esigenze. (Solo tu sai quell'algoritmo !) Quindi, mentre ci sono indubbiamente strumenti che possono generare wrapper sottili, suggerirei che non sono già onnipresenti perché, alla fine, devi ancora solo programmare in modo intelligente, e quindi manualmente, contro l'interfaccia anche se è nascosto dietro un wrapper.

Detto questo, ci sono tattiche che puoi usare in molte lingue per evitare di fare riferimento direttamente a una classe. E in alcuni casi, è possibile "fingere" un'interfaccia o un wrapper sottile che in realtà non esiste. In C #, ad esempio, vorrei seguire una delle due rotte:

  1. Usa una fabbrica e digitazione implicita .

Puoi evitare lo sforzo di avvolgere completamente una classe complessa con questa piccola combinazione:

// "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();
  }
}

Se puoi evitare di archiviare questi oggetti come membri, sia attraverso un approccio più "funzionale" sia memorizzandoli in un dizionario (o qualsiasi altra cosa ), questo approccio ha il vantaggio di verificare il tipo in fase di compilazione senza che le tue entità aziendali principali debbano sapere esattamente con quale classe stanno lavorando.

Tutto ciò che serve è che, al momento della compilazione, la classe restituita dalla propria fabbrica abbia effettivamente i metodi su cui sta utilizzando l'oggetto business.

  1. Usa la digitazione dinamica .

Questo è nella stessa maniera dell'uso della tipizzazione implicita , ma comporta un altro compromesso: perdi i controlli di tipo compilazione e ottieni la possibilità di aggiungere anonimamente dipendenze esterne come membri della classe e iniettare le tue dipendenze.

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();
  }
}

Con entrambe queste tattiche, quando arriva il momento di deridere ExternalPDFLibraryDocument, come ho detto prima, hai del lavoro da fare - ma, è un lavoro che dovresti comunque fare . E, con questa costruzione, hai evitato noiosamente la definizione di centinaia di classi di involucri sottili. Hai semplicemente usato la libreria senza guardarla direttamente - per la maggior parte.

Detto questo, ci sono tre grandi ragioni per cui prenderei ancora in considerazione l'idea di racchiudere esplicitamente una libreria di terze parti, nessuna delle quali suggerirebbe di usare uno strumento o un framework:

  1. La libreria specifica non è intrinseca all'applicazione.
  2. Sarebbe molto costoso scambiare senza avvolgerlo.
  3. Non mi piace l'API stessa.

Se non ho qualche preoccupazione di livello in tutte e tre queste aree, non fai alcuno sforzo significativo per concludere. E, se hai qualche preoccupazione in tutte e tre le aree, un wrapper sottile generato automaticamente non sarà davvero d'aiuto.

Se hai deciso di concludere una libreria, l'uso più efficiente ed efficace del tuo tempo è costruire la tua applicazione sull'interfaccia che desideri ; non contro un'API esistente.

Detto in altro modo, segui i consigli classici: rinvia ogni decisione che puoi. Crea prima il "core" della tua applicazione. Codice contro interfacce che alla fine faranno quello che vuoi, che lo farà fine saranno soddisfatte da "materiale periferico" che non esiste ancora. Colmare le lacune secondo necessità.

Questo sforzo non può sentirsi come il tempo salvato; ma se ritieni di aver bisogno di un wrapper, questo è il modo più efficace per farlo in sicurezza.

Pensare in questo modo.

Devi programmare contro questa libreria in qualche angolo buio del tuo codice, anche se è chiuso. Se prendi in giro la libreria durante i test, c'è inevitabile sforzo manuale lì, anche se è finito. Ma ciò non significa che devi riconoscere direttamente quella libreria per nome in gran parte della tua applicazione.

TLDR

Se vale la pena avvolgere la biblioteca, usa le tattiche per evitare riferimenti diretti e diffusi alla tua biblioteca di terze parti, ma non prendere scorciatoie per generare involucri sottili. Costruisci prima la tua logica aziendale, sii deliberato sulle tue interfacce ed emergi organicamente i tuoi adattatori, se necessario.

E, se si tratta di esso, non abbiate paura dei test di integrazione. Sono un po 'più sfocati, ma offrono ancora prove del codice funzionante e possono ancora essere facilmente fatti per tenere a bada le regressioni.


2
Questo è un altro post non rispondendo alla domanda - che era esplicitamente non "quando per avvolgere", ma "come ridurre lo sforzo manuale".
Doc Brown,

1
Scusa, ma penso che tu abbia perso il punto centrale della domanda. Non vedo come i tuoi suggerimenti potrebbero ridurre qualsiasi sforzo manuale nel confezionamento. Supponiamo che la lib in gioco abbia un modello a oggetti complesso come API, con alcune decine di classi e centinaia di metodi. La sua API va bene com'è per l'uso standard, ma come avvolgerla per i test unitari con meno dolore / sforzo?
Doc Brown,

1
TLDR; se vuoi i punti bonus, dicci qualcosa che non sappiamo già ;-)
Doc Brown il

1
@DocBrown Non ho perso il punto. Ma penso che tu abbia perso il punto della mia risposta. Investire lo sforzo giusto ti fa risparmiare molto lavoro. Ci sono implicazioni per i test, ma questo è solo un effetto collaterale. L'uso di uno strumento per generare automagicamente un thin wrapper attorno a una libreria ti lascia comunque costruire la tua libreria principale attorno all'API di qualcun altro e creare beffe, il che è un grande sforzo che non può essere evitato se insisti a lasciare la libreria fuori dei tuoi test.
svidgen,

1
@DocBrown Questo è assurdo. "Questa classe di problemi ha una classe di strumenti o approcci?" Sì. Certo che lo fa. Codifica difensiva e non ottenere dogmatici sui test unitari ... come dice la mia risposta. Se si ha avere un involucro sottile automagically generato, che valore sarebbe fornire !? ... Non ti permetterebbe di iniettare dipendenze per i test, devi comunque farlo manualmente. E non ti permetterebbe di scambiare la libreria, perché stai ancora codificando contro l'API della libreria ... è solo indiretto ora.
svidgen,

9

Non testare l'unità quel codice. Scrivi invece test di integrazione. In alcuni casi, test unitari, beffardo, è noioso e doloroso. Abbandona i test unitari e scrivi i test di integrazione che rendono effettive le chiamate dei fornitori.

Si noti che questi test devono essere eseguiti dopo la distribuzione come attività automatizzata post-distribuzione. Non vengono eseguiti come parte di unit test o come parte del processo di compilazione.

A volte un test di integrazione si adatta meglio di un test unitario in alcune parti dell'applicazione. I cerchi che uno deve attraversare per rendere il codice "testabile" a volte può essere dannoso.


2
La prima cosa che ho pensato quando ho letto la tua risposta è stata "irrealistica per il PDF, poiché il confronto dei file a livello binario non ti dice cosa è cambiato o se la modifica è problematica". Poi ho trovato questo post SO più vecchio , indica che potrebbe effettivamente funzionare (quindi +1).
Doc Brown,

@DocBrown lo strumento nel collegamento SO non confronta i PDF a livello binario. Confronta la struttura e il contenuto delle pagine. Struttura interna in pdf: safaribooksonline.com/library/view/pdf-explained/9781449321581/…
linuxunil

1
@linuxunil: sì, lo so, come ho scritto, prima ho pensato ... ma poi ho trovato la soluzione sopra menzionata.
Doc Brown,

oh .. capisco .. forse il 'potrebbe effettivamente funzionare' nel finale della tua risposta mi ha confuso.
linuxunil,

1

A quanto ho capito, questa discussione si concentra sulle opportunità per l'automazione della creazione di wrapper piuttosto che di idee e linee guida di implementazione. Proverò a sottrarmi all'idea poiché qui c'è già molto.

Vedo che stiamo giocando con le tecnologie .NET, quindi abbiamo potenti capacità di riflessione nelle nostre mani. Puoi considerare:

  1. Strumento come .NET Wrapper Class Generator . Non ho usato questo strumento e so che funziona su una pila di tecnologia legacy, ma forse per il tuo caso si adatterebbe. Naturalmente, la qualità del codice, il supporto per l'inversione delle dipendenze e la segregazione delle interfacce dovrebbero essere studiati separatamente. Forse ci sono altri strumenti del genere, ma non ho cercato molto.
  2. Scrivere il proprio strumento che cercherà nell'assieme di riferimento metodi / interfacce pubblici e eseguirà la mappatura e la generazione del codice. Il contributo alla comunità sarebbe più che benvenuto!
  3. Se .NET non è un caso ... forse guarda qui .

Credo che tale automazione possa costituire un punto di base, ma non una soluzione finale. Sarà necessario il refactoring manuale del codice ... o, nel peggiore dei casi, una riprogettazione poiché sono completamente d'accordo con ciò che svidgen ha scritto:

Costruisci la tua applicazione sull'interfaccia che desideri; non contro un'API di terze parti.


Ok, almeno un'idea di come il problema potrebbe essere gestito. Ma non basato sulla propria esperienza per questo caso d'uso, suppongo?
Doc Brown,

La domanda si basa sulla mia esperienza personale nel campo dell'ingegneria del software (wrapper, reflection, di)! Per quanto riguarda gli strumenti, non l'ho usato come indicato nella domanda.
tom3k,

0

Seguire queste linee guida durante la creazione delle librerie wrapper:

  • esporre solo un piccolo sottoinsieme della libreria di terze parti di cui hai bisogno in questo momento (ed espandere su richiesta)
  • mantenere il wrapper il più sottile possibile (nessuna logica appartiene lì).

1
Mi chiedo solo perché hai ottenuto 3 voti per un post che non rispecchia il punto della domanda.
Doc Brown,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.