[jUnit] Testowanie metody

0

Witam

Mam klasę powiedzmy że nazywa się Dane. W tej klasie są jakieś pola typu imie, nazwisko, pesel etc Teraz mam metodę w której jest logika biznesowa wypełniająca te pola (metoda ta jest ween tej klasy ponieważ jest ona związana tylko z tą klasą więc nie wystawiam tego gdzie indziej).

Metoda (publiczna) przyjmuje wielkiego Stringa i wewn robię cuda-wyodrębniam pojedyńcze wyrazy i uzupełniam obiekt. Zwraca tylko true or false. False w przypadku gdy czegoś nie dało się rozdzielić , ztrimować etc.

Z grubsza metoda tak wygląa:

public boolean(String tekst)
{

tutaj się coś dzieje z tekst
i potem:
this.imie = ...

znowu coś się dzieje

this.pesel = ....

i tak dalej
}

Jak taka metodę przetestować w jUnit? Wsadzic Stringa do metody a wynik-sprawdzać wypełnianie obiektu klasy z której wywołano metodę?

0

Wg mnie twoja propozycja jest ok. W końcu w testach chodzi o to, żeby za każdym razem stworzyć świeży obiekt, wywołać na nim jakieś metody, a potem sprawdzić jego stan.

0
@Test
public void testMyMethod(){
  assertTrue(myObj.myMethod("poprwany string"));
  assertFalse(myObj.myMethod("niepoprwany string"));
}

Przy czym daj różne poprawne i nie poprawne stringi tak by pokryć wszystkie przypadki w metodzie.

0

Ale panowie - nie do końca mnie zrozumielisćie.

Metoda ok daje true or false i mogę to przetestować wsadzając w nią dobre lub złe dane.
Ale oprócz boolena przecież są jeszcze inne wyniki: w ciele metody wyodrębniam np imię i nazwisko etc i przypisuje do pól klasy. Jak te wypełnienie pól klasy można zweryfikować?

Coś w stylu:

 
    @Test
    public void testRozwalenieAdresuNaDrobneIwypelnieniePol() {
        System.out.println("test");
        String tekst = "Mirosław Jagniecki 45-999 Wawa";
        String expResultImie = Mirosław;
        String expResultNazwisko = Jagnicki;
        .
        . 
        .
        boolean result = Utils.indeksPierwszejNapotkanejLitery(tekst);
        if(result)
        {
        assertEquals(expResultImie, this.imie);
        assertEquals(expResultNazwisko, this.nazwisko);
        }
      
    }

Oczywisćie to nie działa bo takiej składni nie ma. Ale jak to zasymulowac poprawnie?

0

Ja robie tak ze wywoluje metode z roznymi stringami, i po kazdym wywolaniu sprawdzam te pola. Jesli masz gettery, wolasz gettery. Jesli sa to pola private - ja robie je po prostu package (czyli bez modyfikatora). Mam taka konwencje ze testy mam w tych samych pakietach co testowane klasy, i do nazwy dolaczan *Test, zatem mam do takich pol dostep (uzywam mavena i jego konwencji). Nie mam za duzo takich testow, ale czasami sie zdarzaja. Podobnie, zdarza mi sie chciec przetestowac prywatne metody - i wtedy tez zmieniam z private na package (nawet Joshua Bloch w Effective Java mowi ze tak mozna :>). No bo co innego - refleksja?
Mozna tez zrobic tak ze zamiast pol z fragmentami stringa masz jedno pole jakiejs tam innej klasy, ktorej obiekt bierzesz jako parametr konstruktora tej klasy ktora te dane ma przechowywac. W testach mozesz zaposac tam mocka, i weryfikowac z jakimi parametrami byly wywolywane jego metody. Nowe frameworki typu Mockito umieja robic mocki klas (nie final), wiec nie potrzeba interfejsow. Ale jakos wydaje mi sie to troche przesadzone...

0

@::.

"Ja robie tak ze wywoluje metode z roznymi stringami, i po kazdym wywolaniu sprawdzam te pola. Jesli masz gettery, wolasz gettery."

Dokładnie tak to sobie wyobrażam tylko nie umiem tego jakoś zainicjalizować- obiektu.
Mozesz mi napisac taki prosty test do metody przypiszDane?? Bym wiedział o co się zachaczyć?

Przykładowe stringi niech bedą:
"jan waleń warszawa"
"janwaleńwarszawa"

package temp;

public class TestExample {
    
    private String _imie;
    private String _nazwisko;
    private String _miejscowosc;
    
