Si applica la regola "una asserzione per test" per verificare le condizioni iniziali?

5

Ho riflettuto sulle best practice del test unitario e ho riscontrato la regola one assertion per unit test . Riesco a vedere dove questa idea potrebbe aiutare a isolare parti di operazioni complesse o di verifica quando si dispone di codice in questo modo:

private Place place;

@Test
public void test_whiteHouse(){
   place = new place(KnownPlaces.WHITEHOUSE);
   assertEquals("1600 Pennsylvania Avenue", place.getAddress());
   assertEquals("Washington", place.getCity());
   assertEquals("DC", place.getState());
}

Vedo dove vorresti in genere dividerlo in test più piccoli come questo:

private Place place;

@Before
public void setUp(){
   place = new place(KnownPlaces.WHITEHOUSE);
}

@Test
public void test_whiteHouse_street(){
   assertEquals("1600 Pennsylvania Avenue", place.getAddress());
}

@Test
public void test_whiteHouse_city(){
   assertEquals("Washington", place.getCity());
}

@Test
public void test_whiteHouse_state(){
   assertEquals("DC", place.getState());
}

Tuttavia, se sto testando un setter, il codice potrebbe apparire come questo:

@Test
public void test_place_setCity(){
   String newCity = "timbuktu";
   Place place = new Place();
   place.setCity(newCity);
   assertEquals(newCity, place.getCity());
}

Il test funzionerà correttamente, ma se c'è una configurazione condivisa, sto facendo delle ipotesi sullo stato iniziale. Quale sarebbe il rovescio della medaglia su come eseguire un controllo sullo stato iniziale qualcosa come:

@Test
public void test_place_setCity(){
   String newCity = "timbuktu";
   Place place = new Place();
   assertFalse(place.getCity().equals(newCity));
   place.setCity(newCity);
   assertEquals(newCity, place.getCity());
}

Questo controllo verificherebbe che il metodo setter effettivamente modificato qualcosa verificando lo stato iniziale fosse diverso dallo stato finale. In caso contrario, un cambiamento o un malfunzionamento in un costruttore o in un metodo di installazione potrebbero rendere il test inefficace. Ci sono degli svantaggi di violare la regola di asserzione per vincolo in questo modo?

    
posta Joshc1107 08.11.2012 - 15:08
fonte

3 risposte

10

Abbiamo una politica in cui lavoro che utilizza una classe Assume (fornita da Nunit) per tutti questi tipi di controlli iniziali. Inoltre, abbiamo creato un wrapper attorno alla classe di assunzione che costringe lo scrittore di test a fare riferimento a un altro test che verifica esplicitamente l'ipotesi. Penso di poterlo descrivere meglio con il codice:

@Test
public void test_place_setCity(){
   // Arrange
   String newCity = "timbuktu";
   Place place = new Place();
   Assume.That(place.getCity(), Is.Not.EqualTo(newCity), validatingTest:"test_new_place_should_not_have_city_set");

   // Act
   place.setCity(newCity);

   // Assert
   assertEquals(newCity, place.getCity());
}

@Test
public void test_new_place_should_not_have_city_set(){
   // Arrange
   // Nothing to Arrange

   // Act
   Place place = new Place();

   // Assert
   Assert.That(place.getCity(), Is.Empty, message:"New cities should not have a city initialized");
}

Quindi, nel nostro test test_place_setCity , chiamiamo Assume e specificiamo il nome del test che DOVREBBE fallire se l'assunzione fallisce. La classe di assunzione di NUnit restituisce un risultato "inconcludente" anziché "non riuscito" quando la condizione non riesce. Penso che questi pre-controlli siano importanti per molti aspetti. Hanno lasciato che i futuri manutentori sappiano CHE COSA IL TEST SI ASSUME di passare in prima fila in un modo facile da capire, FAIL FAST quando l'ipotesi non regge perché sono in cima dei test e danno RISULTATI SEPARATI per scopi di reporting e per eliminare il rumore. Il nome di un altro test è anche un bonus per il debug. Invece di provare a eseguire il debug perché un test non verrà eseguito perché un'ipotesi non riesce, puoi invece navigare facilmente al test che si concentra sull'assunzione e sul debug utilizzando il test più mirato.

Si spera che tu possa trovare qualcosa nel tuo framework di test per raggiungere tutti questi ideali, oppure eseguire il rollover di te stesso. Abbiamo usato questa metodologia per un po 'e si è dimostrato molto utile.

    
risposta data 08.11.2012 - 15:56
fonte
7

Non lo definirei una regola, ma piuttosto una linea guida del passato. Il comune equivoco riguarda ciò che significa "una sola affermazione". Sfortunatamente, alcune persone tendono a piegarlo verso "un assert metodo call" senza pensarci davvero. Questo è in qualche modo comprensibile, dopo tutto una delle prime linee guida relative al testing delle unità probabilmente sentirai che "test delle unità dovrebbe essere atomico, piccolo e testare una cosa " .

Tuttavia, one assertion è più circa concept logico che numero del metodo chiama in prova. Nel tuo esempio, se tutti i getter forniscono un valore semplice che ha poco o nessun senso esistente al di fuori della sua entità, allora tutti e 3 si riferiscono a un concetto logico: la tua entità. Se il getter produce un valore più complesso, potrebbe valere la pena di avere un proprio test. È sempre un giudizio.

Il codice nel tuo esempio usa un modello ben noto chiamato guardia assertion , e non è niente sbagliato con quello. Tuttavia, tieni presente che l'asserzione della guardia potrebbe essere sempre sostituita da un ulteriore test unitario, come indicato qui .

    
risposta data 08.11.2012 - 16:47
fonte
0

Nel tuo esempio, non farei un'asserzione sullo stato dell'oggetto prima di chiamare il setter. Questo è al di fuori dello scopo di ciò che stai testando. Questo avrebbe dovuto essere controllato nei test di inizializzazione. Il test setter / getter sta controllando solo quella funzionalità in modo tale che sarebbe l'unica asserzione di cui hai bisogno.

    
risposta data 08.11.2012 - 16:45
fonte

Leggi altre domande sui tag