[[oktatas:programozás|< Programozás]] ====== TDD ====== * **Szerző:** Sallai András * Copyright (c) 2018, Sallai András * Szerkesztve: 2018, 2019, 2020 * Licenc: [[https://creativecommons.org/licenses/by-sa/4.0/|CC Attribution-Share Alike 4.0 International]] * Web: https://szit.hu ===== A TDD ===== A **TDD** a **Test-driven development** rövidítése, magyarul **teszt-vezérelt fejlesztés**. Egy szoftverfejlesztési folyamat, amely nagyon rövid fejlesztési ciklusok ismétléséből áll. **Kent Beck** amerikai mérnök alkotta meg, vagy újra felfedezte. Tulajdonképpen a kód egy részét, ami jellemzően egy függvény, eljárás vagy metódus, teszteljük, újra és újra. A vizsgált részt egy egységként vagy angolul unitként szokás emlegetni. Így egységteszt vagy unit tesztnek is hívják. Két kódot fejlesztünk párhuzamosan: * teszt kód * ipari kód A fejlesztést a teszt megírásával kezdjük, akkor amikor még nem is áll rendelkezésre az ipari kód. A mai integrált fejlesztői környezetek támogatják a tesztek írását. Így könnyedén használhatjuk a teszt-vezérelt fejlesztésre. ===== A TDD folyamata ===== A tesztelést Java objektum orientált kódokkal tekintjük meg. Három osztályt készítünk. * főosztályt * teszt osztályt * hasznos kódot tartalmazó osztály A TDD lépései - fejlesztjük a tesztet - csak annyit fejlesztünk rajta, hogy az ne teljesüljön - futtatjuk a tesztet - fejlesztjük az ipari kódot - csak annyit fejlesztünk, hogy teljesüljön a teszt - futtatjuk a tesztet - újratervezünk, ha szükséges - tesztelünk - folytatjuk elölről az első ponttól {{:oktatas:programozas:tdd_3_resz.png|}} ===== Az Eclipse ===== Készítsünk egy projektet. A példa kedvéért legyen a neve sikidom. * File -> New -> Project... * Java -> Java Project * legyen a neve: sikidom Készítsünk egy csomagot: * File -> New -> Package * legyen a neve: sikidom Elkészítjük a tesztet: * File -> New -> JUnit Test Case Ha nincs a menüben a JUnit Test Case, akkor válasszuk a következőt: * File -> New -> Other... * JUnit -> JUnit Test Case * legyen a neve: HaromszogTest ==== Eredmény ==== package sikidom; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; class HaromszogTest { @Test void test() { fail("Not yet implemented"); } } ==== A teszt fejlesztése ==== package sikidom; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; class HaromszogTest { @Test void test() { Haromszog haromszog = new Haromszog(); } } ==== A teszt futtatása ==== * Run -> Run A teszt hibával ér végett, mivel nem létezik a Haromszog osztály. ==== Az ipari kód fejlesztése ==== Elkészítjük a Haromszog osztályt, de nem fejlesztjük tovább. * File -> New -> Class * legyen a neve: Haromszog package sikidom; public class Haromszog { } === Futtatjuk a tesztet === * Run -> Run * A teszt teljesült. * Következik a teszt továbbfejlesztése. ==== Teszt fejlesztése ==== Hívjuk meg a szamolKerulet() metódust, ami még nem létezik. Egyelőre paraméterekre sincs szüksége. package sikidom; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; class HaromszogTest { @Test void test() { Haromszog haromszog = new Haromszog(); haromszog.szamolKerulet(); } } Futtassuk a tesztet. A teszt nem teljesül. ==== Ipari kód fejlesztése ==== Annyit fejlesszünk a kódon, hogy teljesítse a tesztet. package sikidom; public class Haromszog { public void szamolKerulet() { } } ==== Teszt fejlesztése ==== package sikidom; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; class HaromszogTest { @Test void test() { Haromszog haromszog = new Haromszog(); double ker = haromszog.szamolKerulet(); } } ==== Ipari kód fejlesztése ==== package sikidom; public class Haromszog { public double szamolKerulet() { return 0; } } ==== Teszt fejlesztése ==== Jó lenne, ha a metódus fogadna paramétereket. package sikidom; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; class HaromszogTest { @Test void test() { Haromszog haromszog = new Haromszog(); double ker = haromszog.szamolKerulet(30, 35, 40); } } ==== Ipari kód fejlesztése ==== package sikidom; public class Haromszog { public double szamolKerulet(double a, double b, double c) { return 0; } } ==== Teszt fejlesztése ==== package sikidom; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; class HaromszogTest { @Test void test() { Haromszog haromszog = new Haromszog(); double ker = haromszog.szamolKerulet(30, 35, 40); assertEquals(105, haromszog.szamolKerulet(30, 35, 40)); } } A teszt sikertelen. Hiba ugyan nincs, de a teszt sikertelen, mivel nem 105 az eredményt, 30, 35 és 40 bemenő paraméterek esetén. ==== Ipari kód fejlesztése ==== A kódot egyszerű javítani, írjuk oda, hogy 105-tel térjen vissza. package sikidom; public class Haromszog { public double szamolKerulet(double a, double b, double c) { return 105; } } A teszt lefut. ==== Teszt fejlesztése ==== package sikidom; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; class HaromszogTest { @Test void test() { Haromszog haromszog = new Haromszog(); double ker = haromszog.szamolKerulet(30, 35, 40); assertEquals(105, haromszog.szamolKerulet(30, 35, 40)); assertEquals(75, haromszog.szamolKerulet(20, 25, 30)); } } A teszt újra sikertelen. A hiba csak 30, 35 és 40 bemenő paraméterek esetén működik. ==== Ipari kód fejlesztése ==== A kódot egyszerű javítani, írjuk oda, hogy 105-tel térjen vissza. package sikidom; public class Haromszog { public double szamolKerulet(double a, double b, double c) { return a + b + c; } } A teszt lefut. Ezek után jöhet a tesztelés szélsőértékekre. ==== Újratervezés ==== package sikidom; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; class HaromszogTest { @Test void szamolKeruletTest() { Haromszog haromszog = new Haromszog(); assertEquals(105, haromszog.szamolKerulet(30, 35, 40)); assertEquals(75, haromszog.szamolKerulet(20, 25, 30)); } } Kitöröltük a felesleges sort, mivel mindenképpen meghívjuk szamolKerulet() metódust. Mivel a test() metódus csak a szamolKerulet() metódust teszteli, ezért átnevezzük szamolKeruletTest() névre. ===== Netbeans ===== ==== Ipari kód osztálya ==== Netbeans alatt létre kell hoznunk a tesztelendő osztályt: /* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package hu.szit.sikidom; /** * * @author andras */ public class Haromszog { } ==== Tesztkód ==== A jobb egér gombbal kattintunk a Prejects fülön a projekt nevén. * New -> Other... * Az előugró ablakban: * Categories: Unit Tests * File Types: JUnit Test * Next > * Az újabb ablakban: * Class Name: HaromszogTest * Package: hu.szit.sikidom * Generated Code: * [ ] Test Initializer * [ ] Test Finalizer * [ ] Test Class Initializer * [ ] Test Class Finalizer * Generated Comments: * [ ] Source Code Hints * Finish gomb ==== Eredmény ==== /* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package hu.szit.sikidom; import org.junit.Test; import static org.junit.Assert.*; /** * * @author andras */ public class HaromszogTest { public HaromszogTest() { } } ==== Első teszt írása ==== /* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ import org.junit.Test; import static org.junit.Assert.*; /** * * @author andras */ public class HaromszogTest { public HaromszogTest() { } @Test public void test() { Haromszog har = new Haromszog(); har.szamitKerulet(); } } Futtassuk a tesztet: * Run -> Test Project (sikidom) ==== Ipari kód fejlesztése ==== Írjunk annyi kódot, ami miatt a teszt nem fut hibára. /* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package hu.szit.sikidom; /** * * @author andras */ public class Haromszog { public void szamitKerulet() { } } A teszt most már nem fut hibára. ==== Tervezzük újra ==== Jelen esetben semmi különöset nem csinálok, csak kiszedem a megjegyzéseket. package hu.szit.sikidom; public class Haromszog { public void szamitKerulet() { } } package hu.szit.sikidom; import org.junit.Test; import static org.junit.Assert.*; public class HaromszogTest { public HaromszogTest() { } @Test public void haromszogKeruletTest() { Haromszog har = new Haromszog(); har.szamitKerulet(); } } ==== A teszt fejlesztése ==== Jó lenne, ha adna vissza értéket szamitKerulet() metódus. @Test public void haromszogKeruletTest() { Haromszog har = new Haromszog(); double ker = har.szamitKerulet(); } Futtassuk a tesztet. ==== Ipari kód fejlesztése ==== Éppen csak annyira fejlesztjük, hogy teljesítse a tesztet. public class Haromszog { public double szamitKerulet() { return 0; } } Mentsünk és futtassuk a tesztet. ==== Újratervezés ==== Most nincs mit. ==== Teszt fejlesztése ==== Jó lenne, ha metódus fogadna bemenő paramétereket. @Test public void haromszogKeruletTest() { Haromszog har = new Haromszog(); double ker = har.szamitKerulet(30, 35, 40); } ==== Ipari kód fejlesztése ==== package hu.szit.sikidom; public class Haromszog { public double szamitKerulet(double a, double b, double c) { return 0; } } ==== Újratervezés ==== package hu.szit.sikidom; public class Haromszog { public double szamitKerulet(double aOldal, double bOldal, double cOldal) { return 0; } } ==== A teszt fejlesztése ==== package hu.szit.sikidom; import org.junit.Test; import static org.junit.Assert.*; public class HaromszogTest { public HaromszogTest() { } @Test public void haromszogKeruletTest() { Haromszog har = new Haromszog(); double ker = har.szamitKerulet(30, 35, 40); assertEquals(105, har.szamitKerulet(30, 35, 40), 0); } } ==== Ipari kód fejlesztése ==== Ha 0 helyett 105-el térünk vissza, a teszt már teljesül: package hu.szit.sikidom; public class Haromszog { public double szamitKerulet(double aOldal, double bOldal, double cOldal) { return 105; } } ==== Újratervezés ==== Az alábbi sort töröljük, mivel helyettesíti az assertEquals()-t tartalmoaz sor: double ker = har.szamitKerulet(30, 35, 40); package hu.szit.sikidom; import org.junit.Test; import static org.junit.Assert.*; public class HaromszogTest { public HaromszogTest() { } @Test public void haromszogKeruletTest() { Haromszog har = new Haromszog(); assertEquals(105, har.szamitKerulet(30, 35, 40), 0); } } ==== A teszt fejlesztése ==== package hu.szit.sikidom; import org.junit.Test; import static org.junit.Assert.*; public class HaromszogTest { public HaromszogTest() { } @Test public void haromszogKeruletTest() { Haromszog har = new Haromszog(); assertEquals(105, har.szamitKerulet(30, 35, 40), 0); assertEquals(75, har.szamitKerulet(20, 25, 30), 0); } } ==== Ipari kód fejlesztése ==== package hu.szit.sikidom; public class Haromszog { public double szamitKerulet(double aOldal, double bOldal, double cOldal) { return aOldal+bOldal+cOldal; } } ==== Újratervezése ==== Most nem teszünk semmit. ==== Teszt fejlesztése ==== package hu.szit.sikidom; import org.junit.Test; import static org.junit.Assert.*; public class HaromszogTest { public HaromszogTest() { } @Test public void haromszogKeruletTest() { Haromszog har = new Haromszog(); assertEquals(105, har.szamitKerulet(30, 35, 40), 0); assertEquals(75, har.szamitKerulet(20, 25, 30), 0); } @Test(expected = IllegalArgumentException.class) public void haromszogKeruletException() { Haromszog har = new Haromszog(); har.szamitKerulet(0, 25, 30); } } JUnit5 már lehetővé teszi: assertThrows(IllegalArgumentException.class, har.szamitKerulet(0, 25, 30)); ==== Ipari kód fejlesztése ==== package hu.szit.sikidom; public class Haromszog { public double szamitKerulet(double aOldal, double bOldal, double cOldal) { if(aOldal <= 0) { throw new IllegalArgumentException("Nem megfelelő paraméter"); } return aOldal+bOldal+cOldal; } } ===== Link ===== JUnit5 API: * https://junit.org/junit5/docs/5.0.1/api/org/junit/jupiter/api/Assertions.html (2020) Kivétel hiánya a JUnit5-ben: * https://howtodoinjava.com/junit5/expected-exception-example/ (2020)