Ex7: Aufgabe 2 - DbC und Tests

MuldeR
BASIC-Programmierer
BASIC-Programmierer
Beiträge: 131
Registriert: 18. Okt 2005 16:14
Wohnort: (d)armstadt
Kontaktdaten:

Ex7: Aufgabe 2 - DbC und Tests

Beitrag von MuldeR »

Hallo,

wir haben gerade ein grundsätzliches Problem mit DbC und Tests, bei dem wir einfach nicht weiter kommen. In der Aufgabe 2 gibt es eine Methode Account.withdraw(), die zunächst bliebige Beträge abheben darf, solange dies der Kontostand zulässt. In JuniorAccount.withdraw() können nur noch 50 Euro auf einmal abgehoben werden. Einfach den Contract in JuniorAccount derart zu ändern, dass der Betrag nun kleiner/gleich 50 Euro sein muss, ist bekanntlich verboten! Contracts dürfen ja nicht verschärft werden...

Unsere Lösung sieht nun eine Methode getMaxWithdrawAtOnce() auf Account Ebene vor. Diese gibt an, wieviel Geld man auf einmal abheben darf, und wird von den einzelnen Account Typen entsprechend überschrieben - was keine DbC verletzung darstellt (die Methode hat keine Vorbedingungen und die einzige Nachbedingung, dass sie den aktuell zulässigen Höchstbetrag für Abhebungen zurückliefert). Ebenfalls auf Account Ebene (!) wurde der Contract von withdraw() derart verändert, dass man nicht mehr Geld auf einmal abheben darf, als getMaxWithdrawAtOnce() liefert. Die anderen Vorbedingungen sind natürlich erhalten geblieben, d.h. man muss auch genügend Geld auf dem Konto haben.

Soweit schien das eine gute Lösung zu sein, denn JuniorAccount braucht die Vorbedingungen jetzt nicht mehr einzuschränken! Es ist ja laut Contract (auf Ebende von Account!) die Aufgabe des Client sicherzustellen , dass er die Vorbedingungen einhält, d.h. er darf nicht mehr als getMaxWithdrawAtOnce() abheben. Das JuniorAccount gibt nun einfach bei getMaxWithdrawAtOnce() nur 50 Euro zurück. Das verletzt auch keine Contracts!

Code: Alles auswählen

@Pre(value="charge > 0 && charge <= $this.computeMaxWithdraw() && charge <= $this.getMaxWithdrawAtOnce()", message="Abzuhebender Betrag muss positiv sein und darf maximal abhebbaren Betrag nicht überschreiten!")
public void withdraw(final int charge)
{
  balance -= charge;
}

Nun zum eigentlichen Problem, dem Test:

Code: Alles auswählen

void main() {
  test(new CheckingAccount(1000)); //geht
  test(new JuniorAccount(1000)); //geht NICHT
}

void test(a Account) {
  a.widthdraw(60);
}
Offensichtlich wird der Test mit CheckingAccount funktionieren, mit JuniorAccount jedoch nicht! Man könnte jetzt argumentieren, dass damit das LSP verletzt ist, unsere Lösung also nicht erlaubt ist. Denn JuniorAccount verhält sich scheinbar anders, als CheckingAccount. ABER: Laut Contract von Account ist der Aufruf a.withdraw(60) gar nicht erlaubt, ohne vorher Account.getMaxWithdrawAtOnce() zu überprüfen! Ansonsten verletzt man ja die Vorbedingungen, die auf Account definiert sind! Somit wäre der Test schlicht unpassend formuliert. Der Test ist ja jetzt der Client und darf nicht einfach die Vorbedingungen der Account Klasse brechen! Wie aber soll man dann überhaupt Tests dieser Art formulieren???

Unsere Lösung ist nach dem DbC gültig, aber lässt sich nicht wie vorgesehen testen. Wir wissen hier leider nicht mehr weiter, da es sich um ein grundsätzliches Frage zu DbC handelt. Ein paar klärende Worte wären hier sehr Hilfreich. Danke schon mal im Vorraus...

maikg2
Mausschubser
Mausschubser
Beiträge: 95
Registriert: 18. Okt 2005 22:29
Wohnort: Darmstadt

Beitrag von maikg2 »

