JUnit - instrukcja
-
Ściągnąć z sieci, spod
adresu Wumpus.zip
projekt “Hunt the Wumpus”.
Projekt ten
to “kultowa” gra znana właśnie
pod tytułem
“Hunt the Wumpus”. Jeżeli gra ta mimo swojej
“kultowości”
jest jednak komuś nieznana, to informacje na jej temat można
znaleźć tu.
- Rozpakować ten projekt i
otworzyć w NetBeans.
- Odnaleźć w projekcie
klasę wumpus.CommandParser i sporządzić dla niej testy
jednostkowe.
Gra
"Hunt hte Wumpus" ma tekstowy interfejs. Gracz wydaje polecenia
wpisując je na standardowym wejściu. W związku z tym potrzebna jest
klasa, która polecenia wydawane przez gracza będzie
parsować. Klasą tą jest wumpus.CommandParser.
3.1.
Wykorzystując środowisko NetBeans sporządzić szkielet testów
dla klasy wumpus.CommandParser.
|
W menu
dostępnym pod prawym przyciskiem myszy należy wybrać "Create JUnit
Tests" |
|
Tworząc
szablon testów jednostkowy można ustalić:
- dla
których metod mają być wygenerowane metody testujące (Method
Access Level),
- czy
wygenerować metody SetUp, TearDown (o ich zastosowaniu będzie za
chwilę) oraz domyślne ciała metod testujących (Deafult Method Bodies -
opcja ta jest niezalecana z uwagi na niską jakość generowanych
testów)
- czy
automatycznie wygenerować komentarze.
W przerabianym tu
przykładzie najlepiej wybrać opcja tak jak na ilustracji obok.
|
3.2. Do klasy
wumpus.CommandParserTest dopisać metodę testGetCommand (assertTrue).
public void testGetCommand()
{
CommandParser commandParser = new CommandParser();
/*W obiekcie
CommandParser trzeba zarejestrować obsługiwane polecenia nim zacznie
się je parsować. Robi się to przy pomocy metody add(). Pierwszy
parametr tej metody to String z nazwą polecenia, natomiast drugi to
obiekt reprezentujący polecenie. */
commandParser.add("a", MoveEastCommand.class);
/*Sprawdzamy
działanie metody getCommand. Metoda ta ma za zadanie zwrócić
obiekt reprezentujący polecenie, którego nazwą jest
parametr, z którym metoda getCommand jest wywoływana.
Funkcja assertTrue to właściwy test. Wszystkie metody z assert w nazwie
służa do sprawdzania warunków i jeżeli sprawdzany warunek
nie jest spełniony to rzucany jest wyjątek i test zawodzi. assertTrue
sprawdza warunki logiczne i rzuca wyjątek, gdy warunek jest
równy false (dostępna jest również metoda
assertFalse).*/
assertTrue(commandParser.getCommand("a") instanceof MoveEastCommand);
}
3.3. Do klasy wumpus.CommandParserTest dopisać metodę test
testNoCommand (assertNull).
public
void testNoCommand()
{
CommandParser commandParser = new CommandParser();
/*Sprawdzamy,
czy metoda getCommand zwraca null, jeżeli w obiekcie CommandParser nie
zarejestrowano żadnych poleceń. Do sprawdzania używamy metody
assertNull. Metoda ta rzuca wyjątek, gdy jej parametr jest
różny od null (dostępna jest również metoda
assertNotNull).*/
assertNull(commandParser.getCommand("a"));
}
3.4.
Do klasy
wumpus.CommandParserTest dopisać metodę test testStringTrimmer
(assertEquals).
public void testStringTrimmer()
{
CommandParser parser = new CommandParser();
/*Sprawdzamy, czy
metoda removeLastWord usówa ostatnie słowo z będącego
parametrem Stringa. Metoda assertEquals porównuje będące
parametrami obiekty i rzuca wyjątek, jeżeli obiekty te nie są sobie
równe (dostępna jest również metoda
assertNotEquals).*/
assertEquals("", parser.removeLastWord("a"));
assertEquals("a b c d", parser.removeLastWord("a b c d e"));
assertEquals("a b c d", parser.removeLastWord("a b
c d e"));
assertEquals("", parser.removeLastWord(""));
assertNull(parser.removeLastWord(null));
}
Proszę
zastanowić się dlaczego w metodzie testStringTrimmer() jest aż pięc
testów. Czy jest to wystarczająca ilość do dokładnego
przetestowania metody removeLastWord()?
3.4.
Do klasy
wumpus.CommandParserTest dopisać metodę testMemoryManagement
(assertSame).
public
void
testMemoryManagement()
{
CommandParser parser = new CommandParser();
parser.add("a", MoveEastCommand.class);
Command first = parser.getCommand("a");
Command second = parser.getCommand("a");
/*Sprawdzamy, czy
metoda getCommand oszczędnie gospodaruje pamięcią. Czyli, czy metoda
getCommand, zwracając to samo polecenie, zwraca ten sam obiekt, czy też
za każdym razem tworzy nowy obiekt. Ponieważ test ten dotyczy kwestii
wydajnościowych, które są drugorzędne, test opatrzono
komentarzem "This test doesn't have to pass". Komentarze, takie jak
ten, można stosować również w pozostałych metodach typu
assert. Komentarze w assertach usprawniają pracę z testami ułatwiając
identyfikowanie testu, który zawiódł, a czasami
również powodu dla którego test
zawiódł. Metoda assertSame sprawdza, czy będące parametrami
referencje wskazują na ten sam obiekt. Jeżeli wskazują na
różne obiekty, to zostanie rzucony wyjątek i test
zawiedzie.*/
assertSame("This test doesn't have to pass.", first, second);
}
- Przetestować klasę
wumpus.CommandParser.
4.1 Uruchomić testy jednostkowe.
Testy
jednostkowe uruchamia się wybierając Test Project z menu dostępnego pod
prawym przyciskiem myszy (i na kilka innych sposobów).
4.2 Poprawić błędy, jeżeli jakieś zostały wykryte podczas testowania.
Metodę
testującą testMemoryManagement() można wykomentować.
Natomiast pozostałe problemy należy rozwiązać.
- Sporządzić testy
jednostkowe dla klasy wumpus.Direction.
public
class DirectionTest extends TestCase
{
public DirectionTest(String testName)
{
super(testName);
}
/*Metoda
setUp jest metodą opcjonalną - można jej w klasie testowej nie
implementować. Ma ona za zadanie przygotować środowisko testowe. Jest
ona automatycznie wywoływana przed każą metodą testową. W prezentowanym
tu przykładzie metoda setUp spowoduje, że w każdej metodzie testowej
będzie stosowany ten sam generator liczb pseudolosowych (stosowany tu
generator losuje zawsze tą samą sekwencję liczb, czyli: 0 1 2 3 4; z
losowością nie ma więc nic wspólnego). Istnieje
symetryczna do setUp metoda o nazwie tearDown - tearDown jest
wywoływane automatycznie po każdej metodzie testowej. Służy do
sprzątania po testach.*/
protected void setUp() throws Exception
{
int seq[] = new int[]{0,1,2,3,4};
Direction.setRandomNumberGenerator(new
PseudoRandomNumberGenerator(seq));
}
/*
Test
of getRandomDirection method, of class wumpus.Direction.
*/
public void testGetRandomDirection()
{
assertEquals(Direction.NORTH,Direction.getRandomDirection());
assertEquals(Direction.SOUTH,Direction.getRandomDirection());
assertEquals(Direction.EAST,Direction.getRandomDirection());
assertEquals(Direction.WEST,Direction.getRandomDirection());
/*Znajdująca
się poniżej sekcja try-catch to standardowy test testujący rzucanie
wyjątku. Test ten zostanie zaliczony wtedy, kiedy
Direction.getRandomDirection() rzuci wyjątek. Natomiast wtedy, kiedy
wyjątek nie zostanie z Direction.getRandomDirection()
rzucony, to test nie przejdzie. Stanie się tak dlatego, że wywołana
zostanie wtedy metoda fail(), która bezwarunkowo powoduje
niezaliczenie testu.*/
try
{
Direction.getRandomDirection();
fail("no excpetion thrown");
}
catch(IllegalArgumentException e)
{
// expected
}
}
/**
*
Test of setRandomNumberGenerator method, of class wumpus.Direction.
*/
public void testSetRandomNumberGenerator()
{
}
/**
*
Test of makePleasantDirectionName method, of class wumpus.Direction.
*/
public void testMakePleasantDirectionName()
{
}
/**
*
Test of getOpposite method, of class wumpus.Direction.
*/
public void testGetOpposite()
{
}
}
Metody
o
pustych ciałach należy
uzupełnić, tak aby testowały odpowiadające im metody z klasy
wumpus.Direction (nazwy tych metod znajdują się w komentarzach).
- Poprawić błędy wykryte
przy pomocy testów z poprzedniego punktu.
- Opracować przypadek testowy metody
executeCommand() z klasy WumpusMain dla komendy "e".
Najistotniejszym elementem metody executeCommand() jest interakcja z
instancjami klas WumpusGame oraz CommandParser. Do przetestowania
przebiegu interakcji można użyć klas typu mock:
package
wumpus;
import junit.framework.TestCase;
import static org.easymock.EasyMock.*;
public class WumpusMainTest extends TestCase {
public WumpusMainTest(String testName) {
super(testName);
}
/**
* Test of executeCommand method, of class
WumpusMain.
*/
public void testExecuteCommand() {
String command = "e";
CommandParser parserMock =
createMock(CommandParser.class);
expect(
parserMock.getCommand(command) ) //mock w trybie konfigurowania
.andReturn( new MoveEastCommand() )
.times(1);
replay(parserMock); //mock przechodzi w tryb odtwarzania
WumpusMain main = new
WumpusMain( ... );
main.setCommandParser(parserMock);
main.executeCommand(command);
verify(parserMock); //weryfikujemy czy mialy m,iejsce
zadeklarowane wywoalnia metod
}
}
Przedstawiony przykład pokazuje jak użyć klasy typu mock do zastąpienia
instancji klasy CommandParser w teście sprawdzającym interakcje po
wykonaniu komendy "e" (przejście do wschodniej komnaty). Aby test ten
był wartościowy należy jeszcze w podobny sposób zastąpić instancję
klasy WumpusGame.
- Opracować przypadki testowe dla przynajmniej 2 innych,
obsługiwanych komend (np. "move e" i "shoot magic").
- Wyeliminować z opracowanego kodu powtórzenia.
- Opracować test jednostkowy dla metody configureCommandParser() z
klasy WumpusMain.
W testowanej metodzie występuje wiele, bardzo podobnych wywołań metod.
Proszę zastanowić się nad możliwościami zminimalizowania liczby linii
kodu w teście. Pomocne mogą być oferowane przez EasyMock
funkcjonalności związane z dopasowywaniem do zadanego wyrażenia
regularnego oraz metoda ustalająca krotność wywołania (times()).
- Wyeliminować powstałe powtórzenia kodu.
Warto rozważyć zastosowanie metody setUp().