I test case fanno tutto il lavoro con il metodo helper: cattive pratiche?

6

Considera una suite di test come questa:

public class MyTestSuite {
   @Test
   public void test_something() {
      testHelperMethod("value11", "value12", "value13");
   }

   @Test
   public void test_something_else_meaningful_name() {
      testHelperMethod("othervalue21", "othervalue22", "othervalue23");
   }

   // ...

   private void testHelperMethod(String value1, String value2, String value3) {
       // set up part (independent of value1, value2, value3)

       // action part (dependent on values)

       // assertion part (dependent on values)

       // tear down part (independent of values)
   }
}

In altre parole, tutti i casi di test sono eseguiti tramite un singolo metodo di supporto parametrizzato, che è strutturato secondo lo schema arrangement-act-assert. I test case chiamano questo metodo con vari parametri, in base a cosa esattamente deve essere testato.

Domanda : qual è lo svantaggio, se esiste, di strutturare i test come questo, e questo (anti?) - pattern ha un nome comune?

Commento

  1. Ho provato a utilizzare Google per un lungo periodo, tra cui su SO e sul blog , ma non sono riuscito a trovare nulla di utile fino ad ora.

  2. Questo domanda è la più vicina che ho trovato, ma le risposte qui affrontano altri aspetti / problemi, ovvero:

    • asserzioni / codice di installazione mescolato (non è il caso qui, come tutto le asserzioni sono alla fine del metodo helper)
    • più asserzioni per test (anche nel mio esempio, ma penso che questo sia un problema non correlato)
    • la responsabilità del metodo helper non è chiara: penso al mio case è chiaro, solo che è diverso da quello "tradizionale"
  3. Per essere più precisi, la parte di asserzione in testHelperMethod è anche un metodo di utilità separato (chiamato con molti parametri), ma immagino che non sia molto importante per la domanda. (I.e: perché è una cattiva pratica delegare i test ai metodi di supporto, se non del tutto?)

  4. Nel caso questo sia importante, si tratta di un test di integrazione, ma sospetto che la risposta sia la stessa anche per i test unitari.

EDIT : ha cambiato il nome dei casi di test per evitare confusione (in precedenza erano chiamati test1 , test2 . Infatti, i test nel progetto nelle domande hanno nomi significativi , Avevo appena realizzato questa semplificazione.)

    
posta Attilio 09.03.2016 - 19:16
fonte

4 risposte

3

Come altri hanno già affermato: niente di sbagliato nell'allontanare un metodo separato per evitare la duplicazione.

Tuttavia, come altri hanno anche affermato: vuoi che ogni singolo test indichi chiaramente cosa sta configurando e cosa sta affermando.

I nomi dei test ti aiuteranno enormemente e presumo che il tuo codice attuale abbia nomi più significativi di Test1 e Test2 .

Per rendere il codice dei singoli test più "intelligibile", ossia rendere più chiaro ciò che ogni test sta facendo semplicemente leggendo il codice di quel test, puoi suddividere il tuo metodo di supporto nelle sue parti ovvie.

Sì, "duplicherà" ciò che altrimenti sarebbe limitato al singolo helper poiché ora avrai un numero di chiamate in ogni test quando potresti farlo con uno, ma ti permetterà di dare nomi più significativi a ciascuno metodo di supporto.

// Arrange
someHelper = MakeSomeDependency(arg1, arg2, arg3);
sut = MakeSomeObjectUnderTest(someHelper);

// Act
sut.DoSomething();

// Assert
AssertCommonAsserts(sut, expectedvalue1, expectedvalue2);
Assert.AreEqual(sut.SomeProperty, expectedValue);

Nota: non sono un fan di più affermazioni in un singolo test, ma ci sono casi in cui un singolo "fatto" può essere controllato solo da più affermazioni e posso anche pensare a una serie di scenari in cui avere un po 'di " guardia "asserisce che l'effettivo" fatto "asserito può aiutare nel debug dei guasti.

Se utilizzi un linguaggio che supporta i parametri denominati, puoi rendere i tuoi test ancora più comprensibili utilizzando tale funzione, in modo che la chiamata al metodo non trasmetta soltanto i valori utilizzati, ma anche i valori utilizzati per tali valori.

// Arrange
someHelper = MakeSomeDependency(
    knownNames: arg1, 
    unknownNames: arg2, 
    lookingFor: arg3);
sut = MakeSomeObjectUnderTest(someHelper);

// Act
sut.DoSomething();

// Assert
AssertCommonAsserts(
    objectUnderTest: sut, 
    numberOfItemsFound: expectedvalue1, 
    nameOfItemFound: expectedvalue2);
Assert.AreEqual(sut.SomeProperty, expectedValue);
    
risposta data 10.03.2016 - 12:55
fonte
1

Non c'è niente di sbagliato nella strutturazione del codice di test e del codice aziendale. Il punto di una suite di test è che esprime chiaramente come dovrebbe comportarsi il codice testato e che segnala un errore se non lo fa. Con refactoring giudizioso e nomi scelti con cura nel tuo codice di test, puoi raggiungere questo obiettivo migliore rispetto a drudgery cut-and-paste.

(Tuttavia, Matthew ha perfettamente ragione: il codice di set-up e di rimozione comune ai test all appartiene ai metodi speciali del framework, non alle subroutine personalizzate.)

    
risposta data 09.03.2016 - 19:59
fonte
0

Devi assolutamente prendere in considerazione il codice comune nei test unitari. Il codice di test unitario, disordinato e mal strutturato, porta a test difficili da comprendere e difficili da mantenere, a prescindere da quanto sia bello il codice che stanno testando.

Detto questo, in questo caso particolare se si utilizza il framework di test .NET NUnit questo non sarebbe affatto necessario - si trasformerebbe l'helper in un metodo di test e si userà l'attributo TestCase per ottenere il framework chiamarlo più volte con set di parametri diversi. Sarei sorpreso se non ci fosse qualcosa di equivalente disponibile per Java e altre lingue.

Quindi esegui bene i test delle unità di struttura, ma assicurati anche di aver appreso le funzionalità del framework di test perché potrebbe essere in grado di aiutarti.

    
risposta data 10.03.2016 - 09:42
fonte
0

Dovresti prendere in considerazione l'utilizzo di un Runner parametrizzato che utilizza un DataProvider per tutti gli input validi per i tuoi casi di test specifici, anziché l'aproach superiore. Uso i metodi di supporto solo per aumentare la leggibilità dei test, se hanno una configurazione complessa nel test e quali potrebbero ingombrare la parte organizzativa del test.

Quello che potrebbe sembrare è:

@RunWith( DataProviderRunner.class )
public class YourClassTest {

    @Test
    @DataProvider( value = { "value11|value12|value13","value21|value22|value23" }, 
                   trimValues = true, splitBy = "\|" )
    public void test_something(String value1, String value2, String value3) {
       // action part (dependent on values)

       // assertion part (dependent on values)
    }


    @Before
    public void independendSetup() {
       // set up part (independent of value1, value2, value3)
    }

    @After
    public void independendtearDown() {
       // tear down part (independent of values)
    }
}
    
risposta data 13.03.2016 - 21:34
fonte

Leggi altre domande sui tag