Qual è il metodo di test più appropriato in questo scenario?

5

Sto scrivendo alcune app Objective-C (per OS X / iOS) e attualmente sto implementando un servizio per condividerle. Il servizio è destinato ad essere abbastanza autonomo.

Per la funzionalità attuale immagino che ci sarà un solo metodo che i client chiameranno per fare una serie di passaggi abbastanza complicata sia usando metodi privati sulla classe, sia passando dati attraverso una serie di "classi di manipolazione dei dati" a arriva a un risultato finale.

L'essenza del codice è di recuperare un registro delle modifiche, memorizzato in un archivio dati interno al servizio, che si è verificato da un particolare momento, semplifica il registro per includere solo l'ultima modifica applicabile per ciascun oggetto, allegare il serializzato valori per gli oggetti interessati e restituire tutto al client.

La mia domanda allora è, come posso testare questo metodo di ingresso? Ovviamente, ogni classe dovrebbe avere test unitari accurati per assicurare che la loro funzionalità funzioni come previsto, ma il punto di ingresso sembra più difficile da "disconnettere" dal resto del mondo. Preferirei non inviare in ognuna di queste classi interne lo stile IoC, perché sono piccole e sono fatte solo classi per soddisfare il principio della responsabilità unica.

Vedo un paio di possibilità:

  1. Creare un'intestazione di interfaccia "privata" per i test con metodi che chiamano le classi interne e testa ciascuno di questi metodi separatamente. Quindi, per testare il punto di ingresso, crea un mock parziale della classe di servizio con questi metodi privati mocked e prova solo che i metodi siano chiamati con gli argomenti giusti.
  2. Scrivi una serie di test più grassi per il punto di ingresso senza prendere in giro nulla, testando l'intera funzionalità in un colpo solo. Questo mi sembra più simile a "test di integrazione" e sembra fragile, ma soddisfa il principio del "solo test tramite l'interfaccia pubblica".
  3. Scrivi uno stabilimento che restituisce questi servizi interni e prendi quello nell'inizializzatore, quindi scrivi uno stabilimento che restituisca versioni fittizie di essi da utilizzare nei test. Questo ha il rovescio della medaglia nel rendere fastidiosa la costruzione del servizio e fa filtrare i dettagli interni al client.
  4. Scrivi un inizializzatore "privato" che utilizzi questi servizi come parametri aggiuntivi, utilizzalo per fornire servizi di simulazione e reinserire l'inizializzatore pubblico su questo. Ciò assicurerebbe che il codice client veda ancora l'inizializzatore facile / carino e che non vi siano perdite di internals.

Sono sicuro che ci sono altri modi per risolvere questo problema a cui non avevo ancora pensato, ma la mia domanda è: qual è l'approccio più appropriato in base alle migliori pratiche di test unitario? Soprattutto considerando che preferirei scrivere questo test-first, nel senso che preferibilmente dovrei creare questi servizi solo perché il codice indica una necessità per loro.

    
posta Daniel Bruce 24.04.2013 - 10:45
fonte

2 risposte

2

C'è qualche logica importante nel tuo metodo di "entry point"? Sembra che il suo unico compito sia quello di istanziare alcune altre classi e collegarle insieme. Se è vero, direi che questo è un codice banale, non è critico per l'azienda, si noterà immediatamente se fosse rotto (in genere se si ottiene che questo tipo di codice "di composizione" sia sbagliato non verrà nemmeno compilato), quindi - So che questo sembra blasfemo - non ha bisogno di test unitari . "Il codice migliore non è un codice" si applica anche ai test: se un test non sta testando qualcosa di importante, difficile o soggetto a rottura senza rilevamento, allora quel test non vale lo sforzo di scrivere o mantenimento.

Se il metodo main è più di un semplice codice (i blocchi try...catch sono comuni in questo tipo di metodo, come le istruzioni if per la configurazione), probabilmente dovresti prendere in considerazione l'estrazione di una logica. Ad esempio, se nella tua app ci sono molte dichiarazioni if potresti estrarre una classe Configuration che gestisca configurazioni arbitrarie e possa essere facilmente testata o estesa unitamente.

    
risposta data 27.05.2014 - 00:17
fonte
1

Vorrei fare qualcosa come la tua versione 4 ma rendere pubblico quell'inizialista.

@interface Frobulator : NSObject

- (instancetype)init; //uses default collaborators
- (instancetype)initWithThingifier: (id <Thingifiying>)thingifier doohicker: (id <Doohicking>)doohicker; //designated initialiser

- (NSData *)frobulate: (NSData *)unfrobulatedData;

@property (nonatomic, readonly) id <Thingifying>thingifier;
@property (nonatomic, readonly) id <Doohicking>doohicker;

@end

Ora i tuoi test di unità:

  • verifica che -frobulate: interagisca con i collaboratori impostati tramite l'inizializzatore. Questi possono essere dei matti.
  • verifica che -init configuri i veri collaboratori.

Hai quindi ottenuto l'opzione nella tua app utilizzando il metodo -init per ottenere i collaboratori predefiniti o fornire collaboratori diversi nel caso in cui le tue esigenze cambino.

    
risposta data 01.05.2013 - 11:54
fonte

Leggi altre domande sui tag