jMock

Mock objekty se dají vytvářet několika způsoby. Můžeme je psát ručně, ale to bývá v mnoha případech nepraktické nebo zcela zbytečné. Na trhu totiž existuje mnoho knihoven s volně šiřitelným kódem, které se starají o automatické generování mock objektů. Podle mnoha odborníků je v součastnosti na trhu nejlepší knihovnou knihovna jMock od autorů webových stránek mockobjects.com[9], které se zabývají právě technikou mockování objektů. Tuto knihovnu používáme v naší aplikaci a její API se objeví ve všech následujících příkladech.

Architektura

jMock je testovací rámec, který poskytuje jednoduché API pro napodobování objektů na základě jejich rozhraní. jMock umožňuje definovat požadavky na volání metod a nastavovat vlastnosti jejich chování.

Hlavní vstupní branou do jMock API je třída MockObjectTestCase, která rozšiřuje třídu TestCase z testovacího rámce jUnit. MockObjectTestCase poskytuje metody pro snadné definování požadavků na interakce testovaného objektu s jeho nejbližšími sousedy a automatické ověřování mock objektů na konci testu.

Mock objekt, který reprezentuje rozhraní definující chování souseda, se kterým testovaný kód komunikuje, vytvoříme konstruktorem new Mock(Rozhrani.class) nebo pomocnou metodou mock(Rozhrani.class), poskytovanou objektem MockObjectTestCase. Tento mock objekt může být přetypován na rozhraní, které napodobuje, a může tak nahradit samotnou implementaci tohoto rozhraní v testovaném kódu.

private Mock mockUserDao = new Mock(UserDAO.class);
private UserManager userManager = new UserManagerImpl();
....

userManager.setUserDAO((UserDAO)mockUserDao.proxy());

Mock objekty poskytují metody pro nastavení tzv. očekávání. jMock rozlišuje dva druhy těchto metod. Jedná se o metody typu pahýl a očekávání. Nyní si tyto druhy metod představíme podrobněji.

Metoda typu pahýl vs. očekávání

Pahýl je v tomto případě druh metody modelového objektu, která může ale nemusí být volána v průběhu testu. V obou případech je test úspěšný. Očekávání je naproti tomu druh metody, na kterou jsou kladeny jisté požadavky a pokud tyto požadavky nejsou naplněny, test není úspěšný. Na metodu můžeme klást požadavek, že bude volána právě jednou v průběhu testu. Při verifikaci mock objektu v závěrečné fázi testování se ověřuje, zda-li tomu tak opravdu bylo a pokud ne, potom je vyvolána chyba a test není úspěšný.

Jak tyto metody používáme? Metody typu pahýl používáme především k získání informací o stavech objektů s tím, že tyto metody nemají žádný vliv na stav světa, zatímco metody typu očekávání jsou příkazy, které takový vliv mají. Metody typu očekávání mohou mít také návratovou hodnotu. Pahýlové metody mohou být volány nesčetněkrát, očekávání mohou být volána pouze tolikrát, kolikrát je nastavíme, jak ukazuje následující příklad, který je částí jednotkového testu UserManagerTest, kde mockLogger a mockUserDao jsou mock objekty, na kterých jsou definovány metody typu pahýl a očekávání.

public static final String USERNAME = "sportoviny";
....
//vyvolání metody typu pahýl
mockLogger.stubs().method("getLoggingLevel").noParams()
        .will(returnValue(Logger.WARNING));

//právě jedno volání metody typu očekávání zajišťuje metoda once()
mockUserDao.expects(once())
        .method("getUserByUsername")
        .with(isA(String.class))
        .will(returnValue(user));

//přinejmenším jedno volání metody typu očekávání
mockUserDao.expects(atLeastOnce()) ...

//právě pětkrát vyvolání metody typu očekávání
mockUserDao.expects(exactly(5)) ...
....

Omezení nastavená na parametrech volaných metod

Na parametry volaných metod můžeme klást různá omezení. Pokud bychom na metodu getUserByUsername() kladli požadavek, že tato metoda má být vždy volána s parametrem rovným libovolnému objektu, který reprezentuje libovolnou hodnotu, naše definice by byla následující.

mockUserDao.expects(once())
        .method("getUserByUsername")
        .with(ANYTHING)
        .will(returnValue(user));

Cítíte asi sami, že toto chování není takové, jaké od této metody očekáváme. My potřebujeme tuto metodu volat s parametrem typu String, který reprezentuje uživatelské jméno registrovaného uživatele. Proto toto očekávání upřesníme a metodu definujeme následovně.

mockUserDao.expects(once())
        .method("getUserByUsername")
        .with(isA(String.class))
        .will(returnValue(user));

Třída MockObjectTestCase definuje mnoho typů omezení, které můžeme po parametrech volaných metod vyžadovat. Pokud by nám tato sada omezení nestačila, můžeme si nadefinovat vlastní omezení

Pořadí vyvolávání metod

Na metody mock objektů můžeme stanovovat pořadí, v jakém mají být volány, a to jak na pahýly tak očekávání. Následující příklad je opět část kódu UserManagerTest, který definuje pořadí vyvolávání metod typu očekávání.

