Che tipo di test unitari dovrebbero essere scritti per una chiamata a una funzione di aggiornamento del database?

6

Ho a che fare con molte funzioni come quella qui sotto e non sono sicuro di quale tipo di test di unità debba essere scritto per loro.

public void UpdateEmployeeClockIn(int employeeId)
{
var sql = string.format("Update Employee set ClockIn = GETUTCDATE() where employeeId={0}", employeeId);

db.ExecuteNonQuery(sql);
}

Penso che potrei refactoring questo metodo in uno che forse restituisce una dichiarazione sql e potrei eseguire alcuni test sulla dichiarazione sql per assicurarsi che ci sia una clausola where corretta ecc. Qualche idea? Continuo a scrivere codice come questo in cui semplicemente non so come o cosa testare.

    
posta Ben 26.11.2016 - 03:29
fonte

2 risposte

8

Dipende dal livello di astrazione tra questo codice e il database.

Se c'è un'astrazione, e puoi effettivamente mettere una finta invece dell'oggetto che effettivamente accede ad un vero database, allora i tuoi test di unità potrebbero controllare che la query SQL corretta venga inviata al mock quando il metodo viene chiamato con un Argomento arbitrario, diciamo 123. Dato il codice effettivo e il fatto che il test unitario è un test in bianco, non c'è nient'altro da testare. Le cose potrebbero essere diverse se stavi disinfettando i tuoi input, ad esempio controllando che employeeId non possa essere negativo o pari a zero.

Se non ci sono astrazioni, non puoi scrivere test unitari per questo metodo senza prima rifattorizzarlo. Puoi scrivere test automatici, ma quelli sarebbero test di integrazione, di sistema, funzionali o di accettazione , non test di unità. In questo caso, eseguirai:

  • Un test simile che passa un ID di un dipendente esistente e controlla che la voce del database sia stata cambiata.

  • Un test che utilizza un ID che non esiste.

  • Un test eseguito su un database impostato in modalità di sola lettura o su un database irraggiungibile (entrambi i casi sono particolarmente importanti se si dispone di un cluster).

Nota che:

  • Mentre nel codice reale, SQL Injection è impossibile, questo è ancora un modo sbagliato di usare il database. Quando si utilizzano le query parametrizzate, il motore di database può memorizzare nella cache il piano di esecuzione. Nel tuo caso, stai praticamente impedendoti di farlo.

  • Non stai disinfettando i tuoi input. Questo potrebbe essere intenzionale, se vuoi lasciare che il tuo database restituisca un errore su valori non validi, ma di solito indica che lo sviluppatore ha semplicemente dimenticato di farlo. Questo potrebbe rendere più semplice l'esecuzione di attacchi DOS contro il tuo database.

  • Non stai riscontrando alcuna eccezione. È intenzionale? Potrebbe essere: ad esempio, potresti preferire il passaggio dell'eccezione allo stack in modo che il livello aziendale o il livello di presentazione possano gestirlo. Tuttavia, ci sono casi in cui devi fare alcune eccezioni al momento. Ad esempio, se si accede a un cluster, potrebbe essere necessario gestire istanze di sola lettura o semplici perdite di connessione; invece di mostrare semplicemente l'errore all'utente, il metodo può gestire l'eccezione con garbo riprovando la query o facendo ciò che ha senso fare in una situazione specifica.

risposta data 26.11.2016 - 03:53
fonte
2

Oltre alla risposta di @ Arseni.

Il tuo metodo dipende dal database. Anche la variabile db è un'interfaccia che si sta ancora passando per una query che dipende dal tipo di database o dalla versione del database ecc.

I test unitari sono test che sono separati da qualsiasi operazione di I / O (file system, servizio Web o database). Poiché i test unitari sono test che lo sviluppatore deve eseguire ogni 1 minuto e che i tempi di esecuzione dei test devono essere molto brevi (secondi). Mentre i test con operazioni IO coinvolte richiedono più tempo.

I test che dipendono dalle operazioni IO hanno luogo, ma hanno chiamato "Test di integrazione".

Quindi, se il tuo metodo fa parte del livello dati, puoi scrivere test di integrazione in cui puoi eseguire un'altra query SELECT e asserire che la colonna ClockIn non è NULL dopo l'aggiornamento.

Tuttavia, se il tuo metodo fa parte della logica aziendale, per un corretto test dell'unità è necessario ad esempio astrarre questo metodo nell'interfaccia. Quindi mentre si verifica un metodo che utilizzerà UpdateEmployeeClockIn , si può affermare che il metodo UpdateEmployeeClockIn è stato chiamato con il parametro corretto.

Il tuo metodo ha un altro caso che può rendere quasi impossibile il test. Si utilizza la funzione datetime del sistema ClockIn = GETUTCDATE() . La funzione GETUTCDATE() restituisce sempre un valore diverso. Non è possibile creare un valore atteso rispetto al quale il risultato può essere affermato.
Poiché utilizzi il tempo dal database, ti suggerisco di spostare il metodo temporale nella funzione dedicata nell'interfaccia. Quale può essere preso in giro e restituire il valore previsto definito per il test

public interface IDataService
{
    void UpdateEmployeeClockIn(int employeeId, DateTime timeIn);
    DateTime GetCurrentTime();       
}

Poi nel tuo livello logico aziendale

public class EmployerManager
{
    private readonly IDataService _dataService;

    public Employer(IDataService dataService)
    {
        _dataService = dataService;
    }

    public void UpdateClockinWithCurrentTime(int employerId)
    {
        DateTime currentTime = _dataService.GetCurrentTime();
        _dataService.UpdateEmployeeClockIn(employerId, currentTime);
    }
}

In questo caso puoi scrivere unit test per il metodo UpdateClockinWithCurrentTime in cui puoi testare tramite la simulazione di IDataService che UpdateEmployeeClockIn è stato eseguito con i parametri corretti.

    
risposta data 26.11.2016 - 17:40
fonte

Leggi altre domande sui tag