    public boolean przypiszDane(String txt) {
        try {
            this._imie = txt.substring(0, txt.indexOf(" "));
            this._miejscowosc = txt.substring(txt.lastIndexOf(" ") + 1, txt.length());
            this._nazwisko = txt.substring(txt.indexOf(" ") + 1, txt.lastIndexOf(" "));
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public String getImie() {
        return _imie;
    }

    public String getMiejscowosc() {
        return _miejscowosc;
    }

    public String getNazwisko() {
        return _nazwisko;
    }

}
0
    @Test
    public void testRozwalenieAdresuNaDrobneIwypelnieniePol() {
        String tekst = "Mirosław Jagniecki 45-999 Wawa";
        String expResultImie = "Mirosław";
        String expResultNazwisko = "Jagnicki";
        TestExample o = new TestExample();
        boolean ret = o.przypiszDane(tekst);
        assertTrue(ret);
        assertEquals(expResultImie, o.getImie());
        assertEquals(expResultNazwisko, o.getNazwisko());
    }

Przy okazji - proponuję zamiast metody przypiszDane utworzyć konstruktor z argumentem.

0

o to mi chodziło - nie wiedziałem własnie ze można tak wprost stworzyć obiekt o i norlmalnie odwoływac się do metod tej klasy. Dzięki!

0
lipkerson napisał(a)

o to mi chodziło - nie wiedziałem własnie ze można tak wprost stworzyć obiekt o i norlmalnie odwoływac się do metod tej klasy. Dzięki!

A dlaczego mialo by to byc niemozliwe?
Od siebie moge dodac jeszcze tylko, ze fajnie mozna to robic za pomoca TestNG. Mianowicie:

public class SomeTest {

@DataProvider(name = "stringsProvider")
Object[][] provideStrings() {
     return new Object[][] {
        {"string a", "string", "a"},
        {"string b", "string", "b"},
        {"string c", "string", "c"}
};
}

@Test(dataProvider = "stringsProvider")
public void testPrzypiszDane(String string, String expected1, String expected2) {
     TestClass o = new SomeTest();
     assertTrue(o.someMethod(string));
     assertEquals(expected1, o.getPart1());
     assertEquals(expected2, o.getPart2());
}
}

W ten sposob masz tylko 2 metody, a wiele testow. TestNG poprawnie pokaze ze sa 3 rozne testy, w drzewku / raporcie ladnie rozdzieli wszystko. Mysle ze jest dosc jasne, dlaczego metoda prodivera zwraca tablice tablic - kazy element jest tablica, ktora jest nastepnie mapowana na parametry metody ktora od danego providera zalezy. Parametrow jak i zastosowan jest bez liku.
Nie wiem czy to jest w Junit4, nie wiem tez czego uzywasz, na podstawie samych adnotacji nie potrafie stwierdzic.

0
__krzysiek85 napisał(a)

Przy okazji - proponuję zamiast metody przypiszDane utworzyć konstruktor z argumentem.

Jest bardzo wielu znanych w swiatku Java ludzi ktorzy sie z tym kompletnie nie zgodza. Dosc popularne jest stwierdzenie ze konstruktor nie powinien robic nic, co najwyzej przypisywac zaleznosci do pol. Ty proponujesz aby konstruktor wykonywal jakastam logike biznesowa.

0

A skąd takie wieści? Obiekt powinien być cały czas w spójnym stanie, a więc konstruktor czasem musi coś zrobić, żeby to zapewnić.

0

Np. Misko Havery (nie jestem pewien czy sie tak to pisze), Crazy Bob Lee, Gavin King i wielu innych prorokow DI. Jak bardzo ci zalezy, to poszukaj sobie prezentacji google i jakosci kodu i takich tam, to sa krotkie, 30-60 min wystapienia, bardzo ciekawe.

0

Jeśli DI to pewnie też wzorce jak Budowniczy czy Fabryka, a więc zwracanie obiektu do kodu używającego dopiero gdy obiekt będzie miał spójny stan.

W Scali (czy innych językach choć częściowo funkcyjnych) mogę użyć lazy val (zalecane) albo lazy var (niezalecane) i mnóstwo problemów odpada. Wieloetapowe budowanie obiektu można zastąpić wielodziedziczoną (w Scali można dziedziczyć po wielu klasach czy traitach) fabryką czy budowniczym.

Zarzuć linkiem bo nie chce mi się szukać ;)

0

Pierwszy z brzegu: http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/
Nie mowie ze to jest religia i nalezy tego zawsze przestrzegac, po prostu dalem komentarz.
Co do Scali to nie jest tutaj mowa o Scali ;d Ale masz racje.

0

:) ja tam Ci dziękuję za ten TestNG - świetna sprawa ten data provider-bardzo ładnie teraz mój test tej metody wygląda (jakiś odpowiednik w jUnit?)...tylko szkoda ze plugin do netbeans by testy tworzyły się z automatu nie działa:/ Lubię netbeans i nie przesiąde się na ten eclipse nigdy. Chociaż ostatnio musiałem trochę tam podłubać z powodu BIRT'a. Ale wracając-spotkał się ktoś z integracją testNG i netbeans? Bo w necie tylko narzekania ze nie chodzi....