Hi.

Deine Lösung klingt ähnlich der in einem anderen Thread. Dort wurde auch allgemein Vorgeschlagen eine "checkParameter(a)" Methode einzuführen, was deinem "getMaxWithdrawAtOnce()" entspricht. Allerdings wurde diese Lösung als unschön eingestuft.

Aber eine Lösung des Problems kann ich dir leider auch nicht geben.

MuldeR
BASIC-Programmierer
BASIC-Programmierer
Beiträge: 131
Registriert: 18. Okt 2005 16:14
Wohnort: (d)armstadt
Kontaktdaten:

Beitrag von MuldeR »

maikg2 hat geschrieben:Hi.

Deine Lösung klingt ähnlich der in einem anderen Thread. Dort wurde auch allgemein Vorgeschlagen eine "checkParameter(a)" Methode einzuführen, was deinem "getMaxWithdrawAtOnce()" entspricht. Allerdings wurde diese Lösung als unschön eingestuft.

Aber eine Lösung des Problems kann ich dir leider auch nicht geben.
Das mit dem checkParameter(a) geht tatsächlich in die selbe Richtung, krankt aber daran, dass man jetzt Probleme mit dem Contract von checkParameter(a) anstatt withdraw(x) hat. Bei unsere Lösung sind die Contracts eingentlich klar definiert. Die getMaxWithdrawAtOnce() unterscheidet sich ja prinzipiell nicht von getBalance() oder dergleichen:

[font=Lucida Console]@PRE: Keine
@POST: $result == aktuelle zulässiger Höchstbetrag pro Abhebung
public int getMaxWithdrawAtOnce() { ... }

@PRE: charge <= $this.computeMaxWithdraw() && charge >= $this.getMaxWithdrawAtOnce()
@POST: Der Betrag "charge" wurde vom Konto abgehoben
public void withdraw(int charge) { ... }[/font]


Die Frage ist viel mehr, in wie fern die Contracts, die wir für eine Superklass definiert haben, in die Tests eingehen. Die Vorbedingungen müsste in den Test Methoden eigentlich zur Laufzeit getestet werden. Tests wie a.withdraw(60) sind da im Prinzip gar nicht mehr zulässig, da wird nicht allgemein sagen können, ob der Wert "60" (oder überhaupt irgend ein fester Wert) für ein Account Objekt zugelassen ist! Und dabei meine ich die Contracts von Account selbst, nicht etwa die der Subklassen! In unsere Fall würde "withdraw(x)" ja laut Contract von "getMaxWithdrawAtOnce()" abhängen, welches selbst einen eindeutigen Contract hat. Ich sehe in dem Design also kein Verletzung von DbC. Man darf ja auch net mehr abheben, als man auf dem Konto hat bzw. Kredit bekommt. Das is von den Vorbedingungen sehr ähnlich definiert zu unseren erweiterten Vorbedingungen von withdraw(). Wenn man also sagt, in den Vorbedingungen von withdarw() darf man getMaxWithdrawAtOnce() nicht abfragen, dann dürfte man genau so wenig computeMaxWithdraw() benutzen! Natürlich ist diese Lösung hier auch nicht gerade schön. Das wirkliche Problem ist aber, wie man für DbC gescheite Tests bauen soll! Und das ist ein grundsätzliches Problem und nicht auf dieses spezielle Design beschränkt. Die Lösung mit getMaxWithdrawAtOnce() ist ja bloß ein Beispiel...


Natürlich kann man einfach withdraw() komplett aus der Account Klasse entfernen und in die entsprechenden Sub-Klassen ziehen. Aber die Frage ist, welche Methoden dann überhaupt noch in Account übrig bleiben und ob man dann überhaupt noch sinnvoll auf einem Objekt mit statischem Typ "Account" arbeiten kann. Bei einem Konto, egal welcher Art, sollte man ja wohl zumindest mal Geld einzahlen und wieder abheben können! Sonst kann ich ja gleich ne leere Superklasse machen, mir die Vererbung sparen und nur auf den Konkreten Kontos Arbeiten. Offensichtlich nicht Sinn der Sache...

Xaero
Endlosschleifenbastler
Endlosschleifenbastler
Beiträge: 173
Registriert: 8. Feb 2006 20:06

