Java >> Programma Java >  >> Java

Sviluppo basato su test con Mockito

In questo esempio impareremo come eseguire una T est D riven D sviluppo (TDD) utilizzando Mockito. Un test unitario dovrebbe testare una classe in isolamento. Gli effetti collaterali di altre classi o del sistema dovrebbero essere eliminati, se possibile. Mockito ti consente di scrivere bellissimi test con un'API semplice e pulita. Gli strumenti e le tecnologie utilizzati in questo esempio sono Java 1.8, Eclipse Luna 4.4.2

1. Introduzione

Mockito è un popolare framework di derisione che può essere utilizzato insieme a JUnit. Mockito ci consente di creare e configurare oggetti fittizi. L'uso di Mockito semplifica notevolmente lo sviluppo di test per classi con dipendenze esterne. Possiamo creare gli oggetti fittizi manualmente o possiamo usare i framework beffardi come Mockito, EasyMock. jMock ecc. I framework Mock ci consentono di creare oggetti fittizi in fase di esecuzione e definirne il comportamento. L'esempio classico per un oggetto fittizio è un fornitore di dati. In produzione viene utilizzato un database reale, ma per il test un oggetto fittizio simula il database e garantisce che le condizioni di test siano sempre le stesse.

2. Sviluppo basato su test

Test-Driven Development (TDD) è un approccio evolutivo allo sviluppo. Offre uno sviluppo test-first in cui il codice di produzione viene scritto solo per soddisfare un test. TDD è il nuovo modo di programmare. Qui la regola è molto semplice; è il seguente:

  1. Scrivi un test per aggiungere una nuova funzionalità (automatizzazione dei test).
  2. Scrivi il codice solo per soddisfare i test.
  3. Esegui nuovamente i test:se un test non funziona, annulla la modifica.
  4. Refactoring e assicurati che tutti i test siano verdi.
  5. Continua con il passaggio 1.

3. Creazione di un progetto

Di seguito sono riportati i passaggi necessari per creare il progetto.

  • Apri Eclipse. Vai a File=>Nuovo=>Progetto Java. In "Nome progetto" inserisci "TDDMockito".

Figura 1. Crea progetto Java

  • Eclipse creerà una cartella 'src'. Fare clic con il tasto destro sulla cartella 'src' e scegliere Nuovo => Pacchetto. Nella casella di testo "Nome" inserisci "com.javacodegeeks". Fai clic su "Fine".

Figura 2. Nuovo pacchetto Java

  • Fai clic con il pulsante destro del mouse sul pacchetto e scegli Nuova=>Classe. Assegna il nome della classe e fai clic su "Fine". Eclipse creerà una classe predefinita con il nome dato.

3.1 Dipendenze

Per questo esempio abbiamo bisogno dei vasetti junit e mockito. Questi vasi possono essere scaricati dal repository Maven. Stiamo usando "junit-4.12.jar" e "mockito-all-1.10.19.jar". Ci sono le ultime versioni (non beta) disponibili al momento. Per aggiungere questi jar nel percorso di classe, fai clic con il pulsante destro del mouse sul progetto e scegli Build Path=>Configure Build Path. Il clic sul pulsante "Aggiungi JAR esterni" sul lato destro. Quindi vai alla posizione in cui hai scaricato questi jar. Quindi fare clic su OK.

4. Prova prima

Diciamo che vogliamo costruire uno strumento per la generazione di Report. Tieni presente che questo è un esempio molto semplice di come usare mockito per TDD. Non si concentra sullo sviluppo di uno strumento di generazione di report completo.

Per questo avremo bisogno di tre classi. La prima è l'interfaccia che definirà l'API per generare il report. Il secondo è l'entità del report stesso e il terzo è la classe di servizio. Per prima cosa inizieremo con la scrittura del test.

Inietteremo la classe di servizio usando @InjectMocks.

@InjectMocks private ReportGeneratorService reportGeneratorService;

@InjectMocks contrassegna un campo su cui eseguire l'iniezione. Consente l'iniezione abbreviata di simulazione e spia. Mockito cercherà di iniettare mock solo tramite iniezione del costruttore, iniezione del setter o iniezione della proprietà nell'ordine e come descritto di seguito. Se una delle seguenti strategie fallisce, Mockito non segnalerà il fallimento, ovvero dovrai fornire tu stesso le dipendenze.

Iniezione del costruttore: viene scelto il costruttore più grande, quindi gli argomenti vengono risolti con mock dichiarati solo nel test. Se l'oggetto viene creato correttamente con il costruttore, Mockito non proverà le altre strategie. Mockito ha deciso di non corrompere un oggetto se ha un costruttore parametrizzato. Se non è possibile trovare argomenti, viene passato null. Se si desiderano tipi non derisi, l'iniezione del costruttore non avverrà. In questi casi, dovrai soddisfare tu stesso le dipendenze.

