Testowanie i logowanie
1. Testowanie aplikacji
●
JUnit.
2. Tworzenie logów:
●
pakiet java.util.logging.
1
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
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
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
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
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
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
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
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
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
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
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
Uruchomienie testów
Aby uruchomić testy wpisujemy komende:
java -cp junit.jar:money.jar junit.swingui.TestRunner MoneyTest
13
JUnit i Eclipse
14
JUnit i Eclipse
15
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
http://logging.apache.org/log4j/docs/
16
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()
,
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:
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
.
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.
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
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
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
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
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
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
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