mockUserDao.expects(once())
      .method("setUser")
      .with(same(user))
      .isVoid().id("setting user"); 
  
mockUserDao.expects(once())
      .method("getAuthorityByName")
      .with(eq(new String("ROLE_CLIENT")))
      .after("setting usser")
      .will(returnValue(new Authority("ROLE_CLIENT")));
  
mockMailManager.expects(once())
      .method("sendActivationEmail")
      .with(new Constraint[] {isA(String.class), 
           isA(String.class), eq(new Locale("cs_CZ"))})
      .after("setting user")
      .isVoid();

Kód zachycuje volání tří metod typu očekávání, kdy metody getAuthorityByName() a sendActiovationEmail() jsou volány až po metodě setUser(). Jejich vzájemné pořadí není rozlišeno. Pokud by toto pořadí nebylo dodrženo, test by nebyl úspěšný.

Pravidla vyhodnocování očekávání

Ostatní metody z našich příkladů, jmenovitě method(), with(), after(), will() definují porovnávací pravidla, která validují příchozí volání metod mock objektu a testují jejich očekávání. Pokud by pravidla porovnávání nevyhovovala našim potřebám, můžeme si vytvořit pravidla vlastní.

Metoda method() objektu MockObjectTestCase definuje jméno volané metody mock objektu, na který je aplikována. Metoda with() aplikuje omezení na parametry volané metody, metoda after() určuje pořadí volání metod mock objektů a will() určuje návratovou hodnotu volané metody.

Nyní si ukážeme, jak připravit jednoduchý jednotkový test za použití techniky mockování objektů a nástroje jMock.

Funkční příklad

Na následujícím komentovaném příkladu si ilustrujeme techniku mockování objektů. V podkapitole 4.5. jsme se zmínili o určitém scénáři vývoje interakčních jednotkových testů. Totoho scénáře se budeme držet i v tomto příkladu. Popíšeme si tvorbu jednotkového testu implementace třídy UserManager a jeho metody newRegistratedUserAndSendActivationEmail(). Plné znění tohoto testu najdete v souboru UserManagerTest.java.

public class UserManagerMockTest extends MockObjectTestCase {

  /* definice mock objektů */
  private Mock mockUserDao;
  private Mock mockMailManager;
  ...
  /* definice testovaného objektu */
  private UserManager userManager;
  ...
  /* nastavení proměnných */
  public static final String USERNAME = "sportoviny";
  ...
  /* instanciace a nastavení testu */
  protected void setUp() throws Exception {
   
    super.setUp();
   
    mockUserDao = new Mock(UserDAO.class);
    mockMailManager = new Mock(MailManager.class);
   
    userManager = new UserManagerImpl();
    userManager.setUserDAO((UserDAO)mockUserDao.proxy());
    userManager.setMailManager((MailManager)mockMailManager.proxy());
    ... 

    user = new User();
    user.setUsername(USERNAME);
    ...

  }

  /** otestuje uložení uživatele a zaslání aktivačního mailu */
  public void testSaveNewRegistratedUserAndSendActivationEmail() {
    
    /* definice pojmenovaného očekávání jednoho 
     * volání metody setUser() s parametrem typu User a 
     * návratovou hodnotou void
     */
    mockUserDao.expects(once())
       .method("setUser")
       .with(isA(User.class)
       .isVoid().id("setting user"); 

    /* definice pojmenovaného očekávání jednoho volání 
     * metody getAuthorityByName() 
     * s parametrem typu String a hodnotou "ROLE_CLIENT" a 
     * návratovou hodnotou jako instancí třídy Authority, která 
     * je volána po metodě setUser()
     */   
    mockUserDao.expects(once())
       .method("getAuthorityByName")
       .with(eq(new String("ROLE_CLIENT")))
       .after("setting usser")
       .will(returnValue(new Authority("ROLE_CLIENT")));
  
    ...
    /* vyvolání metody doménového objektu */
    userManager.saveNewRegistratedUserAndSendActivationEmail(user, 
                new String("nejaka/url"), new Locale("cs_CZ"));
    
    /* verifikace nastavených očekávání */  
    mockUserDao.verify();
    mockMailManager.verify();
    ...
  }

  ....

}

Již několikrát jsme si řekli, že používáme-li mock objekty pro psaní jednotkových testů, pak produkujeme kód, který z hlediska návrhu vyhovuje těm nejpřísnějším normám. Nicméně používání techniky mockování objektů nenahrazuje zkušenosti kvalitního vývojáře. Zkušenosti ukazují, že interakční jednotkové testy se stávájí příliš komplikované v případě, že návrh systému je špatný, a zesilují tak problémy přílišné závislosti kódu. Následující části textu popisují dobré rady, které nám mohou pomoci v návrhu naší aplikace a umožní nám tak psát jednoduší a srozumitelné jednotkové interakční testy.



Komentáře

Téma neobsahuje žádné komentáře.

Vložit komentář

Můžete používat značkovací jazyk Texy!


Jméno:
E-mail:
Url:
Komentář:
1 + 2 =
 
MoroSystems, s.r.o.