Il modo in cui configuri i dati di test è importante, e direi che entrambe le versioni sono subottimali.
Ogni metodo o classe che scrivi ha un contratto, sia esso esplicito (per esempio nella documentazione) o implicito (in base a ciò che effettivamente fa il codice). Questo contratto è importante, perché descrive ciò che i clienti, cioè il codice che utilizza la tua classe, dovrebbero aspettarsi quando lo usa. Il test delle unità è un metodo per documentare a livello di codice il contratto del codice sottoposto a test, in modo da garantire che il comportamento sia lo stesso anche se l'implementazione cambia.
Una caratteristica importante dei test unitari è che vogliono che il codice sotto test (CUT) sia isolato da altro codice. Ciò significa che devi stare molto attento quando il CUT ha dipendenze. Se usa una dipendenza che ha una diversa ragione di cambiare rispetto a se stessa (questo è ciò che significa responsabilità nel singolo principio di responsabilità), di solito si ha un'astrazione per isolare questi due. Questa astrazione stessa ha un contratto, e in un test unitario, si assume che l'astrazione da cui si dipende si comporti in base al proprio contratto. Nei test unitari, questo in genere significa che questa dipendenza sarà derisa , e si pilota la simulazione per comportarsi in un certo modo.
Torna al tuo esempio ora. Stai testando la classe UserLogic
. Ha due dipendenze che posso vedere: User
e DbContext
. Non ho abbastanza contesto per sapere cos'è User
, ma suppongo che sia una sorta di oggetto valore. In tal caso, va bene utilizzarlo direttamente.
DbContext
è una bestia diversa. Sembra essere un'implementazione di una sorta di persistenza. Sicuramente ha un motivo diverso per cambiare rispetto a UserLogic
, il che significa che dovrebbe essere estratto da un'interfaccia di qualche tipo. Immagino che ne abbia già uno chiamato Context
.
Pertanto, posso supporre che l'implementazione di UserLogic.AddUser
assomigli a qualcosa del genere:
public boolean AddUser(User user) {
if (context.HasUser(user)) {
return false;
}
context.AddUser(user);
context.SaveChanges();
return true;
}
Il risultato che desideri sottoporre a test unitario è il seguente: Se User
è già stato aggiunto a context
, assicurati che Context
non sia cambiato (nessun nuovo utente è stato aggiunto, né sono state salvate le modifiche).
La descrizione del risultato descrisse praticamente esattamente come dovrebbe apparire il test unitario. Quello che vuoi organizzare è che context
abbia già uno User
specifico. Quello su cui vuoi agire è AddUser
. Quello che vuoi affermare è che User
non è stato aggiunto e Context
non è stato salvato. Pertanto, il tuo test unitario assomiglia a questo (in Java, non ho molta familiarità con le librerie di test C #):
@Test
public void givenContextAlreadyHasTheUser_whenAddUser_thenTheUserIsNotAddedASecondTime() {
// Arrange
Context context = mock(Context.class);
User user = new User("[email protected]");
UserLogic userLogic = new UserLogic(context);
given(context.HasUser(user)).willReturn(true);
// Act
userLogic.AddUser(user);
// Assert
verify(context, never()).AddUser(any());
verify(context, never()).SaveChanges();
}
Come utente della classe UserLogic
, posso fare riferimento a questo test per sapere esattamente che cosa descrive il contratto di AddUser
nel caso in cui User
sia già stato aggiunto al contesto, che è ciò che I ' sto cercando nei test unitari.