Folien ab 27.11.14

Werbung
11. JDBC
•
•
•
•
•
•
•
Grundsätzliche Nutzung von JDBC
Verbindungsaufbau
Anfragen
Analyse des erhaltenen Ergebnisses
Veränderungen des Ergebnisses
Einführung JUnit
Einführung DBUnit
Ziel: Verständnis für Konzepte von JDBC aufbauen
Datenbanken
Prof. Dr.
Stephan Kleuker
276
Überblick: Datenbankanfragen mit JDBC
class DriverManager
Datenbankverbindung Connection con=
DriverManager.getConnection(...);
herstellen
Statement stmt=
con.createStatement();
Datenbankanfrage
Ergebnisse
verarbeiten
Verbindung zur DB
schließen
Datenbanken
ResultSet rs =
stmt.executeQuery(...);
rs.next();
int n = rs.getInt("KNr");
con.close();
Prof. Dr.
Stephan Kleuker
277
Verbindungsaufbau mit einer Datenbank
• Laden des Datenbanktreibers
DriverManager.registerDriver(
new oracle.jdbc.driver.OracleDriver());
• Aufbau einer Verbindung
Connection con =
DriverManager.getConnection
("jdbc:postgresql://rechner:5712/eshop"
,"user", "password");
• JDBC-URL identifiziert Datenbank
Aufbau: jdbc:subprotocol:subname
• Treibermanager übergibt JDBC-URL der Reihe nach
an registrierte Treiber
Driver.acceptsURL(String url)
liefert true, falls der Treiber den String akzeptiert
Datenbanken
Prof. Dr.
Stephan Kleuker
278
Auslesen der Oracle-Verbindungsdaten
• Oracle „versteckt“ irgendwo Verbindungsdaten in
tnsnames.ora
• benötigt wird IP-Adresse, Port (default 1521), Name der DBInstanz (z. B. in SID) + Nutzername und Passwort
• Daraus abgeleiteter Connection-String für HS-DB (SID: Ora11)
Connection con = DriverManager.getConnection(
"jdbc:oracle:thin:"
+ "@oracle-srv.edvsz.hs-osnabrueck.de"
+ ":1521:Ora11", "username", "passwort");
• Connection-String für DB auf lokalem Rechner (Oracle XE)
Connection con = DriverManager
.getConnection(
"jdbc:oracle:thin:@localhost:1521:xe"
, "username", "passwort");
Datenbanken
Prof. Dr.
Stephan Kleuker
279
Connection
• Ergebnis von DriverManager.getConnection() ist eine
Connection con (oder eine SQLException)
• Connection ist Verbindung zur Datenbank
• Connection ist teure Ressource:
– Aufbau der Verbindung kostet viel Zeit
– Anzahl gleichzeitiger Verbindungen häufig beschränkt
• Wichtigste Aufgaben:
– Erzeugen von Statement-Objekten
– Beschreibungen von Datenbank und DBMS erfragen
– Transaktionen handhaben
Datenbanken
Prof. Dr.
Stephan Kleuker
280
Verbindungsanalyse durch Metadaten (Ausschnitt)
DatabaseMetaData dbmd = con.getMetaData();
System.out.println("DB-Name: " + dbmd.getDatabaseProductName()
+ "\nDB-Version: " + dbmd.getDatabaseMajorVersion()
+ "\nDB-Release: " + dbmd.getDriverMinorVersion()
+ "\nTransaktionen erlaubt: " + dbmd.supportsTransactions()
+ "\nbeachtet GroßKlein :" + dbmd.storesMixedCaseIdentifiers()
+ "\nunterstützt UNION :" + dbmd.supportsUnion()
+ "\nmax. Prozedurname: " + dbmd.getMaxProcedureNameLength());
DB-Name: Oracle
DB-Version: 11
DB-Release: 2
Transaktionen erlaubt: true
beachtet GroßKlein :false
unterstützt UNION :true
max. Prozedurname: 30
Datenbanken
Prof. Dr.
Stephan Kleuker
281
Statement / Anfrage ausführen
• Statement stmt = con.createStatement();
• Wird immer aus bestehender Connection erzeugt
• Aufgabe: Ausführen einer SQL-Anweisung über die
Connection
• Mehrere parallele Statements pro Connection möglich
• SELECT-Anweisung ausführen:
ResultSet rs =
stmt.executeQuery("SELECT * FROM Kunde");
• Daten ändern:
int updates =
stmt.executeUpdate("DELETE FROM Kunde
...");
Datenbanken
Prof. Dr.
Stephan Kleuker
282
Metadaten des Anfrageergebnisses
ResultSet rs = stmt.executeQuery("SELECT * FROM Continent");
ResultSetMetaData rsmd = rs.getMetaData();
int spalten = rsmd.getColumnCount();
for (int i = 1; i <= spalten; i++) { // nicht i=0
System.out.println(i + ". Name: " + rsmd.getColumnName(i)
+ " Typ: " + rsmd.getColumnTypeName(i)
+ " Javatyp: " + rsmd.getColumnClassName(i));
}
1. Name: NAME Typ: VARCHAR2 Javatyp: java.lang.String
2. Name: AREA Typ: NUMBER Javatyp: java.math.BigDecimal
Datenbanken
Prof. Dr.
Stephan Kleuker
283
Analyse von ResultSet
ResultSet rs =
stmt.executeQuery("SELECT * FROM Kunde");
• Ergebnis einer Selektionsanweisung: Tabelle
• ResultSet enthält einen Datensatz-Zeiger (Cursor) zum
Durchlauf der Tabelle
• Voreinstellung: sequenziell und lesend
• ab JDBC 2: nichtsequenzielle und aktualisierbare ResultSets
• Zeiger steht initial vor der ersten Tabellenzeile
• rs.next() positioniert zur nächsten Zeile, liefert false, falls
bereits auf letzter Zeile
Datenbanken
Prof. Dr.
Stephan Kleuker
284
Attributwerte auslesen (1/2)
• Spaltenwerte (Attribute) einer Zeile mit getXXX()-Methoden
lesen
• Treiber konvertiert Daten, falls möglich, deshalb (fast)
immer getString() nutzbar
• Beispiel: Lesen einer ganzen Zahl in DB-Spalte kundenNr:
int n = rs.getInt("kundenNr");
• Effizientere Methode, falls Spaltenindex bekannt:
int n = rs.getInt(4);
• Spaltenindex zum Spaltennamen finden
int findColumn(String columnName)
• Strategie: Spaltenindex einmalig ermitteln und merken,
Werte danach immer über den Index abrufen
Datenbanken
Prof. Dr.
Stephan Kleuker
285
Attributwerte auslesen (2/2)
• Methode getObject()
– Liest jeden beliebigen SQL-Datentyp
– Liefert Ergebnis als entsprechenden Java-Typ
• Nullwerte
– Spalte kann leere Zellen enthalten (Nullwert, SQL-NULL)
– Bei leerer Zelle liefert getInt() ebenfalls das Ergebnis 0
– Unterscheidung zu echter 0 möglich durch wasNull()
• true, falls zuletzt mit getXXX() gelesene Zelle SQLNULL enthielt
• false sonst
Datenbanken
Prof. Dr.
Stephan Kleuker
286
Beispiel: Ausgabe eines Anfrageergebnisses
ResultSet rs = stmt.executeQuery("SELECT * FROM Continent");
ResultSetMetaData rsmd = rs.getMetaData();
int spalten = rsmd.getColumnCount();
while (rs.next()) {
for (int i = 1; i <= spalten; i++) {
System.out.print(rs.getString(i) + " ");
}
System.out.print("\n");
Europe 9562488
}
Asia 45095292
Australia/Oceania 8503474
Africa 30254708
America 39872000
Datenbanken
Prof. Dr.
Stephan Kleuker
287
ResultSet: positionieren und ändern
• Statement createStatement(int
resultSetType, int resultSetConcurrency)
Parameter (ResultSet-Konstanten) ermöglichen beliebiges
Positionieren (Scrolling) und Ändern der Datensätze
• TYPE_FORWARD_ONLY: sequentieller Durchlauf
• TYPE_SCROLL_INSENSITIVE: positionierbar, Änderungen an
Datenbank werden nicht bemerkt
• TYPE_SCROLL_SENSITIVE: positionierbar, Änderungen an
Datenbank werden bemerkt
•
• CONCUR_READ_ONLY: nur lesen
• CONCUR_UPDATABLE: Änderungen möglich
Datenbanken
Prof. Dr.
Stephan Kleuker
288
ResultSet: positionieren (1/3)
• void beforeFirst()
Positioniert vor den ersten Satz
• boolean first()
Positioniert auf den ersten Satz
• boolean last()
Positioniert auf den letzten Satz
• void afterLast()
Positioniert hinter den letzten Satz
Datenbanken
Prof. Dr.
Stephan Kleuker
289
ResultSet: positionieren (2/3)
• boolean absolute(int pos)
Positioniert ausgehend vom Anfang (pos positiv) oder vom
Ende (pos negativ)
• boolean relative(int rows)
Positioniert relativ zum aktuellen Satz vorwärts (rows
positiv) oder rückwärts (rows negativ)
• boolean next()
Positioniert auf den nächsten Satz
• boolean previous()
Positioniert auf den vorigen Satz
Datenbanken
Prof. Dr.
Stephan Kleuker
290
ResultSet: positionieren (3/3)
• int getRow()
Liefert aktuelle Satznummer
• boolean isBeforeFirst()
Liefert true, falls vor dem ersten Satz
• boolean isFirst()
Liefert true, falls auf dem ersten Satz
• boolean isLast()
Liefert true, falls auf dem letzten Satz
• boolean isAfterLast()
Liefert true, falls hinter dem letzten Satz
Datenbanken
Prof. Dr.
Stephan Kleuker
291
ResultSet: Datensatz ändern
•
Methoden updateXXX() ändern Werte in aktueller Zeile.
rs.absolute(5);
rs.updateString("Name", "Heinz");
rs.updateInt(2, 42);
rs.updateNull(3);
rs.updateRow();
• void updateRow()
Schreibt geänderte Zeile in die Datenbank (*)
• void cancelRowUpdates()
Macht Änderungen rückgängig – nur vor updateRow()
• void deleteRow()
Löscht aktuelle Zeile aus ResultSet
(*) Ob Bearbeitung des ResultSet sich direkt auf die Datenbank
auswirkt, hängt von Autocommit-Einstellung ab [später]
Datenbanken
Prof. Dr.
Stephan Kleuker
292
ResultSet: Datensatz einfügen
•
Einfügezeile: im ResultSet, nicht in der Datenbank
rs.moveToInsertRow();
rs.updateString("Name", "Callaghan");
rs.updateInt(2, 42);
rs.insertRow();
rs.moveToCurrentRow();
• void moveToInsertRow()
Positioniert auf die Einfügezeile
• void insertRow()
Schreibt Einfügezeile in ResultSet und Datenbank (abhängig
von Autocommit-Einstellung)
• void moveToCurrentRow()
Positioniert von der Einfügezeile auf die aktuelle Zeile im
ResultSet
Datenbanken
Prof. Dr.
Stephan Kleuker
293
Daten ändern
• ResultSet executeQuery(String) führt SELECT-
Anweisung aus
• int executeUpdate(String) führt INSERT-, UPDATE-
oder DELETE-Anweisung aus
– Liefert Anzahl betroffener Zeilen (Update count)
– Auch für sonstige Befehle (CREATE ...) geeignet
• boolean execute(String) führt beliebige SQLAnweisung aus
– Liefert true, falls Ergebnis ein ResultSet ist, dann mit
getResultSet() Ergebnis abrufbar
– Liefert false, falls Ergebnis ein Update count ist
Datenbanken
Prof. Dr.
Stephan Kleuker
294
PreparedStatement (1/2)
• PreparedStatement Objekt enthält vorübersetzte SQLBefehle
• geeignet, wenn Statement mehr als einmal ausgeführt
werden muss
• PreparedStatement kann Variablen enthalten, die
jedesmal bei Ausführung definiert werden
• Ansatz: Erzeuge PreparedStatement, identifiziere
Variablen mit ?
PreparedStatement pstmt =
con.prepareStatement ("UPDATE Kunde "
+ "SET Status = ? where Umsatz > ?");
Datenbanken
Prof. Dr.
Stephan Kleuker
295
PreparedStatement (2/2)
1.
Variablen-Werte übergeben
pstmt.setXXX(index,value);
2.
Statement ausführen
pstmt.executeQuery();
pstmt.executeUpdate();
int goldenerKunde=42000;
PreparedStatement pstmt =
con.prepareStatement ("UPDATE Kunde "
+ "SET Status = ? where Umsatz > ?");
pstmt.setString(1, "Gold");
pstmt.setInt(2, goldenerKunde);
pstmt.executeUpdate();
Datenbanken
Prof. Dr.
Stephan Kleuker
296
COMMIT
• Für Connection con ist automatisch ein „Auto-COMMIT“
eingestellt, alle Aktionen direkt auf der Datenbank
• Mit con.setAutoCommit(boolean) einstellbar
• Starteinstellung über con.getAutoCommit() ermitteln (bei
Oracle default: true !)
• con.commit() zum erfolgreichen Abschließen
• con.rollback() zum Verwerfen der Änderungen seit dem
letzten Commit
Datenbanken
Prof. Dr.
Stephan Kleuker
297
Verbindungsaufbau (Ausschnitt)
import oracle.jdbc.*; // besser ausschreiben
import java.sql.*;
...
try {
// Oracle JDBC-Treiber laden
DriverManager.
registerDriver(new oracle.jdbc.driver.OracleDriver());
Connection con = DriverManager.
getConnection("jdbc:oracle:thin:@oracle-srv.edvsz"
+".hs-osnabrueck.de:1521:Ora11",
"kleuker", "passwort");
// Oracle DatabaseMetaData fuer Meta-Informationen
DatabaseMetaData meta = con.getMetaData();
// Beispielmetainformation: Treiberversion
System.out.println("JDBC driver version is "+
meta.getDriverVersion()+"\n");
befehl=con.createStatement(); //private Statement befehl
}
catch (SQLException e) {...
}
Datenbanken
Prof. Dr.
Stephan Kleuker
298
Einfache Verbindungsnutzung (Ausschnitt)
ResultSet result;
ResultSetMetaData metaresult;
try {
result=befehl.executeQuery("SELECT * FROM City");
metaresult=result.getMetaData();
int spalten = metaresult.getColumnCount();
while(result.next()){
for(int i=1; i<=spalten;i++)
ausgabe.append(result.getString(i)+" ");
ausgabe.append("\n");
}
ausgabe.append("Bearbeitung abgeschlossen\n");
result.close();
}
catch (SQLException es) {
while (es!=null){ //evtl. mehr als eine Exception
fehler.append("Fehlercode: "+es.getErrorCode()+"\n");
fehler.append("SQL State: "+es.getSQLState()+"\n");
fehler.append(es+"\n");
es= es.getNextException();
}
}
Datenbanken
Prof. Dr.
Stephan Kleuker
299
Einbindung von Oracle-JDBC in Eclipse
Rechtsklick auf Projekt,
dann Properies wählen,
„Java Build Path“, dann
Reiter Libraries, dann
„Add External JARs...“
Quellen: Oracle Web-Seiten
C:\sqldeveloper\jdbc\lib\ojdbc6.jar
Dann passenden Treiber suchen,
z. B. ojdbc6.jar in sqldeveloper\jdbc\lib
Datenbanken
Prof. Dr.
Stephan Kleuker
300
Einbindung von Oracle-JDBC in NetBeans
• Rechtsklick im Projekt auf
Libraries -> Add JAR/Folder
• manövrieren zum Verzeichnis
mit JDBC-Treiber (sinnvoll
Unterordner lib des Projekts)
• markieren und „Öffnen“
Datenbanken
Prof. Dr.
Stephan Kleuker
301
Erinnerung: PL/SQL-Funktion
CREATE OR REPLACE
FUNCTION anzahlNasen(namePar Angestellte.Name%TYPE)
RETURN INTEGER
/* oder NUMBER */
IS
ergebnis INTEGER;
BEGIN
IF namePar = 'Meier'
THEN
RAISE_APPLICATION_ERROR(-20300,
'Meiers nicht zählbar');
END IF;
SELECT COUNT(*)
INTO ergebnis
FROM Angestellte
WHERE Angestellte.Name=namePar;
RETURN ergebnis;
END;
Datenbanken
Prof. Dr.
Stephan Kleuker
302
PL/SQL in Java
• Grundsätzlich besteht bei der Ausführung große Ähnlichkeit
zu PreparedStatements
• benötigt wird Connection-Objekt con
CallableStatement stmt=con.prepareCall(
"{? = call anzahlNasen(?)}");
• man beachte geschweifte Klammern, bei Prozeduraufruf wird
„ ? = “ am Anfang weggelassen
• in Oracle auch möglich:
CallableStatement stmt=con.prepareCall(
"BEGIN ? := anzahlNasen(?); END;");
• Ausgabeparameter müssen registriert werden
stmt.registerOutParameter(1,Types.INTEGER);
Datenbanken
Prof. Dr.
Stephan Kleuker
303
Beispiel
public int nasenZaehlen(String nachname){
int ergebnis = 0;
try {
CallableStatement stmt=con.prepareCall(
"{? = call anzahlNasen(?)}");
stmt.registerOutParameter(1, Types.INTEGER);
stmt.setString(2, nachname);
stmt.execute();
ergebnis=stmt.getInt(1);
} catch (SQLException e) {
if(e.getErrorCode() == 20300) // von RAISE_APPLICATION_ERROR
ergebnis = -1;
else{
ausnahmeAusgeben(e);
ergebnis = Integer.MIN_VALUE;
}
}
return ergebnis;
}
Datenbanken
Prof. Dr.
Stephan Kleuker
304
Nutzung von Cursor als Ergebnis (1/3)
import
import
import
import
import
import
java.sql.CallableStatement;
java.sql.Connection;
java.sql.DriverManager;
java.sql.ResultSet;
java.util.Scanner;
oracle.jdbc.OracleTypes;
/* in Oracle fuer das MondialBeispiel
CREATE OR REPLACE PROCEDURE STAEDTEIN(
Land IN VARCHAR,
stadtcursor IN OUT SYS_REFCURSOR) AS
BEGIN
OPEN stadtcursor for
SELECT *
FROM City
WHERE City.Country=Land;
END STAEDTEIN;
*/
/* Hinweis: Cursor nur lesbar, nicht schreibbar! */
Datenbanken
Prof. Dr.
Stephan Kleuker
305
Nutzung von Cursor als Ergebnis (2/3)
public class PLSQLCursorNutzungInJava { //
public static void main(String[] s){
String nutzer = "kleuker";
String passwort = "kleuker";
try {
DriverManager.registerDriver(new
oracle.jdbc.driver.OracleDriver());
//Verbindung aufbauen in der HS zur DB
Connection conn = DriverManager.getConnection
("jdbc:oracle:thin:@oracle-srv.edvsz.hs-osna"
+ "brueck.de:1521:Ora11", nutzer, passwort);
//Vorbereitung der PL/SQL-Nutzung
ResultSet rset = null;
CallableStatement cstmt = conn.
prepareCall("{call staedtein(?,?)}");
cstmt.registerOutParameter(2,
OracleTypes.CURSOR);
Datenbanken
Prof. Dr.
Stephan Kleuker
306
Nutzung von Cursor als Ergebnis (3/3)
//Testprogramm
String eingabe = "";
while(!eingabe.equalsIgnoreCase("Ende")){
System.out.print(
"Laenderkuerzel (Ende mit \"Ende\"): ");
eingabe=(new Scanner(System.in)).next();
cstmt.setString(1,eingabe);
cstmt.execute();
rset = (ResultSet) cstmt.getObject(2);
while(rset.next())
System.out.println("Stadt: "
+rset.getString("name")
+" Einwohner:"
+rset.getInt("population"));
}
conn.close();
}
}
}
catch(Exception e){ // ausarbeiten, wie immer
}
Datenbanken
Prof. Dr.
Stephan Kleuker
307
Beispieldialog
Laenderkuerzel (Ende mit "Ende"): GR
Stadt: Piraeus Einwohner:196389
Stadt: Patrai Einwohner:142163
Stadt: Larisa Einwohner:102426
Stadt: Iraklion Einwohner:102398
Stadt: Volos Einwohner:71378
Stadt: Kavalla Einwohner:56705
Stadt: Athens Einwohner:885737
Stadt: Thessaloniki Einwohner:406413
Laenderkuerzel (Ende mit "Ende"): LAR
Stadt: Tripoli Einwohner:0
Stadt: Bengasi Einwohner:0
Laenderkuerzel (Ende mit "Ende"): AND
Stadt: Andorra la Vella Einwohner:15600
Laenderkuerzel (Ende mit "Ende"): enDE
Datenbanken
Prof. Dr.
Stephan Kleuker
308
Einschub: Testen mit JUnit
• Framework, um den Unit-Test eines Java-Programms zu
automatisieren
• einfacher Aufbau
• leicht erlernbar
• geht auf SUnit
(Smalltalk) zurück
• mittlerweile für
viele Sprachen
verfügbar (NUnit,
CPPUnit)
S. Kleuker, Qualitätssicherung
durch Softwaretests, Springer
Vieweg, Wiesbaden, 2013
Software-Qualität
Prof. Dr.
Stephan Kleuker
309
Testfall
• Vor dem Testen müssen Testfälle spezifiziert werden
• Vorbedingungen
– Zu testende Software in klar definierte Ausgangslage bringen
(z. B. Objekte mit zu testenden Methoden erzeugen)
– Angeschlossene Systeme in definierten Zustand bringen
– Weitere Rahmenbedingungen sichern (z. B. HW)
• Ausführung
– Was muss wann gemacht werden (einfachster Fall:
Methodenaufruf)
• Nachbedingungen
– Welche Ergebnisse sollen vorliegen (einfachster Fall:
Rückgabewerte)
– Zustände anderer Objekte / angeschlossener Systeme
Software-Qualität
Prof. Dr.
Stephan Kleuker
310
Aufbau einer Testklasse (1/8)
import
import
import
import
import
import
junit.framework.Assert;
org.junit.After;
org.junit.AfterClass;
org.junit.Before;
org.junit.BeforeClass;
org.junit.Test;
public class AnschauungTest {
private int wert;
private static int klasse;
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
Beliebiger Klassenname,
Endung „Test“ üblich
Nutzung von normalen
Exemplarvariablen
Klassenvariablen, außer
als Konstanten, unüblich
311
Aufbau einer Testklasse (2/8)
Verhaltensbeschreibung
mit Annotationen
Methode wird einmal vor
vor allen Tests ausgeführt,
z.B. Aufbau DB-Verbindung
@BeforeClass
public static void setUpBeforeClass()
throws Exception {
System.out.println("setUpBeforeClass");
klasse = 99;
}
einmal nach allen Tests
(aufräumen)
@AfterClass
public static void tearDownAfterClass()
throws Exception {
System.out.println("tearDownAfterClass");
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
312
Aufbau einer Testklasse (3/8)
Methode wird vor jedem Test
ausgeführt, Idee: einheitliche
Ausgangssituation schaffen
@Before
public void setUp() throws Exception {
System.out.println("setUp");
wert = 42;
klasse = klasse + 1;
System.out.println("klasse ist "+klasse);
}
einmal nach jeden Tests
(lokal aufräumen)
@After
public void tearDown() throws Exception {
System.out.println("tearDown");
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
313
Aufbau einer Testklasse (4/8)
Test ist beliebige mit @Test
annotierte Methode
Methodenname ist beliebig,
beginnt typischerweise mit
„test“ und beinhaltet Name der
@Test
Methode oder Sinn des Tests
public void test1() {
System.out.println("test1");
Experimente
wert = wert + 1;
Assert.assertTrue("Erwartet 43 gefunden: "+wert
, wert == 43);
}
Prüfmethode, Parameter Text und Boolesche
Bedingung; ist Bedingung „false“ wird Test als
gescheitert festgehalten und Text ausgegeben
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
314
Aufbau einer Testklasse (5/8)
@Test
public void test2() {
System.out.println("test2");
wert = wert + 2;
Assert.assertTrue(wert == 44);
}
Kurzform ohne Text (gibt
assert-Varianten)
wenn Testfall scheitert ist
entweder das Programm
oder der Test fehlerhaft
@Test
public void test3() {
System.out.println("test3");
wert = wert + 3;
Assert.assertTrue("Erwartet 44 gefunden: "+wert
, wert == 44);
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
315
Aufbau einer Testklasse (6/8)
@Test
Test scheitert, wenn ausgeführt;
public void test4() {
markiert Stellen, die nicht
System.out.println("test4");
erreicht werden sollen
try{
if(42/0 == 0){
gewünschte Exception
}
Assert.fail();
} catch(ArithmeticException e){
ungewünschte Exception
} catch(Exception e){
Assert.fail();
(kann weggelassen
}
werden)
}
@Test
public void test5() {
System.out.println("test5");
throw new IllegalArgumentException();
}
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
316
Aufbau einer Testklasse (7/8)
setUpBeforeClass
setUp
klasse ist 100
test4
tearDown
setUp
klasse ist 101
test5
tearDown
setUp
klasse ist 102
test1
tearDown
setUp
klasse ist 103
test2
tearDown
setUp
klasse ist 104
test3
tearDown
tearDownAfterClass
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
317
Aufbau einer Testklasse (8/8)
• Failure:
Fehler
durch
Assert
• Error:
Fehler
durch
Abbruch
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
318
Erinnerung: Ich muss mein Gebot erhoehen
CREATE OR REPLACE TRIGGER GEBOTERHOEHEN
BEFORE INSERT OR UPDATE ON GEBOT
FOR EACH ROW
CREATE TABLE Gebot(
DECLARE
mnr INTEGER,
PRAGMA AUTONOMOUS_TRANSACTION;
ware INTEGER,
maxi Gebot.gebot%TYPE;
gebot NUMBER(8, 2),
BEGIN
PRIMARY KEY(mnr,ware,gebot)
SELECT MAX (Gebot.gebot)
);
INTO maxi
FROM Gebot
WHERE Gebot.mnr = :NEW.mnr
AND Gebot.ware = :NEW.ware;
IF maxi IS NOT NULL AND maxi >= :NEW.Gebot
THEN
RAISE_APPLICATION_ERROR(-20900
,'Gebot muss erhoeht werden');
END IF;
END;
/
Datenbanken
Prof. Dr.
Stephan Kleuker
319
Basisideen zur Testerstellung
• viele kleine Tests, da nachfolgende Asserts nicht geprüft,
wenn eines vorher abbricht
• erwartetes Verhalten kann zusammen geprüft werden
• jede mögliche Ausnahme in getrenntem Testfall
• Extremwerte prüfen
• Testklassen können weitere Hilfsmethoden enthalten
• typisch: am Anfang auf „leerem Feld“ neue
Testausgangssituation (@SetUp) erstellen
Datenbanken
Prof. Dr.
Stephan Kleuker
320
Test von Datenbanken
• Nie, nie mit laufender Geschäftsdatenbank testen; es wird
immer ein Testsystem benötigt
• generell direkt mit JUnit machbar
• Detailproblem: nach den Tests so aufräumen, dass
Ausgangssituation wieder hergestellt (abhängige Daten !)
• Detailproblem: aufwändiger Vergleich zwischen aktuellem
und erwartetem Tabelleninhalt
Vereinfachung mit DBUnit als Ergänzung von JUnit
• einfaches Leeren und Neueinspielen von Datensätzen
• einfacher Vergleich von Mengen von Tabelleneinträgen
• Tabellen z. B. auf Basis von XML-Dateien definierbar
• (hier nur zentrale Konzepte) http://www.dbunit.org/
Datenbanken
Prof. Dr.
Stephan Kleuker
321
Projektaufbau
Datenbanken
Prof. Dr.
Stephan Kleuker
322
Konfiguration des Loggers (log4j.properties)
log4j.rootLogger=WARN, console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.conversionPattern=%5p [%t]
(%F:%L) - %m%n
• ermöglicht flexibles Schreiben von Meldungen in LogDateien
• sehr einfach ein- und ausschaltbar (hier Level WARN)
Datenbanken
Prof. Dr.
Stephan Kleuker
323
Verbindung
public class Verbindung {
private static String dbAdresse = "127.0.0.1";
private static String dbInstanz = "XE";
private static String nutzer = "ich";
private static String passwort = "ich";
public static Connection verbinden() throws Exception {
DriverManager.registerDriver(
new oracle.jdbc.driver.OracleDriver());
return DriverManager.getConnection("jdbc:oracle:thin:@"
+ dbAdresse + ":1521:" + dbInstanz, nutzer, passwort);
}
public static void verbindungTrennen(Connection con
, IDatabaseConnection conDBU) throws Exception {
if (con != null) {
con.close();
}
if (conDBU != null) {
conDBU.close();
} Datenbanken
Prof. Dr.
324
}
Stephan Kleuker
}
public class GebotErhoehenTest {
private static Connection con = null; // direkte DB-Verbindung
private static IDatabaseConnection conDBU; // DBUnit-Verbindung
@BeforeClass
public static void setUpBeforeClass() throws Exception {
con = Verbindung.verbinden();
conDBU = new DatabaseConnection(con, null, true);
DatabaseConfig config = conDBU.getConfig();
config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY,
new OracleDataTypeFactory());
}
DBUnit hat DB-
individuelle Einstellungen
@AfterClass
public static void tearDownAfterClass() throws Exception {
Verbindung.verbindungTrennen(con, conDBU);
}
Datenbanken
Prof. Dr.
Stephan Kleuker
325
basisdaten.xml
• Möglichkeit zur Spezifikation von Testdaten mit XML
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<Gebot mnr="1" ware="100" gebot="1.00"
<Gebot mnr="1" ware="101" gebot="1.00"
<Gebot mnr="1" ware="100" gebot="2.00"
<Gebot mnr="2" ware="100" gebot="2.01"
<Gebot mnr="3" ware="101" gebot="1.01"
</dataset>
/>
/>
/>
/>
/>
• Wird Spalte nicht angegeben, dann NULL-Wert
• Daten werden in angegebener Reihenfolge eingefügt
Datenbanken
Prof. Dr.
Stephan Kleuker
326
Varianten beim Einspielen von Daten
@Before
public void setUp() throws Exception {
IDataSet dataSet = new FlatXmlDataSetBuilder()
.build(new FileInputStream(".\\testdaten\\basisdaten.xml"));
DatabaseOperation.CLEAN_INSERT.execute(conDBU, dataSet);
}
•
•
•
•
•
CLEAN_INSERT: alle Daten zuerst löschen, dann einfügen
DELETE_ALL: löscht alle Daten in den Tabellen
DELETE : löscht die übergebenen Daten
INSERT: fügt die übergebenen Daten in die Tabellen ein
UPDATE: aktualisiert die vorhandenen Daten mit den
übergebenen Daten
• REFRESH: aktualisiert vorhandene Daten, fügt nicht
vorhandene Daten hinzu
Datenbanken
Prof. Dr.
Stephan Kleuker
327
Test: erlaubtes Insert
@Test
public void testGebotAufNeueWareOk() {
try {
int anzahl = con.createStatement().executeUpdate(
"INSERT INTO Gebot VALUES (4, 102, 3.00)");
Assert.assertTrue("statt 1, " + anzahl
+ "Datensaetze geaendert", anzahl == 1);
} catch (SQLException e) {
Assert.fail("erlaubtes INSERT gescheitert: "
+ "VALUES (4, 102, 3.00)");
}
}
// Hinweis: Test mit erlaubten UPDATE, DELETE sinnvoll
• Etwas kritisch: es wurde eine Zeile eingefügt; wird nicht
überprüft, ob sie so im Ergebnis steht
Datenbanken
Prof. Dr.
Stephan Kleuker
328
Erlaubtes Insert, präzise Prüfung (1/2)
• einfachesInsert.xml
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<Gebot mnr="1" ware="100" gebot="1.00"
<Gebot mnr="1" ware="101" gebot="1.00"
<Gebot mnr="1" ware="100" gebot="2.00"
<Gebot mnr="4" ware="102" gebot="3.00"
<Gebot mnr="2" ware="100" gebot="2.01"
<Gebot mnr="3" ware="101" gebot="1.01"
</dataset>
/>
/>
/>
/>
/>
/>
• Erinnerung: SQL speichert ohne Reihenfolge, was beim
Vergleich zu beachten ist
• DBUnit ermöglicht lexikographische Sortierung
Datenbanken
Prof. Dr.
Stephan Kleuker
329
Erlaubtes Insert, präzise Prüfung (2/2)
@Test
public void testGebotAufNeueWareVarianteOk()
throws Exception {
con.createStatement().executeUpdate(
"INSERT INTO Gebot VALUES (4,102,3.00)");
IDataSet databaseDataSet = conDBU.createDataSet();
ITable actualTable = databaseDataSet.getTable("Gebot");
IDataSet expectedDataSet = new FlatXmlDataSetBuilder()
.build(new File(".\\testdaten\\einfachesInsert.xml"));
ITable expectedTable = expectedDataSet.getTable("Gebot");
Assertion.assertEquals(new SortedTable(expectedTable)
, new SortedTable(actualTable));
}
Datenbanken
Prof. Dr.
Stephan Kleuker
330
Test, ob Primary Key noch existiert
@Test
public void testPrimaryKeyVerstoss(){
try {
con.createStatement().executeUpdate(
"INSERT INTO Gebot VALUES (2,100,2.01)");
Assert.fail("verbotenes INSERT durchgefuehrt: "
+ "VALUES (2,100,2.01)");
} catch (SQLException e) {
}
}
Datenbanken
Prof. Dr.
Stephan Kleuker
331
Test des Triggers (1/2)
@Test
public void testHoeheresGebotOk() {
try {
int anzahl = con.createStatement().executeUpdate(
"INSERT INTO Gebot VALUES (3, 101, 1.02)");
Assert.assertTrue("statt 1, " + anzahl
+ "Datensaetze geaendert", anzahl == 1);
} catch (SQLException e) {
Assert.fail("erlaubtes INSERT gescheitert: "
+ "VALUES (3, 101, 1.02)");
}
}
Datenbanken
Prof. Dr.
Stephan Kleuker
332
Test des Triggers (2/2)
@Test
public void testGleichesGebotVerboten(){
try {
con.createStatement().executeUpdate(
"INSERT INTO Gebot VALUES (3, 101, 1.00)");
Assert.fail("verbotenes INSERT durchgefuehrt: "
+ "VALUES (3, 101, 1.00)");
} catch (SQLException e) {
Assert.assertTrue("Fehler 20900 erwartet, gefunden"
+ e.getErrorCode(), e.getErrorCode() == 20900);
}
}
// fehlen Tests für UPDATE
Test auf passsenden
Fehlercode
}
Datenbanken
Prof. Dr.
Stephan Kleuker
333
Herunterladen