background image

 

 

Testowanie i logowanie

1. Testowanie aplikacji

 JUnit.

2. Tworzenie logów:

 pakiet java.util.logging.

1

background image

 

 

JUnit

JUnit jest platformą umożliwiającą tworzenie testów i testowanie programów 

napisanych w Javie. JUnit jest projektem Open Source, rozpowszechnianym 

na licencji Common Public License Version 1.0. Strona domowa projektu to 

http://www.junit.org

. JUnit jest rozprowadzany w postaci archiwum 

zawierającego odpowiednią bibliotekę, przykłady wykorzystania pakietu oraz 

kod źródłowy. JUnit jest także dołączony do środowiska deweloperskiego 

Eclipse.

2

background image

 

 

Przykładowa testowana klasa

class Money {

private int fAmount;
private String fCurrency;

public Money(int amount, String currency) {

fAmount = amount;
fCurrency = currency;

}

public int getAmount() {

return fAmount;

}

public String getCurrency() {

return fCurrency;

}

}

3

background image

 

 

Piszemy metody testujące

public class MoneyTest extends TestCase {
    private Money f12CHF;
    private Money f14CHF;   

   // inicjacja pakietu testów

protected void setUp() {

        f12CHF= new Money(12, "CHF");
        f14CHF= new Money(14, "CHF");
    }

// pierwsza z metod testujących
public void testGetters() {

Assert.assertEquals(this.f12CHF.getAmount(), 12);
Assert.assertEquals(this.f12CHF.getCurrency(), "CHF");

}

}

4

background image

 

 

Metody publiczne z klasy Assert

public static void assertTrue(String message, boolean 
condition)

Sprawdza czy 

condition

 jest równe 

true

. Jeśli nie zgłaszany jest wyjątek 

AssertionFailedError

 zawierający 

message

.

public static void assertTrue(boolean condition)
public static void assertFalse(...)

public static void fail(...)

public static void assertEquals(...)

public static void assertNotNull(...)

public static void assertNull(...)

public static void assertSame(...)

Sprawdza czy podane referencje to te same obiekty

public static void assertNotSame(...)

5

background image

 

 

Piszemy metody testujące cd.

Chcemy żeby była możliwość dodawania dwóch obiektów typu  

Money

. Zanim 

napiszemy metodę 

add()

 w klasie 

Money

 określimy jej własności dopisując do 

zestawu 

MoneyTest

 odpowiedni test.

public void testAdd() {

Money expected = new Money(26, "CHF");
Money result = this.f12CHF.add(f14CHF);
Assert.assertTrue(expected.equals(result));

}

Ale jak powinna działać metoda 

equals()

 wywołana na rzecz instancji klasy 

Money

?

6

background image

 

 

Piszemy metody testujące cd.

Metoda 

equals()

 powinna działać tak, aby między innymi spełnić poniższy 

test.

public void testEquals() {

Money m12CHF= new Money(12, "CHF");
Money m14CHF= new Money(14, "CHF");

Assert.assertTrue(!m12CHF.equals(null));
Assert.assertEquals(m12CHF, m12CHF);
Assert.assertEquals(m12CHF, new Money(12, "CHF"));
Assert.assertTrue(!m12CHF.equals(m14CHF));

}

Dwie instancje 

Money

 są uważane za równe gdy równa jest waluta i kwota.

7

background image

 

 

Dopisujemy kod do testowanej klasy

W klasie 

Money

 dodajemy następujące metody:

// dodawanie dwóch instancji klasy Money
public Money add(Money m) {

return new Money(getAmount()+m.getAmount(),

getCurrency());

}

// porównanie dwóch instancji klasy Money
public boolean equals(Object anObject) {

if (anObject instanceof Money) {

Money aMoney = (Money)anObject;
return aMoney.getCurrency().equals(getCurrency())

&& (getAmount() == aMoney.getAmount());

}
return false;

}

8

background image

 

 

Zanim uruchomimy test

Aby uruchomić zestaw testów należy:

 określić sposób uruchomienia testu,

 określić sposób uruchomienia zestawu testów.

JUnit umożliwia uruchamianie poszczególnych testów na dwa sposoby: 

statyczny i dynamiczny.

Statyczne

 wywołanie testu następuje poprzez nadpisanie metody 

runTest

 

dziedziczonej po klasie 

TestCase

 i uruchomienie w niej określonego testu. 

Przykład:

TestCase test = new MoneyTest("test dodawania"){

public void runTest() {

testAdd();

}

};

Każdy test musi posiadać nazwę!

9

background image

 

 

Zanim uruchomimy test

Dynamiczne wywołanie testu polega na skorzystaniu z mechanizmu Java 

Reflection w celu uruchomienia danego testu. Nazwa testu jest tożsama z 

nazwą metody realizującej test. Aby wywołać metodę testAdd() konstruujemy 

test następująco:

TestCase test = new MoneyTest("testAdd");

