Devo testare i metodi ereditati?

28

Supponiamo che io abbia una classe Manager derivata da una classe base Dipendente e che Employee abbia un metodo getEmail () ereditato da Gestione . Devo verificare che il comportamento del metodo getEmail () di un gestore sia in realtà lo stesso di quello di un dipendente?

Nel momento in cui questi test sono scritti, il comportamento sarà lo stesso, ma di Certo, a un certo punto in futuro qualcuno potrebbe scavalcare questo metodo, cambiare il suo comportamento e quindi rompere la mia domanda. Tuttavia sembra un po 'strano testare essenzialmente l' assenza del codice di intromissione.

(Tieni presente che testare il metodo Manager :: getEmail () non migliora la copertura del codice (o qualsiasi altra metrica di qualità del codice (?)) fino a Manager :: getEmail () viene creato / sostituito.)

(Se la risposta è "Sì", potrebbero essere utili alcune informazioni su come gestire i test condivisi tra classi di base e derivate.)

Una formulazione equivalente della domanda:

Se una classe derivata eredita un metodo da una classe base, come esprimi (test) se stai aspettando che il metodo ereditato sia:

  1. Comportarsi esattamente nello stesso modo che fa la base adesso (se il comportamento della base cambia, il comportamento del metodo derivato no);
  2. Comportarsi esattamente allo stesso modo della base per sempre (se il comportamento della classe base cambia, cambia anche il comportamento della classe derivata); o
  3. Comportati comunque (non ti interessa il comportamento di questo metodo perché non lo chiami mai).
posta mjs 05.03.2013 - 16:11
fonte

7 risposte

23

Qui prenderei l'approccio pragmatico: se qualcuno, in futuro, sostituisce Manager :: getMail, allora è la responsabilità dello sviluppatore fornire il codice di prova per il nuovo metodo.

Ovviamente è valido solo se Manager::getEmail ha lo stesso percorso di codice di Employee::getEmail ! Anche se il metodo non è sovrascritto, potrebbe comportarsi in modo diverso:

  • Employee::getEmail potrebbe chiamare un% virtuale protettogetInternalEmail che è sovrascritto in Manager .
  • Employee::getEmail potrebbe accedere ad alcuni stati interni (ad esempio alcuni campi _email ), che possono differire nelle due implementazioni: ad esempio, l'implementazione predefinita di Employee potrebbe garantire che _email sia sempre [email protected] , ma Manager è più flessibile nell'assegnazione degli indirizzi di posta.

In questi casi, è possibile che un bug si manifesti solo in Manager::getEmail , anche se l'implementazione del metodo stesso è la stessa. In tal caso, testare Manager::getEmail separatamente potrebbe avere senso.

    
risposta data 05.03.2013 - 16:19
fonte
14

Lo farei.

Se stai pensando "bene, in realtà si chiama solo Employee :: getEmail () perché non lo sovrascrivo, quindi non ho bisogno di testare Manager :: getEmail ()", quindi non sei davvero testare il comportamento di Manager :: getEmail ().

Penserei a cosa dovrebbe fare solo Manager :: getEmail (), non se sia ereditato o sovrascritto. Se il comportamento di Manager :: getEmail () dovrebbe essere quello di restituire qualsiasi cosa Employee :: getMail () restituisce, allora questo è il test. Se il comportamento è di restituire "[email protected]", allora questo è il test. Non importa se è implementato per ereditarietà o sovrascritto.

Ciò che importa è che, se cambia in futuro, il tuo test lo rileva e sai che qualcosa si è rotto o deve essere riconsiderato.

Alcune persone potrebbero non essere d'accordo con l'apparente ridondanza nel controllo, ma il mio contro sarebbe che stai testando il comportamento Employee :: getMail () e Manager :: getMail () come metodi distinti, anche se non lo sono " re ereditato o sovrascritto. Se uno sviluppatore futuro deve modificare il comportamento di Manager :: getMail (), deve anche aggiornare i test.

Le opinioni possono variare, penso che Digger e Heinzi abbiano fornito giustificazioni ragionevoli per il contrario.

    
risposta data 05.03.2013 - 16:38
fonte
5

Non testare l'unità. Fai un test funzionale / di accettazione.

I test unitari dovrebbero testare ogni implementazione, se non si sta fornendo una nuova implementazione quindi attenersi al principio DRY. Se vuoi fare uno sforzo qui, puoi migliorare il test dell'unità originale. Solo se esegui l'override del metodo dovresti scrivere un test unitario.

Allo stesso tempo, i test di funzionalità / accettazione dovrebbero fare in modo che alla fine del giorno tutto il tuo codice faccia quello che dovrebbe e, si spera, catturi qualsiasi stranezza dall'ereditarietà.

    
risposta data 05.03.2013 - 21:03
fonte
4