Co do logiki biznesowej w konstruktorze - nie mam wiedzy takiej jak wy ale na logikę to biorę. Metoda moja ma 250+ linii i dziwnym by było wsadzanie tego do konstruktora. Dla mnie konstruktor powinien mieć jak najmniej parametrów i w ogóle mało co powinno mieć w nim miejsce. Raczej definiuje w nim stan i właściwości obiektu przed właściwym jego użyciem. Tak podpatruję to w różnych opensourceowych projektach.

BTW praktykuje się dla jednej klasy testy napisane w jUnit i testNG?

0

No to moze troche o TestNG. W czasach gdy krolowal JUnit i wymuszal implementacje interfejsow, dziedziczenie po klasach i jakies specjalne nazewnictwo metod (test*, anybody?), w czasach Javy 5 powstal TestNG. Opieral sie na adnotacjach, dodawal pelno nowych rzeczy, miedzy innymi stanowe testy. JUnit jest zdania, ze kazda metoda testowa to osobny przypadek, i tworzy nowa instancje klasy testowej dla kazdej metody. Jesli chce sie przechowywac stan, to sie uzywa roznych mykow typu statiki itp. TestNG umozliwia tworzenie stanowych testow, pozwala na przekazywanie stanu miedzy wszystkimi testami, miedzy calymi zbiorami testow, miedzy metodami. Dodaje te data providery, fabryki testow. Te ostatnie szczegolnie ciekawe - zamiast TestNG, moje fabryki tworza instancje testow, dlatego miedzy innymi moje testy uzywaja @Inject po uzywam Guice aby to dla mnie zrobil (nowe wersje maja to juz natywnie). Normalnie cuda mozna robic - co nie znaczy ze w JUnit sie nie da; ot, jest to o wiele wiecej roboty, ktora by trzeba testowac, poprawiac i utrzymywac. Na podstawie popularnosci TestNG i adnotacji powstal JUnit 4 ktory wiele adnotacji uzywa podobnie. Ba, podstawowe testy miedzy JUnit 4 a TestNG sa na tyle kompatybilne ze mozna zmienic importy z jednego na drugie i dziala.
Nie znaczy to ze nie lubie JUnit - ma swoj udzial, i uzywamy go nadal. W firmie u nas napisalem wielki frakework testowy dla TestNG, ale ostatnio mielismy potrzebe zrobienia portu do JUnit3 poniewaz jeden z projektow korzysta z zamknietej biblioteki ktora dostarcza bazowe klasy testowe dla JUnit - dalo sie, ale jak mowie - duzo pracy i dosc zmudna. Ja jak nie bede musial do JUnit nie wroce.

@Wibowit / donek: scala-test znasz?

0

W JUnicie można to zapisać tak:

   @Test
   public void testRozwalenieAdresuNaDrobneIwypelnieniePol() {
             testRozwalenieAdresuNaDrobneIwypelnieniePolImpl("Mirosław Jagniecki 45-999 Wawa", true, "Mirosław", "Jagnicki");
 }

   @Test
   public void testRozwalenieAdresuNaDrobneIwypelnieniePolBezSpacji() {
             testRozwalenieAdresuNaDrobneIwypelnieniePolImpl("MirosławJagniecki45-999Wawa", false, null, null);
 }

   private void testRozwalenieAdresuNaDrobneIwypelnieniePolImpl(String tekst, boolean expResult, String expResultImie, String expResultNazwisko) {
        TestExample o = new TestExample();
        boolean ret = o.przypiszDane(tekst);
        assertEquals(expResult, ret);
        assertEquals(expResultImie, o.getImie());
        assertEquals(expResultNazwisko, o.getNazwisko());
    }
0

No spojrzyj ino jeszcze raz na przyklad z TestNG - sam przyznasz ze to nie to samo? Kazdy test ma miec osobna metode, ktora wola jedna utility metode. Prawie dobrze...

0
::. napisał(a)

No spojrzyj ino jeszcze raz na przyklad z TestNG - sam przyznasz ze to nie to samo? Kazdy test ma miec osobna metode, ktora wola jedna utility metode. Prawie dobrze...

