TDD: separa il codice di test dal codice di produzione, evitando la corrispondenza one-to-tone. Spiegazione ed esempi di codice

2

Cerco di capire meglio in quali circostanze la corrispondenza uno a uno tra codice di prova e codice di produzione non è necessaria, dopo aver letto TDD Harms Architecture . Ad esempio, prima:

"una corrispondenza uno a uno implica un accoppiamento estremamente stretto."

quindi di seguito:

"Se guardi una parte di FitNesse scritta dopo il 2008, vedrai che c'è un calo significativo nella corrispondenza uno a uno: i test e il codice assomigliano più al design verde sulla destra. "

Non spiega perché la corrispondenza one-to-one è stata abbandonata.

Voglio sapere perché, partendo dal presupposto che solo il metodo pubblico è testato

  • È perché la classe o il metodo di produzione sono coperti da altre classi / metodi di prova INDIRETTAMENTE? o
  • È perché sono così banali, o lo sviluppatore è così fiducioso, che il codice di prova viene eliminato? o
  • Esistono schemi o regole generali di progettazione che possono aiutare a scrivere codice che disaccoppia il test dal codice di produzione?
  • Qualche altro motivo?

Qualcuno potrebbe spiegarlo?

    
posta Pingpong 20.07.2017 - 11:08
fonte

2 risposte

1

Is it because the production class or method are covered by other test class/method INDIRECTLY?

Fondamentalmente si. I test servono a garantire che il sistema abbia alcune proprietà. Di solito il sistema fornisce alcune funzionalità. (A volte scriviamo test per riprodurre un comportamento difettoso, tuttavia, ad esempio, quando si registra un bug report.) Quindi se si scrivono nuovi test meno fragili perché sono più disaccoppiati dal codice di produzione, e anche più espressivi perché indicare le funzionalità ottenute dal sistema e non il modo in cui il sistema le ottiene; i vecchi test diretti non forniscono alcun valore aggiuntivo e devono essere rimossi perché rappresentano un onere di manutenzione. L'indirazione ti consente di ottenere sia il disaccoppiamento che l'espressività.

Are there general design pattern or rules that can help writing code that decouples test from production code?

Lo schema verde di zio Bob sulla destra sarebbe ed esempio del modello di facciata perché nasconde servizi multipli e complicati dietro una singola interfaccia.

Per chiarire una cosa, gli sviluppatori junior non leggono il diagramma UML di zio Bob letteralmente. I servizi implementano la facciata in senso sprovveduto.

Vedi sotto l'esempio per vedere come i servizi applicativi implementano la facciata del test. Per vedere anche la differenza / relazione tra la facciata dell'applicazione e la facciata del test (come nel commento di @ jonrsharpe). Un suggerimento può essere: scrivi la tua facciata di prova in modo che si legga come le storie che dimostrerai, o gli scenari nel tuo piano di test, ecc.

class SomeTestClass
{
    public void users_cant_register_without_providing_contact_info()
    {
        failsWithMissingContactInfoErrorMessage( 
            () -> user.sendsRegistrationFormWithMissingContactInfo());

        admin.doesntSeeInRegisteredUsers(user);
    }

    private failsWithMissingContactInfoErrorMessage(Runnable r)
    {
        failsWithErrorMentioning(r, MissingContactInfo.class, "missing contact");
    }
}

class UserFacade // Can be named after roles
{
    CommandInterface handler;
    ViewInterface view;

    String enteredEmail;
    String enteredPhoneNumber;

    void sendsRegistrationFormWithMissingContactInfo()
    {
        fillsTheForm();
        leavesEmailBlank();
        leavesPhoneNumberBlank();
        sendsRegistrationForm();
    }

    void leavesEmailBlank()
    {
        this.enteredEmail = "";
    }

    void leavesPhoneNumberBlank()
    {
        this.enteredPhoneNumber = "";
    }

    void entersAValidEmail()
    {
        this.enteredEmail = "[email protected]";
    }

    void entersAValidPhoneNumber()
    {
        this.enteredPhoneNumber = "+1-541-754-3010";
    }

    void entersAnInvalidPhoneNumber()
    {
        this.enteredPhoneNumber = "##123456**";
    }

    void sendsRegistrationForm()
    {
        handler.handle(new RegisterNewUser(enteredEmail, enteredPhoneNumber));
    }

}

class AdminFacade
{
    BackEnd backEnd;

    void doesntSeeInRegisteredUsers(UserFacade user)
    {
        // ....
    }
}
    
risposta data 21.07.2017 - 09:58
fonte
1

È accennato nell'articolo, ma non è spiegato chiaramente.

L'idea principale qui è che il codice cambia. Quindi se segui l'ideologia che i test dovrebbero avere una struttura uno-a-uno per il codice che sta testando, allora se la struttura del codice cambia, quindi ha bisogno di una struttura di test.

Ma questo è esattamente ciò che non vuoi. Vuoi essere in grado di rinominare i metodi o estrarre le classi senza preoccuparti dei test e avere la certezza che i test riporteranno se la modifica non ha infranto nulla. E cambiare i test aumenta i rischi che tali cambiamenti possano rompere i test. Inoltre, dover modificare i test insieme al codice richiede più tempo.

Nella mia esperienza, il modo migliore è costruire un involucro o una facciata attorno al codice testato. Questa facciata rende i test più chiari descrivendo l'intento e rende facile apportare modifiche strutturali al codice in esame senza modifiche importanti ai test stessi. Solo il cambiamento è davvero necessario all'interno del wrapper.

Esempio che è vicino a quello che ho in mente sono i test BDD. Il wrapper attorno al codice fornisce l'API testuale in modo che i test siano più facili da scrivere e comprendere, e in genere questa API basata su testo non espone la struttura dell'implementazione. Ma il wrapper non deve arrivare fino a fornire API di testo in stile BDD. Può utilizzare un semplice "linguaggio specifico di dominio" basato su classi che utilizza le funzionalità del linguaggio invece del framework di test BDD.

    
risposta data 20.07.2017 - 11:21
fonte

Leggi altre domande sui tag