Le regole di Robert Martin per TDD sono:

  1. Non sei autorizzato a scrivere alcun codice di produzione a meno che non si debba effettuare un passaggio di prova dell'unità in errore.
  2. Non è consentito scrivere più di un test unitario di quanto sia sufficiente per fallire; e gli errori di compilazione sono errori.
  3. Non ti è permesso scrivere più codice di produzione di quanto sia sufficiente per superare il test dell'unità fallita.

Se segui queste regole, non potresti essere in grado di porre questa domanda. Se il metodo getEmail deve essere testato, sarebbe stato testato.

    
risposta data 05.03.2013 - 22:31
fonte
2

Should I test that the behaviour of a manager's getEmail() method is in fact the same as an employee's?

Direi di no in quanto sarebbe un test ripetuto, a mio avviso, testerei una volta nei test dei dipendenti e sarebbe quello.

At the time these tests are written the behaviour will be the same, but of course at some point in the future someone might override this method, change its behaviour

Se il metodo è sovrascritto, sono necessari nuovi test per verificare il comportamento sottoposto a override. Questo è il compito della persona che implementa il metodo di sovrascrittura getEmail() .

    
risposta data 05.03.2013 - 16:18
fonte
2

Sì, dovresti provare i metodi ereditati perché in futuro potrebbero essere sovrascritti. Inoltre, i metodi ereditati possono chiamare metodi virtuali che sono sovrascritti , il che cambierebbe il comportamento del metodo ereditato non sovrascritto.

Il modo in cui lo collaudo è creando una classe astratta per testare la (o astratta) base di base o l'interfaccia, come questa (in C # usando NUnit):

public abstract class EmployeeTests
{
    protected abstract Employee CreateInstance(string name, int age);

    [Test]
    public void GetEmail_ReturnsValidEmailAddress()
    {
        // Given
        var sut = CreateInstance("John Doe", 20);

        // When
        string email = sut.GetEmail();

        // Then
        Assert.IsTrue(Helper.IsValidEmail(email));
    }
}

Quindi, ho una classe con test specifici per Manager , e integro i test del dipendente in questo modo:

[TestFixture]
public class ManagerTests
{
    // Other tests.

    [TestFixture]
    public class ManagerEmployeeTests : EmployeeTests
    {
        protected override Employee CreateInstance(string name, int age);
        {
            return new Manager(name, age);
        }
    }
}

Il motivo per cui posso farlo è il principio di sostituzione di Liskov: i test di Employee devono ancora passare quando viene passato un oggetto Manager , poiché deriva da Employee . Quindi devo scrivere i miei test solo una volta, e posso verificare che funzionino per tutte le possibili implementazioni dell'interfaccia o della classe base.

    
risposta data 05.03.2013 - 23:31
fonte
1

No, non è necessario testare metodi ereditati. Le classi e i loro casi di test che si basano su questo metodo si interromperanno comunque se il comportamento è cambiato in Manager .

Pensa al seguente scenario: l'indirizzo email è assemblato come [email protected]:

class Employee{
    String firstname, lastname;
    String getEmail() { 
        return firstname + "." + lastname + "@example.com";
    }
}

Hai testato l'unità e funziona correttamente con Employee . Hai anche creato una classe Manager :

class Manager extends Employee { /* nothing different in email generation */ }

Ora hai una classe ManagerSort che ordina i gestori in un elenco in base al loro indirizzo email. Di certo, supponiamo che la generazione di email sia la stessa di Employee :

class ManagerSort {
    void sortManagers(Manager[] managerArrayToBeSorted)
        // sort based on email address omitted
    }
}

scrivi un test per il tuo ManagerSort :

void testManagerSort() {
    Manager[] managers = ... // build random manager list
    ManagerSort.sortManagers(managers);

    Manager[] expected = ... // expected result
    assertEquals(expected, managers); // check the result
}

Tutto funziona bene. Ora arriva qualcuno e sostituisce il metodo getEmail() :

class Manager extends Employee {
    String getEmail(){
        // managers should have their lastname and firstname order changed
        return lastname + "." + firstname + "@example.com";
    }
}

Ora, cosa succede? Il tuo testManagerSort() fallirà perché il getEmail() di Manager è stato sovrascritto. Esaminerai in questo numero e troverai la causa. E tutto senza scrivere una testcase separata per il metodo ereditato.

Pertanto, non è necessario testare i metodi ereditati.

Altrimenti, ad es. in Java, dovresti testare tutti i metodi ereditati da Object come toString() , equals() ecc. in ogni classe.

    
risposta data 06.03.2013 - 13:28
fonte

Leggi altre domande sui tag