Testowanie jednostkowe Jacek Starzyński, ZETiIS PW
Testowanie Po co testować? Co testować? Kiedy testować? Jak testować? Narzędzia
Po co testować? Testy nie udowadniają poprawności......ale pozwalają wykryć błędy Testy są ważnym narzędziem przyrostowej budowy oprogramowania Testy są niezbędnym narzędziem konserwacji oprogramowania
Co testować? Konstrukcję klasy testy jednostkowe Architekturę systemu testy integracji Wypełnianie wymagań testy systemu Zachowanie systemu testy integracji systemu Nerwy zamawiającego testy akcepujące
Co testować? Źródłó: http://www.methodsandtools.com/archive/archive.php?id=13
Kiedy testować? Jak najwcześniej Projektowanie testów razem z testowanym kodem (a nawet wcześniej) pozwala dopracować funkcjonalność kodu. Wczesne testowanie jest łatwiejsze i szybsze. Jak najczęściej Zwłaszcza przy zmianach w kodzie częste testy są łatwiejsze i skuteczniejsze.
Jak testować? Automatycznie czy ręcznie? Automatycznie = łatwo => często ręczne testowanie może być bardziej wszechtronne, ale bywa zawodne ludzie są leniwi. Zwinnie, czy tradycyjnie? Eksploracyjnie czy skryptowo?
Narzędzia Odpowiednia składnia języka np. metoda main w Javie Biblioteki do testowania jednostkowego xunit: SUnit, JUnit,... JUnitX JTiger TestNG Narzędzia do zarządzania kodem
Biblioteka JUnit Narzędzie do testowania jednostkowego w Javie Stworzona przez K. Becka i E. Gammę jest najbardziej (u zna)danym członkiem rodziny xunit Stosunkowo stara i niewygodna... ale ciągle jeszcze bardzo popularna
JUNIT podstawowe klasy Test <<interface>> TestResults TestCase TestSuite MyTest
JUNIT przykład import java.util.*; public class MathUtl { public double avr( List<Double> l ) { double s= 0; for( double x : l ) s+= l; return s / l.size(); import junit.framework.*; import java.util.*; public class MathUtlTest extends TestCase { public void testaverage2 () { Vector<Double> nums = new Vector<Double>(); nums.add(3.0); nums.add(13.5); asserttrue( MathUtl.avr(nums) == 8.25);
Struktura katalogów ` -- src `-- contact -- BusinessContact.java -- Contact.java -- ExGui.form -- ExGui.java -- PhoneBook.java `-- PhoneBookUtils.java `-- test `-- contact -- BusinessContactTest.java -- ContactTest.java `-- PhoneBookTest.java
JUNIT kompilacja i uruchamianie $ javac -cp /opt/junit/org.junit_3.8.1/junit.jar MUT.java MathUtl.java $ java -cp /opt/junit/org.junit_3.8.1/junit.jar:. junit.textui.testrunner MUT... Time: 0,006 OK (3 tests) $ java -cp /opt/junit/org.junit_3.8.1/junit.jar:. junit.swingui.testrunner MUT
JUNIT struktura klasy testującej konstruktor jest zwykle pomijany metoda setup() służy do inicjalizacji i jest wywoływana przed każdym testem metoda teardown() sprząta i jest wywoływana po każdym teście przypadki testowe są implementowane w postaci metod testcośtam() public class MyTest extends TestCase { void setup() {... void teardown() {... void testone() {...... void testxxx() {...
JUNIT metoda testująca public class JOWTest extends TestCase { private JOW jow = null; public void setup() { jow= new JOW( "Warszawa", 2000000 ); public void testtostring() { String s= jow.tostring(); assertequals( "JOW Warszawa: liczba wyborców: 2000000, głosowało: 0", s ); public void teardown() { jow= null;
Rodzaje assercji asserttrue( [komunikat], warunek ) assertfalse( [komunikat], warunek ) assertnull( [komunikat], referencja ) assertnotnull( [komunikat], referencja ) assertsame( [komunikat], oczekiwana, faktyczna ) assertnotsame( [komunikat], oczekiwana, faktyczna ) assertequals( [komunikat], oczekiwana, faktyczna ) assertequals( [komunikat], oczekiwana, faktyczna,delta ) assertnotequals( [komunikat], oczekiwana, faktyczna ) fail( [komunikat] )
Inicjowanie obiektów public class JOWTest extends TestCase { Nie w konstruktorze! private JOW jow = null; public JOWTest( String testname ) { super( testname ); jow = new JOW( null, 0 );... Obiekt jow nie zostanie utworzony (w wyniku zgłoszenia wyjątku przez konstruktor klasy JOW). Wyjątek zostanie przechwycony przez środowisko Junit, ale komunikat będzie niepełny, a więc mylący.
Inicjowanie obiektów public class JOWTest extends TestCase { W metodzie setup() private JOW jow = null; public setup( ) { jow = new JOW( null, 0 );... Zgłoszenie wyjątku zaowocuje prawidłowym, jasnym komunikatem. java cp /opt/eclipse/plugins/org.junit_3.8.1/junit.jar:. junit.textui.testrunner JOWTest.E Time: 0,021 There was 1 error: 1) testtostring(jowtest)java.lang.illegalargumentexception: JOW: Złe wywołanie konstruktora at JOW.<init>(JOW.java:8) at JOWTest.setUp(JOWTest.java:8) FAILURES!!! Tests run: 1, Failures: 0, Errors: 1
Kolejność wykonywania testów public class JOWTest extends TestCase { testthisbefore() {... To nie zadziała testthisafter() {... - Przypadki testowe są wykonywane w losowej kolejności - Nie mogą miec efektów ubocznych
Kolejność wykonywania testów public class JOWTest extends TestCase { public JOWTest( String name ) { super( name ); public void testjeszczepozniej() { System.out.println( "JeszczePozniej" ); public void testpotem() { System.out.println( "Potem" ); public void testnajpierw() { System.out.println( "Najpierw" ); public static Test suite() { TestSuite suite = new TestSuite(); suite.addtest( new JOWTest( "testnajpierw" ) ); suite.addtest( new JOWTest( "testpotem" ) ); suite.addtest( new JOWTest( "testjeszczepozniej" ) ); return suite;...
Zewnętrzne zasoby public class MyClassTest extends TestCase {... public void setup() { InputStream data= new FileInputStream( "/var/lib/testdata.txt" );...... Brak danych testowych (np. w innym środowisku) może generować mylące komunikaty.
Wewnętrzne zasoby public class MyClassTest extends TestCase {... Dane w kodzie testującym private double danetestowe[] = { 1., 3., 5., 7., 13. ; public void setup() { InputStream data= this.getclass().getresourceasstream( "mojedanetestowe.dat" );...... Dane pobierane za pośrednictwem class loadera
Testy powinny być niezależne od kontekstu public class MyClassTest extends TestCase {... public void testlocale() { Locale l= Locale.getDefault(); assertequals( "Fri Jun 08 09:07:09 GMT 2007", new Date().toString() );... Nie można oczekiwac określonej daty, czy nawet jej formatu
Testy nie powinny obsługiwać wyjątków public class MyClassTest extends TestCase {... public void testtestnulldata() { try { myclassobject.method( null, null ); catch( NullPointerException e ) { fail( "Przechwycono wyjątek" );... Przechwycenie wyjątku nie pozwala zdiagnozować jego przyczyny
Testowanie wyjątków public class MyClassTest extends TestCase {... public void testtestnulldata() { try { myclassobject.method( null, null ); fail( "Oczekiwano NullPointerException" ); catch( NullPointerException e ) { // jest ok!... Wyjątek powinien być zgłoszony, a więc jego brak jest błędem.
Testowanie wyjątków public class MyClassTest extends TestCase { public void testnulldata() { myclassobject.method( null, null ); public static Test suite() { TestSuite s= new testsuite(); s.addtest( new ExceptionTestCase( "testnulldata", NullPointerException.class ); return s; Specjalna klasa dimplementująca logikę odwracania wyjątku.
Wady Junit 3.x konieczność dziedziczenia po TestCase sztywne konwencje nazewnicze niewygodne kontrolowanie kolejności testów tylko wspólna inicjacja i finalizacja ograniczona funkcjonalność
Rozszerzenia JUnitX nacisk na testowanie składowych prywatnych JUnit 4.0 wykorzystanie nowych możliwości Javy 5.0 TestNG oddzielna od implementacji konfiguracja testów
JUnitX - Testowanie metod (http://www.extreme-java.de/junitx/) prywatnych public class JOWTest extends PrivateTestCase {... public void testglosuje() throws TestAccessException { jow.glosuje( 100 ); int g= getint( jow, "glosowalo" ); assertequals( 100, g ); public class JOW { private String nazwa; private int liczbawyborcow; private int glosowalo;...
JUnitX uruchamianie testów import junit.framework.*; import junitx.framework.*; public class TestPackage implements junitx.framework.testpackage { static public Test suite () { TestSuite suite = new TestSuite ("JOW tests"); suite.addtestsuite (JOWTest.class); return suite;
JUnitX pośrednik import java.lang.reflect.*; import junit.framework.*; import junitx.framework.*; public class TestProxy extends junitx.framework.testproxy { public Object newinstance (Object[] anarglist) throws TestAccessException { try { return getproxiedclass ().getconstructor ( anarglist).newinstance (anarglist); catch (Exception e) { throw new TestAccessException ( "could not instantiate " + gettestedclassname (), e);...
klasy testowe to POJO wykorzystanie anotacji JUnit 4.0 metody inicjujące/sprzątające związane z konkretnym testem wbudowana obsługa wyjątków sterowanie zbiorem wykonywanych testów obsługa limitów czasowych kompatybilność z JUnit 3.x (dwustronna)
Zastosowanie JUnit 4.x import org.junit.*; import static org.junit.assert.*; public class JOWT4 { private JOW jow = null; @Test public void testtostring() { String s= jow.tostring(); assertequals(... );...... @Before public void makejow() { jow= new JOW(... ); @After public void destroyjow() { jow= null;
Kompilacja i uruchomienie testów $ javac -cp junit4.3.1/junit-4.3.1.jar JOW.java JOWT4.java $ java -cp junit4.3.1/junit-4.3.1.jar:. org.junit.runner.junitcore JOWT4 JUnit version 4.3.1... Time: 0,052 OK (3 tests)
Anotacje Junit 4 @BeforClass @AfterClass @Ignore @Test(timeout=1000) @Test(expected=MyException)
http://testng.org/ TestNG Testing, the Next Generation... klasy testowe to POJO wykorzystanie anotacji metody inicjujące/sprzątające związane z konkretnym testem, grupą, klasą parametryzacja przypadków testowych zależności pomiędzy przypadkami
... http://testng.org/ wbudowana obsługa wyjątków TestNG Testing, the Next Generation sterowanie zbiorem wykonywanych testów, także przez zewnętrzny plik wykorzystanie programowych asercji Javy obsługa limitów czasowych
@Test wybrane parametry: Anotacje TestNG alwaysrun (nawet, gdy zależy od metody, która padła) dataprovider (skąd dane) dataproviderclass (gdzie szukać dataprovidera) dependsongroups (lista grup) dependsonmethods (lista metod) description enabled expectedexceptions groups timeout
Konfiguracyjne anotacje TestNG @BeforeSuite @AfterSuite @BeforeTest @AfterTest @BeforeGroups @AfterGroups @BeforeClass @AfterClass @BeforeMethod @AfterMethod Parametry: alwaysrun dependsongroups dependsonmethods enabled groups inheritgroups
Inne anotacje TestNG @DataProvider -> Object[][] parametr name @Factory -> Object[] @Parameters parametr value
Przykład @DataProvider(name = "test1") public Object[][] createdata1() { return new Object[][] { { "Cedric", new Integer(36), { "Anne", new Integer(37), ; @Test(dataProvider = "test1") public void verifydata1(string n1, Integer n2) { System.out.println(n1 + " " + n2);
Uruchamianie testów z linii poleceń java org.testng.testng testng1.xml [testng2.xml testng3.xml...] przy pomocy anta przez IDE / pluginy do IDE
Konfiguracja testów public class Test1 { @Test(groups = { "functest", "checkintest" ) public void testmethod1() { @Test(groups = {"functest", "checkintest" ) public void testmethod2() { @Test(groups = { "functest" ) public void testmethod3() { <test name="test1"> <groups> <run> <include name="functest"/> </run> </groups> <classes> <class name="example1.test1"/> </classes> </test>
Konfiguracja testów, cd @Test public class Test1 { @Test(groups = { "windows.checkintest" ) public void testwindowsonly() { @Test(groups = {"linux.checkintest" ) public void testlinuxonly() { @Test(groups = { "windows.functest" ) public void testwindowstoo() { <test name="test1"> <groups> <run> <include name="windows.*"/> </run> </groups> <classes> <class name="example1.test1"/> </classes> </test>
Konfiguracja testów, cd @Parameters({ "first-name" ) @Test public void testsinglestring(string firstname) { System.out.println("Invoked teststring " + firstname); assert "Cedric".equals(firstName); <suite name="my suite"> <parameter name="first-name" value="cedric"/> <test name="simple example"> <--... -->
Ćwiczenie Proszę pobrać niezbędne pliki z http://www.iem.pw.edu.pl/~jstar/dyd/java/junit/ i napisać klasę testową dla JOW z wykorzystaniem Junit3.8, Junit4.3, JUnitX. Przykładowe szkielety do JunitX i Junit4 są dostępne w katalogu podanym wyżej.