Test unitario - per iniziare


14

Ho appena iniziato con i test unitari, ma non sono sicuro di capire davvero il punto di tutto. Ho letto tutorial e libri su tutto, ma ho solo due domande veloci:

  1. Ho pensato che lo scopo del test unitario fosse testare il codice che abbiamo effettivamente scritto. Tuttavia, a me sembra che per essere in grado di eseguire il test, dobbiamo modificare il codice originale, a quel punto non stiamo davvero testando il codice che abbiamo scritto ma piuttosto il codice che abbiamo scritto per il test.

  2. La maggior parte dei nostri codici si basa su fonti esterne. Al momento del refactoring del nostro codice, anche se rompesse il codice originale, i nostri test funzionerebbero comunque bene, dal momento che le fonti esterne sono solo confuse nei nostri casi di test. Non sconfigge lo scopo del test unitario?

Scusami se sembro stupido qui, ma ho pensato che qualcuno potesse illuminarmi un po '.

Grazie in anticipo.

Risposte:


7

I miei 0,02 $ ... questo è un po 'soggettivo, quindi prendi un po' di sale, ma speriamo che ti faccia pensare e / o scatenare qualche dialogo:

  1. Lo scopo principale del test unitario per me è quello di garantire che il codice che hai scritto soddisfi i contratti e i casi limite che il tuo codice è destinato a soddisfare. Con i test unitari in atto, puoi assicurarti meglio che quando tu (o qualcun altro in futuro) riformatterai il tuo codice, tutti i consumatori esterni del tuo codice dovrebbero rimanere inalterati se hai una copertura statale adeguata. (almeno nella misura in cui intendi che non siano interessati).

    Nella maggior parte dei casi dovresti essere in grado di scrivere codice che può essere spedito alla produzione ed è facilmente testabile in unità. Un buon punto di partenza potrebbe essere quello di esaminare i modelli e le strutture di iniezione di dipendenza. (O altre filosofie per la tua lingua / piattaforma di scelta).

  2. È corretto che implementazioni esterne potrebbero influire sul codice. Tuttavia, garantire che il codice funzioni correttamente come parte di un sistema più grande è una funzione del test di integrazione . (Che potrebbe anche essere automatizzato con vari gradi di sforzo).

    Idealmente il tuo codice dovrebbe fare affidamento solo sui contratti API di qualsiasi componente di terze parti, il che significherebbe che fintanto che i tuoi mock soddisfano l'API corretta i tuoi test unitari continueranno a fornire valore.

    Detto questo, ammetterò prontamente che ci sono state volte in cui ho rinunciato ai test unitari a favore dei soli test di integrazione, ma sono stati solo i casi in cui il mio codice ha dovuto interagire così abbondantemente con componenti di terze parti con API scarsamente documentate. (ovvero l'eccezione anziché la regola).


5
  1. Prova a scrivere prima i test. In questo modo, avrai una solida base per il comportamento del tuo codice e il tuo test diventerà un contratto per il comportamento richiesto del tuo codice. Pertanto, la modifica del codice per superare il test diventa "modifica del codice per soddisfare il contratto proposto dal test" anziché "modifica del codice per superare il test".
  2. Bene, fai attenzione alla differenza tra mozziconi e beffe. Non essere influenzato da eventuali modifiche al codice è un comportamento caratteristico degli stub, ma non delle beffe. Cominciamo con la definizione del finto:

    Un oggetto Mock sostituisce un oggetto reale in condizioni di test e consente di verificare le chiamate (interazioni) contro se stesso come parte di un test di sistema o unità.

    -L'arte del test unitario

Fondamentalmente, le tue beffe dovrebbero verificare il comportamento richiesto delle tue interazioni. Pertanto, se l'interazione con il database ha esito negativo dopo un refactoring, anche il test con la simulazione dovrebbe fallire. Questo ovviamente ha dei limiti, ma con un'attenta pianificazione le tue beffe faranno molto di più del semplice "stare seduti lì" e non "vanificheranno lo scopo del test unitario".


1

Fare una buona domanda non è stupido in alcun modo.

Risponderò alle tue domande.

  1. Lo scopo del test unitario non è testare il codice che hai già scritto . Non ha nozione di tempo. Solo in TDD dovresti testare prima, ma ciò non si applica rigorosamente a nessun tipo di test unitario. Il punto è poter testare automaticamente ed efficacemente il tuo programma a livello di classe. Fai quello che devi fare per arrivarci, anche se ciò significa cambiare il codice. E lascia che ti dica un segreto - spesso significa questo.
  2. Quando scrivi un test hai 2 opzioni principali per aiutarti a garantire che il test sia corretto:

    • Varia gli input per ogni test
    • Scrivi un caso di prova che assicuri che il tuo programma funzioni correttamente, quindi scrivi un caso di prova corrispondente che assicuri che il tuo programma non funzioni nel modo in cui non dovrebbe

    Ecco un esempio:

    TEST(MyTest, TwoPlusTwoIsFour) {
        ASSERT_EQ(4, 2+2);
    }
    
    TEST(MyTest, TwoPlusThreeIsntFour) {
        ASSERT_NE(4, 2+3);
    }
    

    Inoltre, se stai testando l'unità la logica all'interno del tuo codice (non le librerie di terze parti), allora è perfettamente bene che non ti preoccupi della rottura dell'altro codice, mentre in quel contesto. Stai essenzialmente testando il modo in cui la tua logica si avvolge e utilizza le utility di terze parti, che è uno scenario di test classico.

Una volta terminato il test a livello di classe, puoi continuare a testare l'integrazione tra le tue classi (i mediatori, da quello che ho capito) e le librerie di terze parti. Questi test sono chiamati test di integrazione e non usano simulazioni, ma piuttosto implementazioni concrete di tutte le classi. Sono un po 'più lenti, ma comunque molto importanti!


1

Sembra che tu abbia un'app monolitica che fa tutto void main(), dall'accesso al database fino alla generazione dell'output. Ci sono diversi passaggi qui prima di poter iniziare il corretto test dell'unità.

1) Trova un pezzo di codice che è stato scritto / copiato e incollato più di una volta. Anche se è giusto string fullName = firstName + " " + lastName. Dividilo in un metodo, come:

private static string GetFullName (firstName, lastName)
{
    return firstName + " " + lastName;
}

Ora hai un pezzo di codice testabile dall'unità, per quanto banale possa essere. Scrivi un test unitario per questo. Risciacquare e ripetere questo processo. Alla fine finirai con un carico di metodi raggruppati logicamente e sarai in grado di estrarne un numero di classi. La maggior parte di queste classi sarà testabile.

Come bonus, una volta che hai estratto più classi, puoi estrarre interfacce da esse e aggiornare il tuo programma per parlare con le interfacce anziché con gli oggetti stessi. A quel punto, sei in grado di utilizzare un framework di derisione / stub (o anche i tuoi falsi fatti a mano) senza cambiare affatto il programma. Questo risulta molto utile dopo aver estratto le query del database in una classe (o molte) perché ora è possibile falsificare i dati che la query dovrebbe restituire.


0

Qualcuno intelligente ha detto "Se è difficile testarlo, è probabilmente un codice errato". Ecco perché non è una brutta cosa riscrivere il codice, per poterlo decomprimere. Il codice con dipendenze esterne è MOLTO DIFFICILE da testare, rappresenta un risc al codice e dovrebbe essere evitato ovunque possibile e concentrato nelle aree specifiche di integrazione del tuo codice, fx. classi di tipo facciata / gateway. Ciò renderà molto più semplice la modifica del componente esterno.

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.