Beitrag von Xaero »

Hallo,
habe eine ganz andere Frage. Ich komme mit dem contract4j5 nicht klar.
Wenn ich eine Precondition-baue, wie z.b.
@Pre(value="charge<=50 && charge>=0",message="0<x<50")
public void withdraw(final int charge) {
this.balance -= charge;
}
und einen Eingabewert 60 übergebe, dann müsste er ja ein Fehler schmeißen oder? Aber das macht er bei mir nicht, kann mir jemand sagen woran es liegen kann?

MuldeR
BASIC-Programmierer
BASIC-Programmierer
Beiträge: 131
Registriert: 18. Okt 2005 16:14
Wohnort: (d)armstadt
Kontaktdaten:

Beitrag von MuldeR »

Xaero, versuch es mal damit:

Code: Alles auswählen

import org.contract4j5.contract.*;
import org.junit.Test;


@Contract
public class testfoo {

	/**
	 * @param args
	 */
	@Test
	public void MyTest() {
		Test(60);
	}
	
	@Pre(value="x <= 50", message="faaaalsch")
	public void Test(int x)
	{
		System.out.print(x);
	}
	
}
Das tut bei mir so, wie es sollte:
"[FATAL] DefaultContractEnforcer: *** Contract Failure (testfoo.java:17): Pre test "x <= 50" for "Test" failed. faaaalsch [failure cause = null]"

Xaero
Endlosschleifenbastler
Endlosschleifenbastler
Beiträge: 173
Registriert: 8. Feb 2006 20:06

Beitrag von Xaero »

Ja...das geht bei mir auch nicht...komisch
Aber trotzdem danke

MuldeR
BASIC-Programmierer
BASIC-Programmierer
Beiträge: 131
Registriert: 18. Okt 2005 16:14
Wohnort: (d)armstadt
Kontaktdaten:

Beitrag von MuldeR »

Xaero hat geschrieben:Ja...das geht bei mir auch nicht...komisch
Aber trotzdem danke
führst du es denn auch als JUnit Test aus?
Ansonsten check nochmal ob das Contract4j5 und das JRuby wirklich korrekt installiert sind...

Benutzeravatar
Junky
Windoof-User
Windoof-User
Beiträge: 24
Registriert: 2. Dez 2005 00:35
Kontaktdaten:

Beitrag von Junky »

Hi,

also ich hänge jetzt auch schon den ganzen Tag an der 2. Aufgabe und krieg zum Verrecken diese Contracts nicht zum Laufen.

Folgender Code müsste doch so nen Error schmeißen, oder?

Code: Alles auswählen

import org.contract4j5.contract.*;
import org.junit.Test;

@Contract
public class Algo {

	@Test
	public void Test1(){
		this.add(-4, 5);
	}
	
	@Pre(value="$args[0]>0",message="a muss größer als 0 sein")
	public int add(int a, int b){
		return a+b;
	}
	
}
Hab ganz brav nach Folie 61 in den Ex07Slides das AspectJ Plugin installiert und in meinem BuildPath liegt jetz nicht nur eine 'AspectJ Runtime Library' sondern auch ein Ordner 'lib' und 'References Libraries' mit der contract4j5.jar.

