Come si rileva Stubs / Mock mentali?

0

Considera questo pezzo di codice dal manuale PHPUnit (sto usando PHP solo come esempio):

class StubTest extends PHPUnit_Framework_TestCase
{
    public function testStub()
    {
        // Create a stub for the SomeClass class that could be later injected into another class, but removed here for simplicity.
        $stub = $this->getMock('SomeClass');

        // Configure the stub.
        $stub->expects($this->any())
             ->method('doSomething')
             ->will($this->returnValue('foo'));

        // Calling $stub->doSomething() will now return 'foo'.
        $this->assertEquals('foo', $stub->doSomething());
    }
}

Ora ho appena soppresso il metodo doSomething() per restituire foo .

Che cosa succede se qualche tempo dopo in futuro decido di implementare SomeClass in questo modo:

class SomeClass{

    function doSomething(){
        return 'bar';
    }
}

Ovviamente ho test per SomeClass che testano per bar , ma ho completamente dimenticato di testare per foo .

Ora ho un test di menzogna. In che modo TDD gestisce questa situazione?

    
posta Songo 18.05.2014 - 15:20
fonte

1 risposta

3
// Calling $stub->doSomething() will now return 'foo'.
$this->assertEquals('foo', $stub->doSomething());

Questo qui è in realtà la parte chiave del codice che rappresenta i seguenti due problemi:

  • Nel modo in cui stai implementando i test, stai testando i tuoi test, non testando il tuo codice ,
  • Se hai bisogno di quel livello di specificità, sei test di implementazione, non di interfaccia .

Affermando che l'output di uno stub è quello che hai detto dovrebbe essere esercitato la funzionalità della funzionalità di stub del framework di test piuttosto che qualsiasi cosa nel codice. Dubito che tu abbia linee reali come sopra nei tuoi test, ma, in modo subdolo, probabilmente hai dei test che equivalgono allo stesso se stai facendo la domanda che stai chiedendo.

Diciamo che abbiamo un test di esempio, che userò per spiegare:

// A Doubler takes the result of SomeClass#doSomething and doubles it.
class DoublerTest extends PHPUnit_Framework_TestCase
{
    public function testToString()
    {

        // Create a stub for the SomeClass
        $stub = $this->getMock('SomeClass');

        // Configure the stub.
        $stub->expects($this->any())
             ->method('doSomething')
             ->will($this->returnValue('foo'));

        $doubler = new Doubler($stub);

        // Calling $doubler->toString() will now return 'foofoo'.
        $this->assertEquals('foofoo', $doubler->toString());
    }
}

Un Doubler ha una semplice funzionalità: raddoppia il valore di ritorno del metodo doSomething dell'iniezione SomeClass . Ciò che è importante notare è che non ho specificato quale percentuale diSomeClass#doSomething è necessario restituire, poiché è irrilevante per l'algoritmo. Se restituisce "foo" o "bar" , il metodo funziona allo stesso modo. Il test fornisce comunque informazioni utili e valide.

Ora puoi affermare che, nel tuo caso, il valore di ritorno cambia il risultato dell'algoritmo:

// Doubler also doubles 'r's in the output string
// ... setup the stub, return "bar" ...
$this->assertEquals('barrbarr', $doubler->toString());

// If Doubler sees a 'q', party time! (return "PartyParty")
// ... setup the stub, return "qux" ...
$this->assertEquals('PartyParty', $doubler->toString());

Questo dovrebbe essere evidente nelle specifiche della classe Doubler (che si usa per guidare la costruzione dei test in TDD), e dovrebbe essere già testata, poiché rappresenta un percorso di codice in quella classe. Uno strumento di copertura del codice renderà facile individuare i percorsi del codice se ne manchi uno (e ce n'è uno integrato in PHPUnit). Pertanto, la modifica dell'implementazione di SomeClass#doSomething è ancora irrilevante per il test.

Se cambi l'interfaccia di SomeClass in un modo che non segue un percorso di codice preesistente, troverai comunque che devi scrivere un nuovo codice nelle classi dipendenti, che è il punto in cui cambierai i test (compresi gli stub) per quel codice, riportandolo in allineamento con la tua interfaccia.

I test unitari hanno lo scopo di testare le interfacce piuttosto che le implementazioni. Se il tuo codice dipende da dettagli di implementazione come l'esatto valore di ritorno di un metodo oltre il suo tipo (o al di là di altri fattori generali), la scrittura e l'esecuzione di test di integrazione molto probabilmente troveranno questi tipi di problemi. Idealmente, il tuo codice non dipenderà dai dettagli di implementazione e potresti usare i modelli ei principi di progettazione OO per isolare quei dettagli, ma non viviamo in un mondo ideale.

    
risposta data 18.05.2014 - 18:36
fonte

Leggi altre domande sui tag