È una cattiva pratica rendere pubblici i metodi esclusivamente per il controllo delle unità? [duplicare]

18

Ho una classe con un metodo pubblico. Ha altri metodi che "assistono" con lo scopo del metodo pubblico. Il metodo pubblico deve essere testato. Tuttavia, voglio anche testare unitamente i metodi privati.

Sarebbe impossibile per i miei test di unità chiamare metodi privati e testarli separatamente, quindi devono essere pubblici.

Il problema qui è che mi rimane un codice che, secondo me, è stato scritto con una mancanza di rispetto per la convenzione OOP, puramente per essere in grado di testarlo unitamente.

Ho ragione nel modificare l'accesso dei metodi esclusivamente per la "testabilità"? O dovrei ripensare e rifattorizzare la struttura nel suo complesso per separare la funzionalità dei metodi privati (nonostante il fatto che non facciano molto e potrebbero, a volte, assomigliare a piccoli metodi di supporto)?

    
posta JᴀʏMᴇᴇ 02.03.2015 - 11:39
fonte

4 risposte

24

Sì, è una pessima pratica: stai lasciando che i tuoi strumenti prendano decisioni sul design per te.

Penso che il problema principale qui è che stai cercando di trattare ogni singolo metodo come un'unità. Questa è generalmente la causa di tutti i problemi di test unitario. Tranne che in alcuni casi in cui il tuo metodo è molto complesso e richiede molti test, dovresti trattare le tue classi come unità. Martin Fowler tratta anche le classi correlate come unità (a volte).

Di conseguenza, dovresti creare un'istanza di una classe, quindi chiamare i metodi sulla sua interfaccia pubblica come verrebbe utilizzata. Questo ti dà i tuoi esempi per la tua documentazione, e assicura che funzioni come l'intera cosa è intesa. Dovresti provare qui per testare i metodi privati chiamando quelli pubblici - e se un metodo privato non viene mai chiamato, allora perché lo hai ancora nel codice?

    
risposta data 02.03.2015 - 12:43
fonte
7

Se hai metodi di "assistenza", è probabile che il tuo unico, grande metodo stia facendo davvero troppo. La suddivisione in metodi più piccoli e lo spostamento di questi metodi in classi separate con interfacce pubbliche mantiene la classe con il grande metodo responsabile di una cosa e di una cosa sola (vedere Principio di responsabilità singola). Questo spostamento in classi separate crea automaticamente una struttura testabile in quanto i tuoi metodi devono essere resi pubblici.

Un esempio

Acquisisci una classe BankAccount che esegue calcoli di budget esaminando un elenco di spese in esso contenute. Una spesa ha una categoria (sport, cibo, auto, ...), un importo e una data. Potresti avere un metodo su BankAccount chiamato "CalculateExpenditureFor" che prende un inizio e una data e una categoria. Tale metodo filtrerebbe le spese per avere solo spese che corrispondono alla data e alla categoria e quindi procedere a sommare gli importi. Prendi nota della quantità di "ands" di cui ho bisogno per esprimere questo requisito! Questa è una chiara indicazione che il tuo metodo fa troppo.

Questo metodo farà effettivamente due cose: filtrare le spese e sommare gli importi. Ora immagina che il tuo BankAccount contenga una classe ExpenditureLedger, che contiene le spese (un "libro mastro" è un libro o un file contenente un elenco di transazioni finanziarie, da cui il nome). Potresti quindi avere un metodo FilterExpenditures su ExpenditureLedger che si occupa del filtraggio in data e categoria. L'elenco risultante di spese può quindi essere utilizzato da BankAccount per calcolare gli importi.

In questo scenario, è possibile testare un metodo pubblico su ExpenditureLedger (si alimentano le spese di contabilità generale e si controlla il risultato). Puoi anche controllare il metodo BankAccount inserendo un ExpenditureLedger (preferibilmente preso in giro) in modo da poter testare il codice che riassume gli importi restituiti da ExpenditureLedger.

    
risposta data 02.03.2015 - 11:56
fonte
4

Rendere i metodi pubblici - sì, è una cattiva pratica. Rendendoli interni - dipende.

Invece di rendere pubblici tutti i metodi da testare e invece di riprogettare completamente le classi, a volte la soluzione più pragmatica è rendere i metodi "interni" e utilizzare l'attributo "InternalsVisibleTo" per consentire ai test delle unità di accedervi . Ciò potrebbe non portare al design ideale in primo luogo, ma a volte devi solo fare le cose, e testare il codice "interno" è spesso meglio che non avere alcun test o introdurre accidentalmente nuovi bug rifattorizzando quei metodi privati per classi separate senza aver prima scritto alcun test unitario.

Vedi anche Come definire i dettagli di implementazione

    
risposta data 02.03.2015 - 15:57
fonte
3

Non dovresti modificare i test di unità se hai solo cambiato i dettagli di implementazione interni e non l'API. Il fatto che i tuoi vecchi test funzionino ancora dopo un cambiamento è la prova che le tue modifiche non hanno rotto nulla. Puoi guardare il registro di commit, vedere che assolutamente nulla nel codice di test è cambiato e sentirsi rassicurato (purché i test siano stati buoni in primo luogo).

Ora stai proponendo un design che impone modifiche ai tuoi test quando modifichi l'implementazione. Distruggi questa fiducia. Ci sarà sempre il rischio che siano state apportate modifiche ai veri test API pubblici durante il test delle modifiche di implementazione.

O la gerarchia dell'oggetto è sbagliata e quei metodi dovrebbero essere pubblici su qualche altro oggetto, oppure sono dettagli di implementazione per il tuo oggetto e stai perdendo tempo a testarli (e rischiando di rendere più fragile l'intero progetto). Prova il contratto.

Una domanda importante è " Perché vuoi metterli alla prova?" Se questi metodi sono nel posto giusto e non sono rilevanti per il codice esterno, allora devi smettere di essere un maniaco del controllo, scrollarti di dosso il tuo OCD e fidarti del sistema. Se non testarli è davvero un problema, la tua attuale gerarchia di oggetti (o la tua progettazione API) è sbagliata.

    
risposta data 02.03.2015 - 12:58
fonte

Leggi altre domande sui tag