Iniezione di setter di proprietà: i mock verranno prima risolti in base al tipo (se un singolo tipo corrisponde all'iniezione avverrà indipendentemente dal nome), quindi, se sono presenti più proprietà dello stesso tipo, in base alla corrispondenza del nome della proprietà e del nome del mock. Se hai proprietà con lo stesso tipo (o la stessa cancellazione), è meglio nominare tutti i campi annotati da @Mock con le proprietà corrispondenti, altrimenti Mockito potrebbe confondersi e l'iniezione non avverrà. Se l'istanza @InjectMocks non è stata inizializzata prima e ha un costruttore no-arg, verrà inizializzata con questo costruttore.

Iniezione sul campo: i mock verranno prima risolti in base al tipo (se un singolo tipo corrisponde all'iniezione avverrà indipendentemente dal nome), quindi, se sono presenti più proprietà dello stesso tipo, in base alla corrispondenza del nome del campo e del nome del mock. Se hai campi con lo stesso tipo (o la stessa cancellazione), è meglio nominare tutti i campi annotati da @Mock con i campi corrispondenti, altrimenti Mockito potrebbe confondersi e l'iniezione non avverrà. Se l'istanza @InjectMocks non è stata inizializzata prima e ha un costruttore no-arg, verrà inizializzata con questo costruttore.

Ora prenderemo in giro l'interfaccia usando l'annotazione @Mock:

@Mock private IReportGenerator reportGenerator;

Ora definiremo l'argomento captor sull'entità del report:

@Captor private ArgumentCaptor<ReportEntity> reportCaptor;

La classe ArgumentCaptor viene utilizzata per acquisire i valori degli argomenti per ulteriori asserzioni. Mockito verifica i valori degli argomenti in stile java naturale:utilizzando un metodo equals(). Questo è anche il modo consigliato per abbinare gli argomenti perché rende i test semplici e puliti. In alcune situazioni, tuttavia, è utile affermare su determinati argomenti dopo la verifica effettiva.

Ora definiremo un metodo di installazione che annoteremo con @Before. Lo useremo per inizializzare i mock.

MockitoAnnotations.initMocks(this);

initMocks() inizializza gli oggetti annotati con le annotazioni Mockito per una data classe di test.

Nel metodo di test chiameremo il metodo generateReport() della classe ReportGeneratorService passando i parametri richiesti:

reportGeneratorService.generateReport(startDate.getTime(), endDate.getTime(), reportContent.getBytes());

Di seguito è riportato lo snippet dell'intera classe di test:

ReportGeneratorServiceTest.java

package com.javacodegeeks;

import static org.junit.Assert.assertEquals;

import java.util.Calendar;

import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

public class ReportGeneratorServiceTest {

  @InjectMocks private ReportGeneratorService reportGeneratorService;
  @Mock private IReportGenerator reportGenerator;
  @Captor private ArgumentCaptor<ReportEntity> reportCaptor;

  @Before
  public void setUp() {
    MockitoAnnotations.initMocks(this);
  }

  @SuppressWarnings("deprecation")
  @Test
  public void test() {
    Calendar startDate = Calendar.getInstance();
    startDate.set(2016, 11, 25);
    Calendar endDate = Calendar.getInstance();
    endDate.set(9999, 12, 31);
    String reportContent = "Report Content";
    reportGeneratorService.generateReport(startDate.getTime(), endDate.getTime(), reportContent.getBytes());

    Mockito.verify(reportGenerator).generateReport(reportCaptor.capture());

    ReportEntity report = reportCaptor.getValue();

    assertEquals(116, report.getStartDate().getYear());
    assertEquals(11, report.getStartDate().getMonth());
    assertEquals(25, report.getStartDate().getDate());

    assertEquals(8100, report.getEndDate().getYear());
    assertEquals(0, report.getEndDate().getMonth());
    assertEquals(31, report.getEndDate().getDate());

    assertEquals("Report Content", new String(report.getContent()));
  }

}

La classe di test non verrà compilata poiché qui mancano le classi richieste. Non preoccuparti perché è così che funziona TDD. Per prima cosa scriviamo il test, quindi costruiamo le nostre classi per soddisfare i requisiti del test.

Ora iniziamo ad aggiungere le classi. Per prima cosa aggiungeremo l'interfaccia. Questa è la stessa interfaccia che abbiamo preso in giro nella nostra classe di test. La classe di servizio farà riferimento a questa interfaccia.

IReportGenerator.java

package com.javacodegeeks;

/**
* Interface for generating reports.
* @author Meraj
*/
public interface IReportGenerator {

  /**
  * Generate report.
  * @param report Report entity.
  */
  void generateReport(ReportEntity report);

}

Si noti che anche questa interfaccia non verrà compilata poiché la classe ReportEntity è ancora mancante. Ora aggiungiamo la classe dell'entità. Questa classe rappresenta l'oggetto di dominio nel nostro design.

ReportEntity.java

package com.javacodegeeks;

import java.util.Date;

/**
* Report entity.
* @author Meraj
*/
public class ReportEntity {

  private Long reportId;
  private Date startDate;
  private Date endDate;
  private byte[] content;

  public Long getReportId() {
    return reportId;
  }

  public void setReportId(Long reportId) {
    this.reportId = reportId;
  }

  public Date getStartDate() {
    return startDate;
  }

  public void setStartDate(Date startDate) {
    this.startDate = startDate;
  }

  public Date getEndDate() {
    return endDate;
  }

  public void setEndDate(Date endDate) {
    this.endDate = endDate;
  }

  public byte[] getContent() {
    return content;
  }

  public void setContent(byte[] content) {
    this.content = content;
  }
}

Ora aggiungiamo la classe di servizio:

ReportGeneratorService.java

package com.javacodegeeks;

import java.util.Date;

/**
* Service class for generating report.
* @author Meraj
*/
public class ReportGeneratorService {

  private IReportGenerator reportGenerator;

  /**
  * Generate report.
  * @param startDate start date
  * @param endDate end date
  * @param content report content
  */
  public void generateReport(Date startDate, Date endDate, byte[] content) {
    ReportEntity report = new ReportEntity();
    report.setContent(content);
    report.setStartDate(startDate);
    report.setEndDate(endDate);
    reportGenerator.generateReport(report);
  }

}

Ora tutte le classi verranno compilate e possiamo eseguire la nostra classe di test.

5. Scarica il file sorgente

Questo è stato un esempio dell'utilizzo di Mockito per eseguire Test Driven Development.mockito TDD

Etichetta Java