Kilka przykładów użycia EasyMock

Paczka EasyMock służy do imitowania (imitacja ang. mock) implementacji klas i metod.

Imitowanie jest szczególnie użyteczne podczas pisania testów jednostkowych (ang. unit test), gdyż testy takowe powinny być:

  • atomowe
  • odizolowane

W realnych warunkach, kiedy obiekty są powiązane i/lub zależne od siebie, ciężko jest jednak napisać test, który jest odizolowany. Zamiast pisać klasy udające/imitujące funkcjonalność klas zależnych, tworzyć konfiguracje testowe lepiej jest imitować ich metody używając paczki EasyMock.

W prostejm testowej klasie przedstawiłem kilka przykładów jak używać paczki EasyMock imitując następującą funkcjonalność interfejsu Wspolpracownik :

  • wołanie metody zwracającej wartość i nie posiadajacej parametru wejściowego
  • wołanie metody zwracającej wartość i posiadającej parametr wejściowy
  • wołanie metody, która ni zwraca wartości
  • wołanie 2 metod współpracownika w jednej metodzie klasy testowanej

Kolejne przykłady dotyczć:

  • częściowej imitacji klasy (testowana metoda klasy używa innej metody tej samej klasy, która to jest imitowana)
  • imitacji klasy z dwoma konstruktorami (oba przyjmują 1 obiekt na wejście)

Testowana jest klasa o nazwie KlasaTestowana (oryginalne nieprawdaż :) ). Używa ona lokalnie interfejsu Wpsolpracownik. Celem przykładu jest pokazanie jek testować klasę imitując wszystkie metody klasy/interfejsu współpracownika.

Do napisania testu skorzystałem z frameworku JUnit. EasyMock współpracuje także z innymi frameworkami testowymi jak chołby TestNG (bardzo lubię)

Obraz zastepuje 1000 słów … z kodem jest podobnie :)

Dodałem kilka komentarzy aby było łatwiej przeanalizować kolejne kroki.

Pobierz źródła Java: ProstyEasyMockTest

import static org.easymock.EasyMock.anyInt;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createMockBuilder;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import junit.framework.TestCase;
 
/**
 * Prosty test przedstawiajacy kilka prostych przypadkow imitacji metod.<br>
 * Do imitowania zostal uzyta paczka <a
 * href="http://www.easymock.org/">EasyMock</a> - w tym przypadku uzywalem
 * wersji 3.0 ale nie powinno byc problemow z innymi werjsami.
 * 
 * 
 * @author <a href="blogpl.jwerk.de">blogpl.jwerk.de</a> - Albert Gorski
 * 
 */
public class ProstyEasyMockTest extends TestCase {
 
  private Wspolpracownik zamockowanyInterfejs;
 
  private KlasaTestowana klasaTestowana;
 
  private KlasaNiekompletnieImitowana mockKlasaNiekompletnieImitowana;
 
  private KlasaDwaKonstruktory mockKlasaDwaKonstruktory;
 
  @Override
  protected void setUp() throws Exception {
    super.setUp();
    // utworzenie proxy imitowaneego obiektu
    this.zamockowanyInterfejs = createMock(Wspolpracownik.class);
    this.klasaTestowana = new KlasaTestowana();
    // ustawienie wspolpracownika (collaborator)
    this.klasaTestowana.setWspolpracownik(this.zamockowanyInterfejs);
    //
    // klasa z niekompletna imitacja
    this.mockKlasaNiekompletnieImitowana = createMockBuilder(
        KlasaNiekompletnieImitowana.class).addMockedMethod("potega", int.class)
        .createMock();
    //
    // klasa z dwoma konstruktorami
    this.mockKlasaDwaKonstruktory = createMockBuilder(
        KlasaDwaKonstruktory.class).withConstructor(String.class)
        .withArgs("tekst").createMock();
  }
 
  /**
   * Przyklad jak imitowac metode, ktora zwraca obiekt.
   */
  public void testDodajPrefix() {
    final String wejscie = "wejscie";
    final String oczekiwanyRezultatMock = "prefix";
    // rozpocznij nagrywanie
    expect(this.zamockowanyInterfejs.pobierzPrzedrostek()).andReturn(
        oczekiwanyRezultatMock);
    // zakoncz nagrywanie
    replay(this.zamockowanyInterfejs);
    // przetestuj wynik
    assertEquals("Nieprawidlowy wynik operacji", oczekiwanyRezultatMock
        + wejscie, this.klasaTestowana.dodajPrzedrostek(wejscie));
    // sprawdz czy imitowana metoda zostala wywolana
    verify(this.zamockowanyInterfejs);
  }
 
