In caso di test unitario dell'ordine delle operazioni? Se é cosi, come?

4

Immagina una classe di codice psedo come questa che nomi i confronti:

public class NameComparer
{
    public bool DoNamesMatch(Name name1, Name name2)
    {
        if(WholeNameMatch(name1, name2))
        {
            return true;
        }

        return ProcessorIntensiveFuzzyMatcher(name1.Forename, name2.Forename)
                && ProcessorIntensiveFuzzyMatcher(name1.Surname, name2.Surname)
    }

    private bool WholeNameMatch(Name name1, Name name2)
    {
        return (name1.Forename == name2.Forename)
                && (name1.Surname == name2.Surname);
    }

    private bool ProcessorIntensiveFuzzyMatcher(string string1, string string2)
    {
        // complex fuzzy matching
    }
}

La best practice suggerisce che questa classe dovrebbe avere un solo metodo pubblico, e che dovremmo testare unitariamente i metodi privati in questa classe attraverso quel metodo pubblico.

Mentre posso scrivere vari test che assicurano che WholeNameMatch e ProcessorIntensiveFuzzyMatcher facciano entrambi quello che dovrebbero fare, l'ordine delle operazioni è importante qui. Se uno sviluppatore si muove erroneamente uno sopra l'altro, il programma subirà un enorme successo in termini di prestazioni.

È auspicabile, e possibile, testare questo ordine di operazioni tramite il metodo pubblico esposto?

    
posta Matt Thrower 09.11.2017 - 18:23
fonte

4 risposte

10

La premessa della tua domanda non è corretta. Non si desidera testare il nome di WholeNameMatch e viene chiamato ProcessorIntensiveFuzzyMatcher.

Vuoi testare

GIVEN WholeNameMatch returns true
WHEN NamesMatch is called
THEN ProcessorIntensiveFuzzyMatch is not called

Questo è facile se la corrispondenza fuzzy è fatta da una dipendenza iniettata che viene derisa dal test dell'unità. La maggior parte dei framework di simulazione consente di verificare che una chiamata non si è verificata.

Se il matcher fuzzy non è una dipendenza iniettata, dovrebbe essere.

    
risposta data 09.11.2017 - 19:04
fonte
6

@KevinCline ha dato una risposta eccellente, ma vorrei aggiungere qualcosa che si può fare quando iniettare il fuzzy matcher non sembra valere la pena e risolvere le cose in un modo più semplice.

Si può semplicemente aggiungere una proprietà booleana di sola lettura LastMatchWasFuzzy a NameComparer e inizializzarla in questo modo:

public bool DoNamesMatch(Name name1, Name name2)
{
    LastMatchWasFuzzy=false;
    if(WholeNameMatch(name1, name2))
    {
        return true;
    }

    LastMatchWasFuzzy = ProcessorIntensiveFuzzyMatcher(name1.Forename, name2.Forename)
                     && ProcessorIntensiveFuzzyMatcher(name1.Surname, name2.Surname);
    return LastMatchWasFuzzy;
}

Ciò dà al chiamante la possibilità di verificare come è stata raggiunta l'ultima partita, il che può essere utile non solo per un test unitario, ma anche per altri casi d'uso.

In generale, a volte non è la peggiore strategia aggiungere alcuni "portelli di manutenzione" a una classe, il che significa proprietà o funzioni solo a scopo di test (tuttavia, questo è sempre un compromesso, questi non dovrebbero inquinare l'API pubblica in modo preoccupante).

    
risposta data 10.11.2017 - 11:58
fonte
5

Best practice suggests that this class only ought to have one public method, and that we ought to unit test the private methods in this class through that public method.

Le buone pratiche suggeriscono che ai test non interessano dettagli di implementazione come i metodi privati. I metodi pubblici sono un modo per esporre funzionalità al mondo esterno, non un modo per testare metodi privati.

While I can write various tests that ensure WholeNameMatch and ProcessorIntensiveFuzzyMatcher both do what they're supposed to do,

Forse puoi, ma non è quello che vuoi. Vuoi assicurarti che l'interfaccia pubblica faccia quello che dovrebbe fare. In che modo questo non è rilevante per i clienti della tua classe e non dovrebbe essere rilevante per i tuoi test.

the order of operation is important here. If a developer mistakenly moves one above the other, the program is going to take a massive performance hit.

L'ordine delle operazioni private è privo di significato per i client della tua classe, inclusi i test, perché non sono nemmeno a conoscenza dell'esistenza di tali operazioni, né tanto meno in quale ordine vengono eseguite. Se le prestazioni sono importanti, prova le prestazioni. In alternativa, segui il consiglio di kevin sull'utilizzo dell'input delle dipendenze e l'intero problema relativo ai metodi privati scomparirà.

Is it desirable - and possible - to test this order of operations via the exposed public method?

Qualche cosa sui metodi privati sono dettagli di implementazione. Se si verificano i dettagli dell'implementazione e in seguito si modificano i dettagli di implementazione, i test non avranno esito positivo. Non è consigliabile, a patto che l'interfaccia pubblica offra la stessa funzionalità che i test non dovrebbero interrompere.

    
risposta data 09.11.2017 - 21:52
fonte
1

No.

I test unitari dovrebbero consentire di riscrivere o riscrivere in modo sicuro il codice mostrando che tutto funziona ancora come previsto. Ma se il codice di test è abbinato ai dettagli di implementazione dell'unità, sarà necessario modificare il test allo stesso tempo quando si modifica il codice. Il che significa che potresti facilmente introdurre errori, e l'intero vantaggio dei test unitari va perso.

In breve, i test unitari dovrebbero verificare che i requisiti dell'unità siano soddisfatti, ma non preoccuparsi dei dettagli di implementazione.

Questo è il motivo per cui è una cattiva pratica testare metodi privati. I metodi privati dovrebbero essere i dettagli di implementazione. Lo stesso vale per verificare se un metodo privato viene chiamato o meno, o in quale ordine.

Nel tuo caso, il comportamento previsto è un certo livello di corrispondenza fuzzy. Quale algoritmo viene utilizzato e se è implementato in uno o più metodi è un dettaglio di implementazione.

Immagina di trovare un algoritmo di corrispondenza fuzzy migliore che abbia la proprietà che sia veloce o più veloce di una normale corrispondenza di stringa quando c'è una corrispondenza esatta. Questo ti consente di saltare l'involucro speciale della corrispondenza esatta. Questo sarebbe un miglioramento che ha preservato il comportamento previsto, ma causerebbe il fallimento del test! Il test ti impedisce di migliorare il codice.

Oppure potresti capire che se Forename ha una corrispondenza esatta ma Surname no, non c'è motivo di eseguire nuovamente una partita sfocata costosa su Forename . Ma prevenire questo richiederà una ristrutturazione dell'ordine delle chiamate al metodo, quindi molto probabilmente testeremo nuovamente impedirà questo cambiamento, anche se il comportamento rimane corretto e le prestazioni sono migliorate. Quindi il test ha un valore negativo.

Ma capisco che tu voglia assicurarti che il confronto tra stringhe completamente uguali non debba essere significativamente più lento a causa dell'algoritmo fuzzy. Vedo due possibilità:

  • il vantaggio in termini di prestazioni per questa ottimizzazione è insignificante
  • il vantaggio prestazionale è significativo

Se è insignificante, non dovresti perdere tempo a provarlo. Se è significativo, dovresti testarlo, ma dovresti testare le prestazioni effettive (tempo o cicli del processore), non i dettagli di implementazione irrilevanti.

    
risposta data 10.11.2017 - 14:27
fonte

Leggi altre domande sui tag