Ja nie powiedziałem, że tak jest lepiej (bo moim zdaniem nie jest).
Pokazałem tylko, jak to można zrobić najlepiej (moim zdaniem) w JUnicie.
W firmie, w której pracuję standardem jest JUnit i właśnie tak robię testy dla wielu zestawów danych.

0

Chyba jest troche czystszy sposob: http://www.mkyong.com/unittest/junit-4-vs-testng-comparison/, wyszukac "6. parameterized test": nalezy zrobic konstruktor z parametrami, dodac metode z anotacja, dodac @RunWith(Parameterized.class). Ma to swoje minusy (jak np. niemozliwosc uzycia innej klasy do @RunWith, ze wzgledu na wymaganie aby konstruktor mial parametry dla danej metody wymusza niejako aby jedna klasa testowala jedna metode - chyba?), ale czasami moze sie przydac.

0
lipkerson napisał(a)

BTW praktykuje się dla jednej klasy testy napisane w jUnit i testNG?

Zalezy jak lezy. Np. maven-surefire-plugin tego nie obsluguje z biegu, ale mozna zrobic taki myk:

  • tworzysz plik test.xml zgodnie z DTD TestNG
  • dzielisz testy na 2 grupy, i tworzysz np. 2 elementy <suite> lub <test>; w jednej z nich dajesz testy TestNG, w drugiej dajesz testy JUnit i dodajesz atrybut junit="true"
  • konfigurujesz surefie zeby uzywal TestNG (domyslnie w tym przypadku, surefire patrzy czy jest TNG w classpath i jesli jest to ustawia sie na tryb TNG) i zeby uzyl tego test.xml zamiast tworzyc swojego tymczasowego
    Kwestia spojrzenia do dokumentacji TNG (jak stworzyc plik itp) i surefire (jak skonfigurowac aby korzystal z konkretnego pliku), zadna magia.

Problem jest taki - tworca TNG napisal adapter z JUnit 3 do TNG, ale stwierdzil ze nie warto pisac adaptera JUnit 4 bo i tak nikt nie bedzie tego uzywal. Bardzo sie pomylil, jest to bardzo czesto zglaszana prosba, zdaje sie pracuje nad tym, ale nie ma tego chyba jeszcze. Oznacza to mniej wiecej tyle - nie mozna mieszac testow TNG i JUnit 4 w surefire, przynajmniej w jednym execution pluginu. Mozna to obejsc konfigurujac dla fazy 'test' surefire 2 razy, ale trzeba wtedy uzywac excludow / includow do testow, zeby bylo wiadomo co ma kiedy byc wykonywane. Bleh.

0
::. napisał(a)

Pierwszy z brzegu: http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/
Nie mowie ze to jest religia i nalezy tego zawsze przestrzegac, po prostu dalem komentarz.
Co do Scali to nie jest tutaj mowa o Scali ;d Ale masz racje.

Przebrnąłem przez to. Wygląda to jak jedna wielka reklama Guice :P Sam jestem fanem DI, jest to świetna sprawa, powinno to być wbudowane w język, a nawet i maszynę wirtualną, a wszelkie statyczne pola powinny być wywalone.

Jednak nie do końca się z tym artykułem zgadzam. W komentach podali mu trochę argumentów, np klasa Triangle(int, int, int) powinna w konstruktorze sprawdzać spójność.
Tu: http://misko.hevery.com/2009/02/09/to-assert-or-not-to-assert/ koleś zachęca do tworzenia osobnego API i do używania nulli. Ja jestem bardzo przeciwko nullom. Poza tym do testowania można tanio robić mocki.

W przykładzie lipkersona do którego tyczyły się nasze komentarze to metoda przypiszDane(String) była po prostu zakamuflowaną metodą inicjalizuj(), którą Misko też odradza. Tak, że aby zadowolic Hevery'ego kodem Javowym tutaj to pasowałoby zrobić chyba fabrykę. Ale wg mnie to trochę na wyrost, bo tutaj nie korzysta się ani z zewnętrznego, ani z globalnego stanu, a sama logika nie jest ciężka. W tym przypadku przesunięcie kodu z przypiszDane do konstruktora nie tylko polepszy jakość kodu (obiekt będzie zawsze spójny), ale i w ogóle nie utrudni testowania.

Edit:
Zapomniałem o czymś. Jednak wg Misko, pisanie takiej logiki jak ta z przypiszDane w konstruktorze jest akceptowalne:

Note: Constructing value objects may be acceptable in many cases (examples: LinkedList; HashMap, User, EmailAddress, CreditCard). Value objects key attributes are:

  • Trivial to construct
  • are state focused (lots of getters/setters low on behavior)
  • do not refer to any service object.

1 użytkowników online, w tym zalogowanych: 0, gości: 1