Scrittura di codice verificabile evitando la generalità speculativa

11

stamattina stavo leggendo alcuni post sul blog e sono incappato in questo :

If the only class that ever implements the Customer interface is CustomerImpl, you don't really have polymorphism and substitutability because there is nothing in practice to substitute at runtime. It's fake generality.

Questo ha senso per me, poiché l'implementazione di un'interfaccia aggiunge complessità e, se esiste una sola implementazione, si potrebbe obiettare che aggiunge complessità inutile. Scrivere un codice che è più astratto di quanto dovrebbe essere è spesso considerato come un odore di codice chiamato "generalità speculativa" (citato anche nel post).

Ma, se seguo TDD, non posso (facilmente) creare duplicati di test senza quella generalità speculativa, sia sotto forma di implementazione dell'interfaccia o altra nostra opzione polimorfica, rendendo la classe ereditabile e i suoi metodi virtuali.

Quindi, come possiamo conciliare questo compromesso? Vale la pena che sia speculativamente generale per facilitare i test / TDD? Se stai usando i duplicati di prova, questi valgono come seconde implementazioni e quindi rendono la generalità non più speculativa? Dovreste considerare una struttura di derisione più pesante che consente di prendere in giro collaboratori concreti (ad esempio Moles contro Moq nel mondo C #)? Oppure, dovresti testare con le lezioni concrete e scrivere quelli che potrebbero essere considerati test di "integrazione" fino a quando il tuo progetto richiede naturalmente il polimorfismo?

Sono curioso di leggere le opinioni di altre persone su questo argomento - grazie in anticipo per le tue opinioni.

    
posta Erik Dietrich 05.02.2012 - 23:00
fonte

5 risposte

14

Sono andato a leggere il post sul blog e sono d'accordo con molte delle affermazioni dell'autore. Tuttavia, se stai scrivendo il tuo codice usando le interfacce per scopi di testing unitario, direi che l'implementazione fittizia dell'interfaccia è la tua seconda implementazione. Direi che non aggiunge molto alla complessità del tuo codice, specialmente se il compromesso di non farlo risulta che le tue classi sono strettamente accoppiate e difficili da testare.

    
risposta data 05.02.2012 - 23:29
fonte
3

Il test del codice in generale non è facile. Se lo fosse, lo avremmo fatto tutto molto tempo fa, e non ne abbiamo fatto un bel po 'solo negli ultimi 10-15 anni. Una delle maggiori difficoltà è sempre stata nel determinare come testare il codice che è stato scritto in modo coeso e ben fattorizzato e testabile senza interrompere l'incapsulamento. Il principio BDD suggerisce che ci concentriamo quasi interamente sul comportamento, e in qualche modo sembra suggerire che non è davvero necessario preoccuparsi dei dettagli interni in misura così grande, ma questo può spesso rendere le cose abbastanza difficili da testare se ci sono un sacco di metodi privati che fanno "cose" in un modo molto nascosto, in quanto possono aumentare la complessità complessiva del test per gestire tutti i possibili risultati a un livello più pubblico.

La derisione può aiutare in una certa misura, ma di nuovo è piuttosto focalizzata esternamente. La dipendenza dall'iniezione può anche funzionare molto bene, di nuovo con mock o test double, ma questo può anche richiedere l'esposizione di elementi tramite un'interfaccia, o direttamente, che altrimenti avresti preferito rimanere nascosti - questo è particolarmente vero se desideri avere un buon livello di sicurezza paranoico su certe classi all'interno del tuo sistema.

Per me, la giuria non sa ancora se progettare le tue classi per essere più facilmente testabile. Questo può creare problemi se ti trovi a dover fornire nuovi test mantenendo il codice legacy. Accetto che dovresti essere in grado di testare assolutamente tutto in un sistema, eppure non mi piace l'idea di esporre - anche indirettamente - gli interni privati di una classe, solo per poter scrivere un test per loro.

Per me, la soluzione è sempre stata quella di adottare un approccio abbastanza pragmatico e combinare un numero di tecniche per adattarsi a ciascuna situazione specifica. Io uso molti doppi test ereditati per esporre proprietà e comportamenti interni ai miei test. Prendo in giro tutto ciò che può essere allegato alle mie lezioni, e dove non comprometterà la sicurezza delle mie lezioni, fornirò un mezzo per sovrascrivere o iniettare comportamenti ai fini del test. Prenderò in considerazione la possibilità di fornire un'interfaccia più guidata dagli eventi, se contribuirà a migliorare la capacità di testare il codice

Dove trovo il codice "non testabile" , cerco di capire se posso fare il refactoring per rendere le cose più testabili. Dove hai un sacco di codice privato nascosto dietro le quinte, spesso troverai nuove classi in attesa di essere scomposte. Queste classi potrebbero essere utilizzate internamente, ma possono spesso essere testate in modo indipendente con meno comportamenti privati, e spesso successivamente con meno livelli di accesso e complessità. Una cosa che faccio attenzione ad evitare, tuttavia, è scrivere codice di produzione con codice di test integrato. Può essere allettante creare " test lugs " che comporta l'inclusione di tali orrori come if testing then ... , che indica un problema di test non completamente decostruito e completamente risolto.

Potresti trovare utile leggere il libro xUnit Test Patterns di Gerard Meszaros, che copre tutto questo tipo di cose in modo molto più dettagliato di quanto io possa entrare qui. Probabilmente non faccio tutto ciò che suggerisce, ma aiuta a chiarire alcune delle situazioni di test più difficili da affrontare. Alla fine della giornata, vuoi essere in grado di soddisfare i tuoi requisiti di test mentre stai ancora applicando i tuoi progetti preferiti, e aiuta ad avere una migliore comprensione di tutte le opzioni al fine di decidere meglio dove potresti dover scendere a compromessi.

    
risposta data 06.02.2012 - 01:02
fonte
1

Il linguaggio che usi ha un modo di "deridere" un oggetto per il test? Se è così, queste fastidiose interfacce possono andare via.

Su una nota diversa ci possono essere dei motivi per avere un SimpleInterface e un unico ComplexThing che lo implementa. Potrebbero esserci pezzi del ComplexThing che non si desidera siano accessibili all'utente SimpleInterface. Non è sempre a causa di un esagerato coder OO-ish.

Ora vado via e permetto a tutti di saltare sul fatto che il codice che fa questo "cattivo odore" a loro.

    
risposta data 05.02.2012 - 23:50
fonte
0

Risponderò in due parti:

  1. Non hai bisogno di interfacce se sei interessato solo ai test. Uso i framework di simulazione a tale scopo (in Java: Mockito o easymock). Credo che il codice che progettate non debba essere modificato a scopo di test. Scrivere codice verificabile equivale a scrivere codice modulare, quindi tendo a scrivere codice modulare (verificabile) e testare solo le interfacce pubbliche di codice.

  2. Ho lavorato in un grande progetto Java e sono profondamente convinto che l'utilizzo di interfacce di sola lettura (solo getter) sia la strada da percorrere (tieni presente che io Sono un grande fan dell'immutabilità). La classe di implementazione può avere setter, ma questo è un dettaglio di implementazione che non dovrebbe essere esposto ai livelli esterni. Da un altro punto di vista preferisco la composizione rispetto all'ereditarietà (modularità, ricordi?), Quindi anche le interfacce aiutano qui. Sono disposto a pagare il costo della generalità speculativa che a spararmi nel piede.