Wywołanie dynamiczne jest prostsze, jednak mniej bezpieczne jeśli chodzi o 

kontrole typów. W przypadku braku podanej metody zwracany jest wyjątek 

NoSuchMethodException

.

10

background image

 

 

Zestawy testów

Aby zdefiniować zbiór testów należy zdefiniować publiczną statyczną metodę 

suite():

public static Test suite() {
    TestSuite suite= new TestSuite();
    suite.addTest(new MoneyTest("testEquals"));
    suite.addTest(new MoneyTest("testAdd"));
    return suite;
}

Wewnątrz tej metody tworzymy obiekt 

TestSuite

  zawierający metody 

obsługujące przeprowadzenie testu. 

TestSuite

 i 

TestCase

 implementują 

interfejs Test zawierający metody obsługujące przeprowadzenie testu.

Istnieje także (od wersji 2.0) możliwość dynamicznego stworzenia zestawu w 

oparciu o klasę zawierającą testy:

public static Test suite() {
    return new TestSuite(MoneyTest.class);
}

11

background image

 

 

Zestawy testów

Statyczna wersja kodu tworząca zestaw:

public static Test suite() {

TestSuite suite = new TestSuite();
suite.addTest(

new MoneyTest("money equals") {

protected void runTest() { 

testEquals(); 

}

}

);

suite.addTest(

new MoneyTest("simple add") {

protected void runTest(){

testAdd();

}

}

);
return suite;

}

12

background image

 

 

Uruchomienie testów

Aby uruchomić testy wpisujemy komende:

java -cp junit.jar:money.jar junit.swingui.TestRunner MoneyTest

13

background image

 

 

JUnit i Eclipse

14

background image

 

 

JUnit i Eclipse

15

background image

 

 

Java Logging API

Logowanie przede wszystkim stosuje się aby umożliwić zdiagnozowanie 

problemu przez:

 użytkownika końcowego lub „lokalnego administratora”,

 pomoc techniczną,

 producenta oprogramowania,

 programistów w trakcie tworzenia i rozwoju oprogramowania.

Kolejne grupy użytkowników zwykle potrzebują coraz więcej informacji. 

Nie należy zastępować zwykłego debugowania przez logowanie ponieważ 

prowadzi to do znacznego rozrostu kodu programu. Ma to szczególnie istotne 

znaczenie w przypadku oprogramowania rozprowadzanego przez sieć.

Klasy obsługujące logowanie są zgrupowane w pakiecie 

java.util.logging

.

http://java.sun.com/j2se/1.4.2/docs/guide/util/logging/overview.html

. Inne 

popularne rozwiązanie log4j: 

http://logging.apache.org/log4j/docs/

.

16

background image

 

 

Java Logging API

17

Najważniejsze z klas obsługujących logowanie to:

 

Logger

 – klasa używana przez aplikacje do wywoływania żądań logowania.

 

LogRecord

 – zawiera dane określające pojedynczy wpis w logu.

Dane zapisywane w obiekcie 

LogRecord

 dostępne przez  metody publiczne:

 poziom – 

Level getLevel()

 (np. 

Level.SEVERE

),

 nazwa użytego obiektu typu 

Logger

 – 

String getLoggerName()

,

 wiadomość do zalogowania (przed ew. formatowaniem i lokalizacją) – 

String 

getMessage()

,

 czas: liczba milisekund od 01.01.1970 – 

long getMillis()

,

 dodatkowe parametry – 

Object[] getParameters()

,

 numer kolejny – 

long getSequenceNumber()

,

 nazwa klasy, w której wywołano logger'a – 

String getSourceClassName()

,

background image

 

 

Java Logging API

18

 nazwa metody, w której wywołano logger'a – 

String 

getSourceMethodName()

,

 identyfikator wątku, w którym wywołano logger'a – 

int getThreadID()

,

 wyjątek związany z logowaną wiadomością – 

Throwable getThrown()

.

 

Handler

 – eksportuje obiekty 

LogRecord

 do różnych miejsc docelowych np: 

pamięci, strumieni wyjścia, konsoli, plików, gniazd sieciowych. Standardowo 

dostępne są następujące typy handler'ow:

background image

 

 

Java Logging API

19

 

Level

 - określa zbiór standardowych poziomów logowania. Program może mieć 

przypisane różne poziomy dla różnych urządzeń odbierających komunikaty. 

Poziomy zdefiniowane w klasie 

Level

:

 SEVERE - 1000

 WARNING- 900

 INFO

- 800

 CONFIG - 700

 FINE

- 500

 FINER

- 400

 FINEST - 300

Dodatkowo zostały zdefiniowane dwa poziomy: 

OFF

 (

Integer.MAX_VALUE

) i 

ALL

 (

Integer.MIN_VALUE

), który pozwalają wyłączyć logowanie lub logować 

wszystkie komunikaty. Można tworzyć własne poziomy pisząc podklasy 

