TDD czyli programowanie sterowane testami

Każdy kto w programistycznych kręgach obraca się pewien czas, prędzej lub później natknął się na pojęcie Test driven development/programming. Jest to tzw. programowanie sterowane testami – metodologia (metoda?) programowania opierająca się na idei, iż przed napisaniem kodu należy utworzyć stosowne testy, które umożliwią sprawdzenie działania kodu, który dopiero zamierzamy napisać. Zatem jajko czy kura?

Po więcej szczegółów zapraszam do wikipedii. Metoda ta potrafi być naprawdę przydatna i pozwala na pisanie lepszego kodu, a także na sprawniejsze nim zarządzanie. W tym wpisie zaprezentuję wzięty z życia przykład TDD, a także mini-HOWTO szkieletu JUnit NetBeans. Zatem do dzieła.

Do napisania jest kawałek kodu, którego celem jest:

– otworzenie pliku tekstowego

– wgranie jego zawartości do zmiennej

– wyświetlenie zawartości na ekran

Rzecz prosta i zasadniczo głównie to, co może się zakończyć niepowodzeniem to pierwszy punkt, co zresztą implikuje powodzenie lub niepowodzenie działań z punktów kolejnych. Mamy taką listę rzeczy do zrobienia, zatem przed napisaniem kodu zastanówmy się co może pójść nie tak:

– plik może nie istnieć

– możemy nie mieć uprawnień do jego odczytu

– plik może okazać się po prostu pusty

– wczytanie z pliku może zakończyć się niepowodzeniem

W naszym projekcie klasy realizującej odczyt z pliku dzięki powyższej liście rysuje się wygląd poszczególnych metod, choć w zasadzie to potrzebujemy raptem jedną 🙂  Możnaby stworzyć kilka metod, które wykonywałyby się jedna po drugiej, uzależniając swe wykonanie od powodzenia poprzedniej. Czyli mielibyśmy jakieś fileExists(), potem pewnie isReadable(), zaś na końcu ReadFile() i być może jeszcze fileClose(). Kilka z tych metod zaimplementujemy.

Najbardziej popularnym szkieletem (frameworkiem) używanym do przeprowadzania testów jednostkowych, jest JUnit. Standardowo jest on dodawany do NetBeans, zatem stworzenie testów dla projektu tworzonego w tym IDE jest naprawdę szybkie i bezbolesne. W saym JUnit wszystkie klasy testujące inne klasy, muszą dziedziczyć po klasie TestCase z tegoż frameworka (to dla wersji 3.x). NetBeans umożliwia automatyczne wygenerowanie testów dla już istniejącej klasy w zaledwie kilka chwil. Tutaj mała uwaga. Istnieją różnice między kolejnymi wersjami JUnitNetBeans w wersji 6.1, na której pracuję dawał możliwość wyboru pomiędzy stylami 3.x i 4.x.  Różnica jest zasadniczo dość istotna – w wersji 3.x każda klasa testująca musi dziedziczyć swoją funkcjonalność po klasie TestUnit, a także trzymać się konwencji nazewniczych (dodawania suffixu -Test do nazwy każdej metody). W wersji 4.x wykorzystywany jest mechanizm asercji , więcej na temat różnic pomiędzy wersją 3.x, a 4.x można znaleźć tutaj.

Wiadomo już zatem o co mniej więcej się rozchodzi. Napiszmy zatem klasę, która zawiera interesujące nas metody:

public class ChlebikConfigChangelog {

private StringBuilder content = new StringBuilder();
private FileReader filereader;

public void loadFile(String fileName) throws FileNotFoundException
{
filereader = new FileReader(fileName);
}

public String readFile() throws IOException
{

String s;
BufferedReader br = new BufferedReader(filereader);

while((s = br.readLine()) != null)
{
System.out.println(s);
content.append(s + "\n");
}
filereader.close();

if( content.length() > 0 )
{
return content.toString();
}
else
{
throw new IOException();
}

}

}

Kod jest prosty jak konstrukcja gwoździa. Konstruktor ma za zadanie otworzyc plik poprzez stworzenie obiektu FileReader. Jeśli plik nie istnieje metoda fileExists wyrzuca wyjątek. Mamy też metodę ReadFile, która przechodzi po pliku i wczytuje do zmiennej jego treść, po czym zwraca ten łańcuch, albo też wyrzuca wyjątek jeśli okazało się, że plik był pusty. Oczywiście napisaliśmy wpierw kod, a dopiero teraz stworzymy dla niego testy. Powinno się to odbywać zasadniczo w drugą stronę, ale pragnę pokazać to na jaskrawym przykładzie. Samego pisania testów przed programowaniem trzeba się nauczyć samemu poprzez praktykę.

Testy w NetBeans tworzy się bardzo łatwo. Wystarczy kliknąć prawym przyciskiem na wybranej klasie, wybrać Tools i Create JUnit Test. Wypada zapoznać się z możliwymi opcjami generowania klasy testującej. U mnie takowa wygląda po utworzeniu następująco:

public class ChlebikConfigChangelogTest {

public ChlebikConfigChangelogTest() {
}

@BeforeClass
public static void setUpClass() throws Exception {
}

@AfterClass
public static void tearDownClass() throws Exception {
}

@Test
public void testLoadFile() throws Exception {
System.out.println("loadFile");
String fileName = "";
ChlebikConfigChangelog instance = new ChlebikConfigChangelog();
instance.loadFile(fileName);
fail("The test case is a prototype.");
}

@Test
public void testReadFile() throws Exception {
System.out.println("readFile");
ChlebikConfigChangelog instance = new ChlebikConfigChangelog();
String expResult = "";
String result = instance.readFile();
assertEquals(expResult, result);
fail("The test case is a prototype.");
}

}

I teraz do ciała każdej z metod testujących należy wrzucić kod, który przetestuje metodę. By należycie przetestować metodę readFile można zaproponować taki oto kod (modyfikując ten wygenerowany automatycznie):

@Test
public void testReadFile() throws Exception {

ChlebikConfigChangelog instance = new ChlebikConfigChangelog();

// Jesli tresc pliku to 'test'
instance.loadFile("test.txt");
String result = instance.readFile();
assertEquals("test", result);

}

@Test(expected=IOException.class)
public void testEmptyReadFile() throws IOException {

// Jesli tresc pliku nie istnieje (jest pusta)
System.out.println("readFile");
ChlebikConfigChangelog instance = new ChlebikConfigChangelog();
instance.loadFile("/test2.txt");
String result = instance.readFile();
}

Ta druga metoda służy do badania wyrzucanych wyjątków. Zastosowanie asercji @Test(excepcted=tunazwaklasywyjatku.class) buduje test (metodę), której wywołanie niezakończone wyrzuceniem wyjątku będzie uznawane za porażkę testu. Jest to bardzo użyteczny sposób na ich testowanie.

Mam nadzieję, że ten krótki przykład da podstawowe pojęcie o testach jednostkowych i TDD. Jak już wcześniej wspomniałem by w pełni wykorzystać zalety tego podejścia należy robić jedną rzecz – testować, testować, testować, a wówczas TDD stanie się naturalną drogą tworzenia oprogramowania.

Advertisements

2 thoughts on “TDD czyli programowanie sterowane testami

  1. Pingback: TDD na przykładzie Grails - klasy domenowe i ich reguły « Stronnice Chlebika

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s