risposta data 01.06.2013 - 02:42
fonte
0

Ho visto molti vantaggi da quando ho iniziato a programmare di più su un'interfaccia oltre il polimorfismo.

  • Mi costringe a pensare in anticipo all'interfaccia della classe (sono i metodi pubblici) in anticipo e a come interagirà con le interfacce delle altre classi.
  • Mi aiuta a scrivere classi più piccole che sono più coese e seguono il principio di responsabilità singola.
  • È più facile testare il mio codice
  • Meno classi statiche / stato globale come la classe deve essere a livello di istanza
  • Più facile integrare e assemblare i pezzi prima che l'intero programma sia pronto
  • Iniezione delle dipendenze, separando la costruzione dell'oggetto dalla logica aziendale

Molte persone saranno d'accordo sul fatto che più, classi più piccole sono meglio di classi meno numerose e più grandi. Non è necessario concentrarsi su una sola volta e ogni classe ha uno scopo ben definito. Altri potrebbero dire che stai aggiungendo alla complessità avendo più classi.

È utile utilizzare gli strumenti per migliorare la produttività, ma penso che basarsi esclusivamente sui framework Mock e sul posto in termini di testabilità e modularità degli edifici direttamente nel codice comporterà un codice di qualità inferiore nel lungo periodo.

Tutto sommato, credo che mi abbia aiutato a scrivere un codice di qualità superiore e i benefici superano di gran lunga qualsiasi conseguenza.

    
risposta data 01.06.2013 - 09:30
fonte

Leggi altre domande sui tag