  /**
   * Przyklad jak imitowac metode, ktora posiada parametr wejsciowy.
   */
  public void testWywolajMetodePosiadajacaParametr() {
    final String wejscie = "input";
    final String oczekiwanyRezultatMock = "output";
    // rozpocznij nagrywanie
    expect(
        this.zamockowanyInterfejs
            .metodaPosiadajacaParametr(anyObject(String.class))).andReturn(
        oczekiwanyRezultatMock);
    // zakoncz nagrywanie
    replay(this.zamockowanyInterfejs);
    // przetestuj wynik
    assertEquals("Nieprawidlowy wynik operacji", oczekiwanyRezultatMock,
        this.klasaTestowana.wywolajMetodePosiadajacaParametr(wejscie));
    // sprawdz czy imitowana metoda zostala wywolana
    verify(this.zamockowanyInterfejs);
  }
 
  /**
   * Przyklad jak imitowac metode, ktora nic nie zwraca. Dodatkowo testujemy
   * wykonanie 2 metod interfejsu podczas jednego wolania metody klasy
   * testowanej.
   */
  public void testZrobCosWaznegoPotemDodajPrefix() {
    final String wejscie = "input";
    final String oczekiwanyRezultatMock = "prefix";
    // rozpocznij nagrywanie
    // metody bez wartosci zwracanej podczas nagrywania sa po prostu wolane na
    // obiekcie proxy
    this.zamockowanyInterfejs.zrobCosWaznego();
    // po wolaniu zamarkuj, ze oczekujesz jej wywolania podczas testu
    expectLastCall();
    // nagraj wywolanie innej metody
    expect(this.zamockowanyInterfejs.pobierzPrzedrostek()).andReturn(
        oczekiwanyRezultatMock);
    // zakoncz nagrywanie
    replay(this.zamockowanyInterfejs);
    // przetestuj wynik
    assertEquals("Nieprawidlowy wynik operacji", oczekiwanyRezultatMock
        + wejscie, this.klasaTestowana.zrobCosWaznegoPotemDodajPrefix(wejscie));
    // sprawdz czy imitowane metody (w tym przypadku dwie) zostaly wywolane
    verify(this.zamockowanyInterfejs);
  }
 
  /**
   * Przyklad jak imitowac metode, ktora nic nie zwraca. Dodatkowo testujemy
   * wykonanie 2 metod interfejsu podczas jednego wolania metody klasy
   * testowanej.
   */
  public void testCzesciowaImitacjaDodajDoPotegi() {
    final int liczba = 10;
    final int mockWartoscPotegi = 4;
    expect(this.mockKlasaNiekompletnieImitowana.potega(anyInt())).andReturn(
        mockWartoscPotegi);
    replay(this.mockKlasaNiekompletnieImitowana);
    // przetestuj wynik
    assertEquals("Nieprawidlowy wynik operacji", liczba + mockWartoscPotegi,
        this.mockKlasaNiekompletnieImitowana.dodajDoPotegi(liczba, 2));
    // sprawdz czy imitowana metoda zostala wywolana
    verify(this.mockKlasaNiekompletnieImitowana);
  }
 
  /**
   * Przyklad imitacji jednego z konstruktorow (dwuznaczne bo oba konstruktory
   * maja na wejsciu jeden obiekt)
   */
  public void testDwaKonstruktoryCzyToInteger() {
    // zakoncz nagrywanie - wazny tylko konstruktor
    replay(this.mockKlasaDwaKonstruktory);
    // przetestuj wynik
    assertEquals("Nieprawidlowy wynik operacji", false,
        this.mockKlasaDwaKonstruktory.czyToInteger());
    // sprawdz konstruktor zostal wywolany
    verify(this.mockKlasaDwaKonstruktory);
  }
 
  // --------------------------------------------------------------------------
  //
  // Klasy testowane oraz wspolpracujace
  //
  // --------------------------------------------------------------------------
  public static class KlasaDwaKonstruktory {
 
    private final Object objekt;
 
    public KlasaDwaKonstruktory(String parametr) {
      objekt = parametr;
    }
 
    public KlasaDwaKonstruktory(Integer parametr) {
      objekt = parametr;
    }
 
    public boolean czyToInteger() {
      return Integer.class.isInstance(objekt);
    }
  }
 
