Autorenarchiv

Maxima

Freitag, 21. August 2009

Während und auch nach des Studiums kommt es gelegentlich vor, dass man was rechnen muss. In den meisten Fällen ist der Schultaschenrechner dafür nicht geeignet und auch wenn es ein grafischer, programmierbarer Taschenrechner ist. Und Geld für ein Computeralgebrasystem (CAS) auszugeben wäre angesichts der nur gelegentlich Nutzung auch nicht angebracht. Aber es gibt noch Maxima. Maxima ist ein Nachbau des legendären Macsyma und basiert wie diese im Wesentlichen auf der Sprache Lisp.

Erste Schritte

Man startet Maxima auf der Kommandozeile mit „maxima“ und schon erhält man einen Prompt für die eigenen Eingaben. Gibt man auf z. B. „4+5;“ ein, dann gibt Maxima die richtige Lösung 9 zurück. Eingabezeilen werden mit „%i1“, „%i2“ usw. durchnummiert, die Ausgabezeilen analog mit „o“ statt „i“. Will man später darauf zugreifen, kann mit mit „%o“ und der Nummer darauf zugreifen.

(%i1) 4+5;

(%o1) 9

Variablen, Konstanten und Funktionen.

Es ist sehr einfach, Werte Variablen zuzuweisen oder Funktionen zu definieren. Mit %i, %e, und %pi kann die imaginäre Zahl, die eulersche bzw. die ludolphsche Zahl nutzen. Funktionen werden mit “:=” definiert, bei Variablen verwendet man nur den Doppelpunkt.

A : 4; Weist A den Wert 4 zu.

F(x) := x^2+x-2;

F(4); Ergibt 18.

Symbolisches Rechnen

Maxima rechnet weitestgehend mit den Ausdrücken, mit Symbolen. sqrt(4) wird sofort verlustfrei zu 2, aber sqrt(3) bleibt erst mal die Wurzel aus drei, weil die Transformation nicht verlustfrei wäre. Der Vorteil dieses Vorgehens ist, dass Rundungsfehler im Endergebnis minimal sind. Um den doch Zahlenwert der Wurzel aus 3 zu erhalten, muss man bfloat(sqrt(3)) aufrufen.

Weitere Fähigkeiten

Maxima kann z. B. Ausdrücke ausklammern, wie z. B.

(%i10) x*(x+1)-2

(%i11) expand(%o10) //Ausgabe ist dann x^2 + x -2

Und mit solve lassen sich u. a. Nullstellen errechnen.

(%i12) solve([%o10], [x]);

bzw.

(%i13) solve([%o10=0], [x]);

oder die Schnittstellen auf der Gerade 1.

(%i14) solve([%o10=1], [x]);

oder die Schnittstellen mit der Gerade f(x)=x:

(%i15) solve([%o10=x], [x]);

Fazit

Dies ist nur eine kleine Einführung in das CAS Maxima und zeigt natürlich nicht alle Funktionen und Möglichkeiten von Maxima auf, die da wären:

  • Lösen von Gleichungen, Gleichungssystemen und Grenzwerten
  • Taylorentwicklung
  • Laplace-Transformation
  • Differentiation, Integration
  • Lösen von Differentialgleichungen erster und zweiter Ordnung
  • Rechnen mit Matrizen
  • Berechnung von Eigenwerten und Eigenvektoren
  • Plotten von Funktionen, TeX-Ausgabe

Maxima ist frei und kostenlos und samt Dokumentation zu finden unter http://maxima.sourceforge.net/.


Alexander Draeger


Randomisierte parametrisierte Tests in JUnit

Montag, 04. Mai 2009

JUnit kennt neben dem Testrunner für einfache Testfälle auch noch den Testrunner für Theorien, den ich bereits vorgestellt habe, und den für parametrisierte Tests. Möchte man einen dieser beiden Testrunner verwenden, steht man unweigerlich vor dem Problem, welche Eingabedaten verwendet werden sollen. Insbesondere deren Erzeugung ist eine immer wiederkehrende und monotone Arbeit, die man sich sparen möchte.

Die in dem Artikel über  parametrisierte Tests vorgestellte Herangehensweise, ermöglicht es Zufallsdaten für parametrisierte Tests und Theorien zu liefern. Sie lässt auch viel Raum für weitere Ideen. Für gute Vorschläge und Denkanstöße wären wir dankbar.


Alexander Draeger


Organisation von Testklassenhierarchien

Freitag, 27. März 2009

Jeder weiß, dass frühzeitiges und intensives Testen die Entwicklungszeit und -kosten gering hält und trotzdem: Der Softwaretest ist ein gern vernachlässigtes Kapitel der Softwareentwicklung. Es mag vielleicht daran liegen, dass viele Unit-Tests zu viel sich wiederholenden Quelltext haben. Dabei bringen die objektorientierten Programmiersprachen Konzepte mit, die man in den Tests so einsetzen kann wie in dem getesteten System (SUT, system under test).

Die Konzepte Vererbung, Polymorphie und Generizität erlauben es, klassenorientierte Testfälle zu schreiben, die dann auf alle Unterklassen der CUT (class under test) angewandt werden können. In der Literatur gibt es dazu den Begriff des symmetrischen Testtreibers („Symmetric Driver“), was nichts anderes heißt, als dass parallel zu einer Klassenhierarchie eine Testklassenhierarchie geschrieben wird. Der Name resultiert daher, dass die Testklassenhierarchie symmetrisch zur Klassenhierarchie des SUT ist. Das beigefügte Dokumente zeigt, wie die Testfallhierarchie zu einer Klassenhierarchie aussehen kann. Strukturelemente wie die Testobjekte (OUTs, OUT = object under test) sollten protected sein, sodass Unterklassen darauf zugreifen können. Die Testmethoden sind public, wie es das Testframework, z. B. JUnit, erfordert.

