Flusso di lavoro TDD con test di integrazione

1

Ho problemi con l'utilizzo del giusto flusso di lavoro con TDD. Alcuni dicono che dovremmo progettare prima di scrivere qualsiasi codice, alcuni dicono che dovremmo fare un test, farlo passare, quindi rifattorizzare il codice e questo dovrebbe aiutare il nostro pensiero a trovare il progetto giusto.

Ma tutti affermano che dovresti avere 10 volte più test unitari rispetto ai test di integrazione.

Mi sento a mio agio iniziando facendo un test di integrazione che farebbe tutta la user story, e quindi il refactoring. Ma alla fine ho quasi solo test di integrazione e quasi nessun test unitario.

Ecco un esempio del flusso di lavoro che utilizzo:

Caratteristica: un utente deve essere in grado di registrarsi tramite l'API in / register /, passando l'email e una password.

Prima farei un test di integrazione:

<?php

class RegisterUserTest extends TestCase
{
    /** @test */
    public function user_is_registered_when_email_is_valid()
    {
        $email = '[email protected]';

        // Given the email was not registered before
        $this->notSeeInDatabase('user', ['email' => $email]);

        // When we post a request to /register/
        $this->json('POST', '/register/', ['email' => $email]);

        // Then we see the user is now registered
        $this->seeInDatabase('user', ['email' => $email]);
    }
}

Quindi scriverei il codice per fare passare questo test (mostrerò solo il codice del controller):

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\User;

class RegisterController extends Controller
{
    public function registerUser(Request $request)
    {
        (new User)->create(['email' => $request->input('email')]);
    }
}

Ora basta fare il primo test, quindi mi fermo qui e faccio un altro test per dimostrare che quando l'e-mail non è valida non dovrebbe essere inserita nel database, e dovrebbe restituire un codice di risposta 422:

<?php

class RegisterUserTest extends TestCase
{
    //...

    /** @test */
    public function invalid_email_returns_a_422_and_user_is_not_registered()
    {
        // Given the email is invalid
        $email = 'invalid';

        // When we post a request to /register/
        $this->json('POST', '/register/', ['email' => $email]);

        // Then we see the response code is 422
        $this->assertResponseStatus(422);
        $this->notSeeInDatabase('user', ['email' => $email]);
    }
}

Ora scriverò il codice per farlo passare:

public function registerUser(Request $request)
    {
        $email = $request->input('email');

        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return response('Invalid Email', 422);
        }

        (new User)->create(['email' => $email]);
    }

Ora per me è già troppa logica nel controller, quindi dovrei fare il refactoring ovviamente:

public function registerUser(Request $request)
{
    try {
        $user = new User;
        $user->setEmail($request->input('email'));

        $user->save();
    } catch (InvalidArgumentException $invalidArgumentException) {
        return response($invalidArgumentException->getMessage(), 422);
    }
}

Hai un'idea. Non dovevo fare test di unità lì, i miei test di integrazione funzionano bene e sono sufficienti. Potrei andare avanti senza test necessari dell'unità.

Quindi come / quando inizi il test unitario?

    
posta Steve Chamaillard 17.04.2018 - 14:35
fonte

2 risposte

4

Può essere un po 'opprimente cercare di trovare il giusto equilibrio per il tuo progetto. Mi piacerebbe aiutarti con una struttura di come mettere tutto a posto.

Inizia con il cliente

Il proprietario del tuo prodotto o il cliente ha una nuova funzionalità che desidera installare. Fanno del loro meglio per descrivere cosa significa e credi di avere una buona idea. A questo punto inizi a pensare a come incorporarlo nel progetto.

  • Se fai il BDD, il tuo team dovrebbe scrivere le specifiche nelle istruzioni Given / When / Then. Una volta che il tuo cliente è soddisfatto di tali affermazioni, il tuo team di test inizierebbe a darsi da fare rendendo questi eseguibili.
  • Esegui un design di alto livello per decidere quali pezzi hanno quali responsabilità nella tua applicazione. Non hai bisogno di entrare in minutia, ma hai bisogno di una buona idea di ciò che deve essere fatto.

Un po 'di Bottom Up e Top Down

Continua e scrivi un test di integrazione. Inizia con il "percorso felice" (vale a dire cosa dovrebbe succedere quando non ci sono errori). Fallirà perché non è ancora stato scritto nulla. Va bene.

Mentre lavori su specifiche unità di codice che a loro volta saranno necessarie per far passare il test di integrazione, scrivi test unitari. Non modificare il codice di produzione senza test di unità che testano l'implementazione . Scrivi solo una quantità sufficiente per soddisfare il test di integrazione.

Ora puoi scrivere il tuo prossimo test di integrazione e lavorare sui test delle unità man mano che procedi. Con un buon design, avrai bisogno di meno test di nuove unità per gestire ogni nuovo test di integrazione.

Un'analogia

Quando stavo imparando come sono state fatte e mantenute le katanas, il modo in cui la persona che ha lucidato la lama ha funzionato perfettamente in questo scenario. Il lucidatore esamina sempre l'intera lama quando sta lavorando su una piccola parte di essa. L'obiettivo è assicurarsi che la geometria della lama e il livello di lucidatura siano coerenti dalla base alla punta.

I tuoi test di integrazione sono come raccogliere il blade per esaminarne la lunghezza per assicurarti che ciò che stai facendo sia coerente con il design del blade.

I tuoi test unitari sono come lavorare localmente per correggere le imperfezioni in una piccola area.

Lavorano insieme.

    
risposta data 17.04.2018 - 16:26
fonte
2

Penso che la tua domanda riguardi di più quando esegui un test di integrazione invece di un test unitario.

Now that's enough to make the first test pass, so I'll stop there and make another test to prove that when the email is not valid it should not be inserted in database, and it should return a 422 response code

Penso che potresti fermarti qui con il test di integrazione.

Una buona pratica che ho usato è fare il "percorso felice" con un test di integrazione, solo per essere sicuro che tutto il mio codice (servizi, controllori, repository, entità, database, ecc) funzionino bene insieme per il più semplice Astuccio. Questo garantisce che le iniezioni di dipendenza siano OK, che qualsiasi dato inaspettato sia salvato sul database, ecc.

Per quanto riguarda l'e-mail non valida, potresti creare un test unitario per questo caso, testare e-mail diverse e vedere se le e-mail previste sono OK e quelle sbagliate stanno restituendo false o un'eccezione (dipende dal tuo codice).

Se ci si preoccupa che l'e-mail non valida non possa essere salvata sul database, non è possibile verificarla anche con un test di unità. Ad esempio: puoi prendere in giro il ritorno della convalida dell'e-mail e verificare se il metodo save è stato chiamato o meno.

Per fare TDD in questo scenario, inizi a creare una classe che sarà responsabile di queste due (o più) cose: convalida e salvataggio delle chiamate.

    
risposta data 17.04.2018 - 15:28
fonte

Leggi altre domande sui tag