java.util.logging.Levels

.

background image

 

 

Java Logging API

20

 

Filter

 – umożliwia dodatkową, precyzyjniejszą kontrolę logowanych 

wiadomości niż ta zapewniona przez klase 

Level

 

Formatter

 – wspiera formatowanie informacji zapisanych w obiekcie 

LogRecord

. Standardowodostępne są dwie klasy formatujące: 

 

SimpleFormatter

  - zapisuje informacje w typowej formie tekstowej

 

XMLFormatter

 – umożliwia tworzenie logów w formacie XML.

background image

 

 

Prosty przykład

public class LoggingSample{

private static Logger logger =

Logger.getLogger("LoggingSample");

public static void inner(){

logger.info("jestem w srodku");

}

public static void main(String argv[]){

logger.info("zaczynam");
logger.fine("jestem gadatliwy");
try {

Thread.sleep(1000);
inner();
Thread.sleep(1000);

} catch (InterruptedException e) {

logger.log(Level.WARNING, "sleep", e);

}
logger.warning("programista sie pomylil");
logger.finer("jestem bardziej gadatliwy");
logger.info("zrobione");

}

}

21

background image

 

 

Prosty przykład

Efekt działania – tekst w konsoli:

2006-02-28 16:27:47 LoggingSample main
INFO: zaczynam
2006-02-28 16:27:48 LoggingSample inner
INFO: jestem w srodku
2006-02-28 16:27:49 LoggingSample main
WARNING: programista sie pomylil
2006-02-28 16:27:49 LoggingSample main
INFO: zrobione

Brak logowania wiadomości na poziomie 

FINE

! Sposób działania logowania jest 

określony w pliku 

logging.properties

, znajdującym się w podkatalogu 

lib

 

środowiska JRE.

22

background image

 

 

logging.properties

# Global properties
handlers= java.util.logging.ConsoleHandler
.level= INFO
# Handler specific properties.
java.util.logging.FileHandler.pattern = %h/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = 
java.util.logging.XMLFormatter

java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = 
java.util.logging.SimpleFormatter

# Facility specific properties.
# Provides extra control for each logger.
com.xyz.foo.level = SEVERE 

Podmiana pliku jest możliwa poprzez podanie odpowiedniego argumentu dla 
polecenia 

java

:

-Djava.util.logging.config.file=myfile 

23

background image

 

 

Konfiguracja dynamiczna

public class LoggingHandlerSample extends LoggingSample{

public static void main(String[] args){

try {
    Handler fh1 = new FileHandler("log.txt");
    fh1.setLevel(Level.WARNING);

    Handler fh2 = new FileHandler("logSimple.txt");
    fh2.setFormatter(new SimpleFormatter());
    fh2.setLevel(Level.ALL);

    Handler fh3 = new FileHandler("logXML.txt");
    fh3.setLevel(Level.INFO);
    fh3.setFormatter(new XMLFormatter());

    logger.addHandler(fh1);
    logger.addHandler(fh2);
    logger.addHandler(fh3);
    

logger.setLevel(Level.FINEST);

24

background image

 

 

Konfiguracja dynamiczna

} catch (SecurityException e) {

logger.log(Level.WARNING, "problem", e);

} catch (IOException e) {

logger.log(Level.WARNING, "problem", e);

}

    LoggingSample.main(args);
}

}

W konsoli wynik ten sam. W pliku 

logSimple.txt

:

2006-02-28 16:33:37 LoggingSample main
INFO: zaczynam
2006-02-28 16:33:37 LoggingSample main
FINE: jestem gadatliwy
2006-02-28 16:33:38 LoggingSample inner
INFO: jestem w srodku
2006-02-28 16:33:39 LoggingSample main
WARNING: programista sie pomylil
2006-02-28 16:33:39 LoggingSample main
FINER: jestem bardziej gadatliwy
2006-02-28 16:33:39 LoggingSample main
INFO: zrobione

25

background image

 

 

Konfiguracja dynamiczna

Fragment 

log.txt

:

<?xml version="1.0" encoding="windows-1250" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<?xml version="1.0" encoding="windows-1250" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
  <date>2006-02-28T16:33:39</date>
  <millis>1141140819377</millis>
  <sequence>3</sequence>
  <logger>LoggingSample</logger>
  <level>WARNING</level>
  <class>LoggingSample</class>
  <method>main</method>
  <thread>10</thread>
  <message>programista sie pomylil</message>
</record>
</log> 

W pliku 

logXML.txt

: jest więcej wpisów związanych z niższym poziomem 

logowania.

26

background image

 

 

Podsumowanie

Korzystanie z narzędzi służących testowaniu oprogramowania pozwala znacznie 

skrócić czas potrzebny na przygotowanie rozbudowanych aplikacji. Logowanie 

umożliwia śledzenie pracy programu i łatwiejszą lokalizacje przyczyn 

ewentualnych błędów.

27