Illustration der allgemeinen Testklassenhierarchie

Dass Testfälle auf Unterklassenmethoden wiederholt werden, klingt vielleicht nach Redundanz, ist aber pure Notwendigkeit. Die kürzlich mit dem Turing-Preis ausgezeichnete Barbara Liskov postulierte, dass Vorbedingungen (oder auch: der Definitionsbereich) einer überschriebenen Methode nicht eingeschränkt werden dürfen. Die Nachbedingungen dürfen eingeschränkt werden, aber nicht erweitert werden. Solche Eigenschaften sollten nach Möglichkeit durch die gesamte Klassenhierarchie erhalten bleiben und eignen sich hervorragend für der „Symmetric Driver“.

Der Aufbau der Testklassenhierarchie demonstriert die Stärke objektorientierter Modellierung. Objektorientierte Softwareentwicklung hat viele Fehlerquellen beseitigt, aber es kommen auch neue hinzu, wie z. B. eingeschränkte Vorbedingungen von überschriebenen Methoden. Die Testklassenhierarchie ist hilfreich beim systematischen Aufdecken von Fehlern des objektorientierten Entwurfes.

Die Verletzung des Liskovschen Substitutionsprinzips zeigt folgendes Beispiel:

class A {
  public void method(int n) {
     if (n < 0) throw new IllegalArgumentException();
  }
}
 
class B extends A {
 public void method(int n) {
     if (n < 1) throw new IllegalArgumentException();
  }

Die folgende Testklassenhierarchie findet den Fehler, vorausgesetzt, es werden die richtigen Datenpunkte definiert.

@RunWith(Theories.class)
class TestA {
 
   @Datapoint
   public static final Integer int1 = Integer.valueOf(0);
   protected A out;
  @Before
   public void setUp() {
     out = new A();
   }
 
   @Theory
   public smokeTest(int n) {
     assumeTrue(n>=0);
     out.method(n);
   }
}
 
class TestB extends TestA {
  @Before
   public void setUp() {
      out = new B();
   }
}

Alexander Draeger


“Theorien” in JUnit 4.4 – Anmerkung

Montag, 17. November 2008

Man kann Theorien sehr effektiv auf alle möglichen Klassen des System ausdehnen, ohne viel Zusatzarbeit. Die im Beispiel getesteten Eigenschaften bzgl. equals und hashcode müssen auch für alle Klassen gelten. Der Schlüssel hierzu ist eine generische, abstrakte Klasse, von der abgleitet wird.

package theoryexample;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
 
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;
 
@RunWith(Theories.class)
public abstract class ObjectTest &lt;ClassUnderTest&gt; {
 
	@Theory
	public void symmetrie(ClassUnderTest a, ClassUnderTest b) {
		assumeTrue(a.equals(b));
		assertTrue(b.equals(a));
	}
 
	@Theory
	public void reflexivitaet(ClassUnderTest a) {
		// assumeTrue(true);
		assertTrue(a.equals(a));
 
	}
 
	@Theory
	public void transitivaet(ClassUnderTest a, ClassUnderTest b, ClassUnderTest c) {
		assumeTrue(a.equals(b));
		assumeTrue(b.equals(c));
		assertTrue(a.equals(c));
	}
 
	@Theory
	public void equalsHashcode(ClassUnderTest a, ClassUnderTest b) {
		assumeTrue(a.equals(b));
		assertEquals(a.hashCode(), b.hashCode());
	}
 
}
package theoryexample;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
 
import org.junit.Test;
import org.junit.experimental.theories.DataPoint;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;
 
public class DreieckTest extends ObjectTest  {
	@DataPoint
	public static final Dreieck d1 = new Dreieck(0.0, 0.0, 1.0, 1.0, 2.0, 2.0);
	@DataPoint
	public static final Dreieck d2 = new Dreieck(0.5, 0.5, 1.5, 1.5, 2.5, 2.5);
	@DataPoint
	public static final  Dreieck d3 = new Dreieck(0.0, 0.0, 1.0, 1.0, 3.0, 3.0);
 
	@DataPoint
	public static final  Dreieck d4 = new Dreieck(0.0, 0.0, -1.0, -2.0, -2.0, -2.0);
	@DataPoint
	public static final  Dreieck d5 = new Dreieck(0.5, 0.5, 1.5, 1.5, -2.5, -2.5);
	@DataPoint
	public static final  Dreieck d6 = new Dreieck(-1.0, 0.0, -2.0, -2.0, -3.0, -2.0);
 
	@DataPoints
	public static final Dreieck[] nochmehrDreiecke = {
		new Dreieck(-1.0, -2.0, -2.0, -2.0, -3.0, 2.0),
		new Dreieck(-1.0, -1.0, -2.0, -2.0, -4.0, 2.0),
		new Dreieck(-1.0, 0.0, -2.0, -2.0, -5.0, 2.0),
		new Dreieck(-1.0, 2.0, -2.0, -2.0, -7.0, 2.0)
	};
 
}

Alexander Draeger