È importante come ho impostato i dati di test durante la creazione dei test unitari?

1

Ho un test unitario simile allo snippet di codice qui sotto, dovrebbe verificare che il metodo AddUser consenta solo e-mail univoche.

La mia domanda riguarda la parte Arrange di questo test unitario, uso il codice di sistema esistente per impostare il primo utente (classe UserLogic ), questo è così che ho un utente nel contesto per eseguire le parti successive di il test ( Act e Arrange ).

[Fact]
public void CheckUniqueEmail()
{
    var context = new DbContext(); //EF Core in memory db

    //Arrange
    UserLogic userlogic = new UserLogic(context);
    User user = new User('[email protected]');

    userlogic.AddUser(user);

    //Act
    UserLogic userlogicNew = new UserLogic(context);
    User userNew = new User('[email protected]');

    bool result = userlogicNew.AddUser(userNew); //result should be false since this email has already been used

    //Assert
    Assert.False(result);
}

Tuttavia, ho visto questo fatto in due modi: il primo è come ho fatto sopra. Il secondo sarebbe inserire i dati direttamente nel contesto, come nel prossimo esempio

[Fact]
public void CheckUniqueEmail()
{
var context = new DbContext(); //EF Core in memory db

//Arrange
context.Users.Add(new User({Email='[email protected]'}))
context.SaveChanges();    

//Act
UserLogic userlogicNew = new UserLogic(context);
User userNew = new User('[email protected]');

bool result = userlogicNew.AddUser(userNew); //result should be false since this email has already been used

//Assert
Assert.False(result);
}

Sulla base di quanto precede, è importante nel modo in cui organizzo i dati per i test unitari? Quale dei due approcci ritieni appropriato per i test unitari?

    
posta user1786107 08.02.2018 - 10:43
fonte

2 risposte

1

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.

    
risposta data 08.02.2018 - 19:38
fonte
1

Dovresti racchiudere le chiamate del framework di entità in una classe repository che implementa un'interfaccia.

È quindi possibile iniettare un repository finto nella classe userlogic. con l'utente appropriato già esistente ed evitare di avere una dipendenza dal database.

Un'alternativa per cui si hanno complicate dipendenze dei dati consiste nell'utilizzare le istantanee del database per creare un database completamente nuovo popolato con dati fittizi per un singolo test

IUserRepo
{
    User GetUser(string id);
    void AddUser(User user);
}

MyTest()
{
    //set up mock with the framework of your choice
    IUserRepo repo = new MockUserRepo();
    repo.GetUser = ('[email protected]') => { return new User('[email protected]'); };

    var userLogic = new UserLogic(repo)
    var user = new User('[email protected]');
    var actual = userLogic.AddUser(user);
    Assert.IsFalse(actual);
}
    
risposta data 09.02.2018 - 09:14
fonte

Leggi altre domande sui tag