  /**
   * Klasa testowana, poddana tylko czesciowej imitacji.
   * 
   * 08.07.2011 [Albert Gorski] initial revision <br>
   * 
   * @author <a href="blogpl.jwerk.de">blogpl.jwerk.de</a> - Albert Gorski
   * 
   */
  public static class KlasaNiekompletnieImitowana {
 
    /**
     * Funkcjonalnosc tej metody bedzie imitowana.<br>
     * Zwrata potege liczby podanej na wejscie.
     * 
     * @param wejscie
     * 
     * @return wejscie*wejscie
     */
    public int potega(int wejscie) {
      return wejscie * wejscie;
    }
 
    /**
     * Zwraca liczbe dodana do potegi liczby <i>liczbaPotegowana</i>.
     * 
     * @param liczba
     * @param liczbaPotegowana
     * 
     * @return liczba + (liczbaPotegowana*liczbaPotegowana)
     */
    public int dodajDoPotegi(int liczba, int liczbaPotegowana) {
      return liczba + potega(liczbaPotegowana);
    }
  }
 
  /**
   * Klasa testowana.
   * 
   * 08.07.2011 [Albert Gorski] initial revision <br>
   * 
   * @author <a href="blogpl.jwerk.de">blogpl.jwerk.de</a> - Albert Gorski
   * 
   */
  public static class KlasaTestowana {
 
    /** Wspolpracownik klasy testowanej - to tej interfejs bedzie imitowany. */
    private Wspolpracownik wspolpracownik;
 
    /**
     * Dodaje przedrostek do parametru.<br>
     * przedrostek + parametr = wynik
     * 
     * @param parametr
     * 
     * @return przedrostek + parametr
     */
    public String dodajPrzedrostek(String parametr) {
      return this.wspolpracownik.pobierzPrzedrostek() + parametr;
    }
 
    /**
     * Wywoluje metode wspolpracownika (Collaborartor). <br>
     * Metoda troche bez sensu ale chcemy przetestowac jak imitowac metody z
     * parametrem.
     * 
     * @param wejscie
     * 
     * @return wyjscie (bez konkretnej specyfikacji)
     */
    public String wywolajMetodePosiadajacaParametr(String wejscie) {
      return this.wspolpracownik.metodaPosiadajacaParametr(wejscie);
    }
 
    /**
     * Robi to samo co metoda {@link #dodajPrzedrostek(String)} ale dodatkowo,
     * uprzednio wywoluje metode wspolpracownika (ktora nic nie zwraca).
     * 
     * @param wejscie
     *          Wejscie
     * 
     * @return przedrostek + wejscie
     */
    public String zrobCosWaznegoPotemDodajPrefix(String wejscie) {
      this.wspolpracownik.zrobCosWaznego();
      return dodajPrzedrostek(wejscie);
    }
 
    /**
     * Setter wspolpracownika.
     * 
     * @param wspolpracownik
     */
    public void setWspolpracownik(Wspolpracownik wspolpracownik) {
      this.wspolpracownik = wspolpracownik;
    }
  }
 
  /**
   * Interfejs, ktory jest uzywany przez klase testowana (ang. Collaborator).<br>
   * Znaczenie metod nie jest wazne, gdyz i tak bedziemy imitowac ich
   * implementacje (imitacja - ang. mock).
   * 
   * 08.07.2011 [Albert Gorski] initial revision <br>
   * 
   * @author <a href="blogpl.jwerk.de">blogpl.jwerk.de</a> - Albert Gorski
   * 
   */
  public static interface Wspolpracownik {
 
    void zrobCosWaznego();
 
    String pobierzPrzedrostek();
 
    String metodaPosiadajacaParametr(String parametr);
  }
}

 

 

Informacje o @albgorski

Od 1999 roku profesjonalnie zajmuję się rozwijaniem oprogramowania. Głównie Java, ale także Groovy, PHP, HTML, JavaScript oraz Adobe Flex. Fascynują mnie metody wymiany danych, ich przechowywania oraz dostępowania. Jestem WIELKIM zwolennikiem Clean Code, TDD oraz agilistą (może lepiej lean-istą). Ekosystem Java dostarcza WIELE świetnych frawework-ów i bibliotek, a społeczność miłośników języka Java jest najlepsza pod słońcem :)
Ten wpis został opublikowany w kategorii java, test i oznaczony tagami , , , , , . Dodaj zakładkę do bezpośredniego odnośnika.

Możliwość komentowania jest wyłączona.