[[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)