Wenn ich aber die obige Klasse mit als JUnit-Test ausführen lasse, passiert einfach garnichts. Der JUnit-Test wird als erfolgreich gewertet und fertig.
Kann jemand helfen? Wär echt schade, wenns nur an der Aufgabe hängen würd :(

Schönen Abend noch
GDI2-Tutor

marcel_b
Nerd
Nerd
Beiträge: 600
Registriert: 31. Okt 2006 17:04
Kontaktdaten:

Beitrag von marcel_b »

Hi,

ja, dein Testfall sollte funktionieren. Siehe Screenshot anbei mit Quellcode und Fehlermeldung.Alles im ea-Projekt. Also mit den gleichen Einstellungen wie ihr Sie auch haben solltet. Die Advice Pfeile im Editor siehst du auch?

Viele Grüße
Marcel
Dateianhänge
algo.png
algo.png (93.83 KiB) 589 mal betrachtet

marcel_b
Nerd
Nerd
Beiträge: 600
Registriert: 31. Okt 2006 17:04
Kontaktdaten:

Re: Ex7: Aufgabe 2 - DbC und Tests

Beitrag von marcel_b »

Mulder,

offensichtlich ist die Vererbungsbeziehung JuniorCheckingAccount extends CheckingAccount nicht sinnvoll wenn man die maximale Höhe in der Basisklasse nicht festgelegt hat. Denk über eine Änderung der Vererbungshierarchie nach. Das Aufgabeblatt weißt dich darauf hin, dass du alles verändern darfst - ohne Einschränkungen. Genaus stellt es dir die Frage, ob JuniorCheckingAccount extends CheckingAccount eine sinnvolle Vererbungsbeziehung ist... (Teilaufgabe 4)

Du kannst auch die precondition formulieren, das withdraw <= maxWithdraw ist. das ist gültig und ok. Das Beispiel aus dem anderen Thread war etwas anders gelagert.
Der Client muss sicherstellen, dass er keine überhöhten Beträge verlangt, die Methode also mit den richtigen parametern aufgerufen wird. Im Fall von Withdraw(60) ist der Test schlichtweg nicht für eure Verträge geeignet und muss somit geändert werden.

Schönen Sonntag noch.

Benutzeravatar
Junky
Windoof-User
Windoof-User
Beiträge: 24
Registriert: 2. Dez 2005 00:35
Kontaktdaten:

Beitrag von Junky »

Hi marcel_b

ich habe keine Ahnung warum, aber ich habe wie du oben einfach die Algo.java in meinen Test-Ordner des importierten 'Exercise07 DbC&Generics' Projekts abgelegt - und es funktioniert. Gibt genau die Fehlermeldung wie bei dir, wenn ich die -4 zu ner 4 ändere, läuft alles korrekt durch.

Ich versteh echt nicht warum, aber jetzt steht nem arbeitsreichen Sonntag wohl nix mehr im Wege ;)

Apropos - muss mal ein großes Lob aussprechen, dass ihr (in diesem Fall du) auch am Wochenende noch Zeit findest, Fragen zu beantworten.

Schönes Wochenende soweit...
GDI2-Tutor

MuldeR
BASIC-Programmierer
BASIC-Programmierer
Beiträge: 131
Registriert: 18. Okt 2005 16:14
Wohnort: (d)armstadt
Kontaktdaten:

Re: Ex7: Aufgabe 2 - DbC und Tests

Beitrag von MuldeR »

marcel_b hat geschrieben:Du kannst auch die precondition formulieren, das withdraw <= maxWithdraw ist. das ist gültig und ok. Das Beispiel aus dem anderen Thread war etwas anders gelagert.
Der Client muss sicherstellen, dass er keine überhöhten Beträge verlangt, die Methode also mit den richtigen parametern aufgerufen wird. Im Fall von Withdraw(60) ist der Test schlichtweg nicht für eure Verträge geeignet und muss somit geändert werden.

Schönen Sonntag noch.
Danke für die Antwort. Ich frage mich halt, wie wir dann überhaupt sinnvolle Tests schreiben können. Unser Contract für Account.withdraw(charge) fordert unter anderem, dass charge <= Account.getMaxWithdrawAtOnce() sein muss. Folglich ist ein Test à la a.withdraw(60), wie bereits gesagt, nich für unsere Verträge geeignet. Der Client/Test darf ja nicht die Verträge von Acount brechen. Aber welcher Test wäre dann überhaupt möglich? Schon mal kein Test, der mit einem festen Wert arbeitet. Theoretisch müsste man es dann doch etwa so in der art machen:

Code: Alles auswählen

void test(int x) {
  if(x <= a.computeMaxWithdarw() && x <= a.getMaxWithdrawAtOnce()) {
    a.withdraw(x)
  }
}
Aber irgendwie is dieser Test dann ziemlich Witzlos, da wir ja nur genau die Fälle testen, von denen wir eh schon wissen, dass sie funktionieren werden. Der Test is quasi auf unsere Implementierung zurechtgeschneidert, so dass er 100% erfolgreich sein wird. Das ist ja irgendwie nicht der Sinn eines Tests...

(BTW: Dieser riesige screenshot ist etwas unpraktisch ^^)

Antworten

Zurück zu „Archiv“