Unit-Tests Teil 2: Integrationstests

April 16th, 2009

Im Gegensatz zu Mocktests beschränkt sich ein Integrationstest nicht auf die JVM, sondern involviert auch Fremdsysteme wie Datenbanken, LDAPs oder Webservices. Ein Integrationstest ist auf immer gleichbleibende Testdaten im Fremdsystem angewiesen, muss aber ggf. auch den schreibenden Zugriff testen. Hier bieten sich Transaktionen an: Jede Testmethode läuft in einer eigenen Transaktion, die unabhängig vom Testergebnis immer wieder zurückgerollt wird. Somit bleibt das Fremdsystem in einem definierten Zustand. Dennoch sollten Integrationstests nicht auf den späteren Liveinstanzen ausgeführt werden, sondern immer auf dedizierte Testinstanzen. Allerdings sollte sichergestellt sein, dass die Test- und Liveinstanzen sich gleich verhalten.

Integrationstests bieten sich insbesondere für Dao-Klassen an, da hier der Zugriffscode für Fremdsysteme gekapselt wird. Gute Unterstützung bietet hier das Spring Framework mit den Annotationen @RunWith, @Transactional und @TransactionalConfiguration.

Beispiel-Code:

  1. @ContextConfiguration(locations = { "/applicationContext-test-jdbc-ldap-transactional.xml" })
  2. @RunWith(SpringJUnit4ClassRunner.class)
  3. @Transactional
  4. @TransactionConfiguration(defaultRollback = true)
  5. public class LdapUserDaoImplIntegrationTest
  6. {
  7. ...
  8.         @Test
  9.         public void createUser()
  10.         {
  11.                 // create the user
  12.                 LdapUser ldapUser = createLdapUserPeterMueller();
  13.                 MutableLdapUserIdentity identity = ldapUser.getIdentity();
  14.                 String id = identity.getSamAccountName();
  15.                 // method under test
  16.                 ldapUserDaoImpl.createUser(ldapUser);
  17.  
  18.                 LdapUser testUser = ldapUserDaoImpl.getLdapUserBySamAccountName(id);
  19.                 Assert.assertNotNull("user should now exist in ldap.", testUser);
  20.                 // test more
  21.         }
  22. }

Vorteile:

  • Das konkrete Verhalten eines Fremdsystems (z.B. Active Directory, oder einer MSSQL) kann abgeprüft werden.
  • Fremde Webservices können automatisiert auf ihre Konsistenz geprüft werden.

Mögliche Stolperfallen:

  • Integrationstests laufen nicht offline. Bei Mavenprojekten sollten sie für die Offline-Entwicklung abgeschaltet werden, da sonst der Build scheitert. Dies kann durch ein eigenes “mobile”-Profil in Kombination mit dem maven-sunfire-plugin sichergestellt werden.
  • Die Testausführung dauert bei Integrationstest im Vergleich zu “normalen” JUnit-Tests wesentlich länger. Bei steigender Anzahl von länger laufenden Tests ist ein kompletter Durchlauf der Tests auf den Entwicklungsmaschinen nicht mehr sinnvoll.

Phonak eStore nominiert zum “Master of Swiss Web 2009″

March 14th, 2009

Fantastisch: Das “eStore”-Projekt, an dem ich letztes Jahr in allen Projektphasen mitarbeiten durfte, wurde nun für den “Master of Swiss Web Award 2009″ nominiert.

Phonak eStore

Der nominierte eStore setzt auf der java-basierten Hybris PIM (Product Information Management) Platform auf, in der die Produktdaten aus mehreren Systemen (SAP, iPFG, Celum) zusammengefasst werden. Das Frontend haben wir mit Spring MVC umgesetzt. Viel Arbeit steckte auch in der Entwicklung des Carts und dem anschliessenden Bestellprozess. Zum Projekt gehörte ebenfalls das Aufsetzen der Infrastruktur: Vom Microsoft ISA 2006 als Webproxy bis hin zum eigenen Active Directory für die Kundenlogins war alles dabei.

Sowohl aus technischer als auch aus fachlicher Sicht ein absolut spannendes Projekt. An der Stelle ein dickes Dankeschön an alle Beteiligten !

Ach ja: Wer für unser Projekt abstimmen möchte muss sich erst hier registrieren.

Update 05.04.2009: Leider hats nicht zum Master gereicht. Allerdings können wir uns über Silber in der Kategorie Technology Quality und Bronze in der Kategorie Usability freuen.

Unit-Tests Teil 1: Mocks

February 27th, 2009

Diese neue Artikelserie stellt Variaten von Unit-Tests vor und geht auf die jeweiligen Eigenschaften und Einsatzzwecke ein. Im ersten Teil werden Mock-Tests vorgestellt.

In Mock-Tests wird die Interaktion des zu testenden Objekts mit einem Mock-Objekt geprüft. Konkret werden sogenannte Erwartungen definiert, die das zu testende Objekt in Form von Methodenaufrufen erfüllen muss. Das eigentliche Mock-Objekt wird zur Laufzeit durch Frameworks wie jMock oder EasyMock generiert, eine eigene Implementierung des Interfaces ist dazu nicht notwendig.

Beispiel:
Eine Service-Klasse besitzt zwei Dao (“Data Access Object”) Objekte, die den Zugriff auf eine Datenbank kapseln. Beide Dao-Klassen wurden bereits mit separaten Unit-Tests abgeprüft, nun möchten wir die Service-Klasse selbst testen (ohne dabei die Dao-Klassen nochmals zu testen). Hier greifen wir auf ein Mock-Framework zurück und testen die Service-Klasse mit Mock-Versionen ihrer Daos. Für jede Methode unserer Service-Klasse beschreiben wir nun, welche Dao-Methoden aufgerufen werden sollten (und was sie in diesem Fall zurückgeben sollen).

Vorteile:

  • Mock-Tests erlauben losgelöstes Testen und bringen den Entwickler dazu, sich intensiver mit der Interaktion seiner Objekte zu beschäftigen.
  • Es werden keine Klassen doppelt getestet.
  • Service-Klassen können ohne Abhängigkeit zu Fremdsystemen getestet werden.

Mögliche Stolperfallen:

  • Ein Mock-Test bindet sich stark an die Implementierung. Wenn es alternative Wege zur Zielerreichung gibt (z.B. überladene Methoden), dann scheitert der Test, obwohl das Resultat des Methodenaufrufs weiterhin richtig wäre.
  • Das Mocken von fremden Klassen führt zu falschen Annahmen, da der Entwickler oftmals das genaue Verhalten eines Frameworks nicht immer zu 100% nachbilden kann.
  • Die Erwartungen (sog. Expectations) der Mock-Tests werden zu komplex (Perl-Syndrom: write once – never understand again.)

Empfohlene Artikel zum Vertiefen:

Spring Security: Default AuthenticationManager der namespace-Konfiguration austauschen

January 24th, 2009

Die namespace-Konfiguration legt einen eigenen AuthenticationManager an, der sich nicht über die XML-Konfiguration austauschen lässt:

You can’t use a custom AuthenticationProvider if you are using either HTTP or method security through the namespace, but this should not be a problem as you have full control over the AuthenticationProviders that are used.

http://static.springframework.org/spring-security/site/reference/html/ns-config.html (Kapitel 2.6).

Es gibt aber Situationen, wo man genau dies möchte. Beispielsweise im Osgi-Umfeld: In einem Bundle wird ein AuthenticationManager deklariert, der von anderen Web Bundles wiederverwendet werden soll.

Der Trick (bzw. der Hack) ist, dass die BeanId des AuthenticationManager genau “_authenticationManager” heissen muss. Wenn eine solche Bean existiert, dann verwendet die namespace-Konfiguration genau diesen AuthenticationManager statt einen eigenen zu erzeugen. Der Beanname wird in der BeanIds Klasse definiert.

Vorteil der Lösung: Copy&Paste wird in den Web Bundles reduziert.
Nachteil der Lösung: Man verwendet einen nicht-dokumentierten, implementierungsabhängigen Key.

Die Vorteile überwiegen jedoch. Credits für diese Lösung gehen an Eberhard Wolff (Springsource).

Spring Security 2.0.4 – namespace-based configuration. Teil 3.

January 11th, 2009

Für jeden LDAP-Server wird mit dem ldap-server Tag eine DefaultSpringSecurityContextSource Instanz registriert.

  1. <s:ldap-server id="ldapA" url="ldaps://ldapA.ads"
  2.                 port="636"
  3.                 manager-dn="cn=LDAPAdmin, cn=Users, dc=ldapa, dc=ads"
  4.                 manager-password="1234"/>
  5.  
  6.     <s:ldap-server id="ldapB" url="ldap://ldapB.ads"
  7.                 port="389"
  8.                 manager-dn="cn=LDAPAdmin, cn=Users, dc=ldapb, dc=ads"
  9.                 manager-password="1234"/>

Jetzt fehlen für die beiden Server nur noch zwei AuthenticationProvider:

  1. <s:ldap-authentication-provider server-ref="ldapA"
  2.                 user-search-base="ou=Customer,dc=ldapa,dc=ads"
  3.                 user-search-filter="(sAMAccountName={0})"
  4.                 group-search-base="ou=Groups,dc=ldapa,dc=ads"
  5.                 group-search-filter="(member={0})" />
  6.  
  7. <s:ldap-authentication-provider server-ref="ldapB"
  8.                 user-search-base="ou=Customer,dc=ldapb,dc=ads"
  9.                 user-search-filter="(sAMAccountName={0})"
  10.                 group-search-base="ou=Groups,dc=ldapb,dc=ads"
  11.                 group-search-filter="(member={0})" />

Das ldap-authentication-provider Tag erzeugt einen LdapAuthenticationProvider mit einem BindAuthenticator und einen DefaultLdapAuthoritiesPopulator.
(Ein BindAuthenticator prüft ob Passwort korrekt ist, ein LdapAuthoritiesPopulator erzeugt GrantedAuthorities aus der Gruppenzugehörigkeit des Users)

Mit der namespace-Konfiguration werden diese Objekte automatisch erzeugt. Soll für eine der beiden Objekte eine eigene Implementierung greifen, so kann das ldap-authentication-provider Tag nicht verwendet werden. Hier kommt wieder das custom-authentication-provider ins Spiel.

Spring Security 2.0.4 – namespace-based configuration. Teil 2.

November 23rd, 2008

Beim Einsatz der namespace-Konfiguration registriert Spring Security im Hintergrund automatisch einen ProviderManager. Wenn nun (ebenfalls via namespace-Konfiguration) ein oder mehrere AuthenticationProvider konfiguriert werden, so werden sie automatisch dem ProviderManager bekannt gegeben. Dieser delegiert dann die eigentliche Authentifizierung an die konfigurierten AuthenticationProvider und erlaubt somit eine flexible Gestaltung der Authentifizierungslogik. Beispielsweise kann Spring Security den User erst gegen LDAP Server A authentifizieren, bei Misserfolg dann LDAP Server B anfragen.

Zurück zum Konfigurationsbeispiel aus Teil 1: Es ist noch unvollständig, da ein AuthenticationProvider fehlt. Spring Security liefert eine Reihe von Standardimplementierungen mit:

  • DaoAuthenticationProvider
  • LdapAuthenticationProvider
  • OpenIDAuthenticationProvider
  • CasAuthenticationProvider

Für das nächste Codebeispiel entscheiden wir uns für den DaoAuthenticationProvider, der Userattribute wie username oder password aus einem UserDetailService bezieht.

  1. <s:http>
  2.     <s:intercept-url pattern="/public/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
  3.     <s:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY " />
  4.     <s:http-basic />
  5.     <s:anonymous />
  6.   </s:http>
  7.  
  8.   <s:authentication-provider>
  9.     <s:user-service>
  10.       <s:user name="pmeier" password="secret" authorities="ROLE_USER, ROLE_ADMIN"/>
  11.       <s:user name="hmueller" password="geheim" authorities="ROLE_USER"/>
  12.     </s:user-service>
  13.   </s:authentication-provider>

Mit dieser Konfiguration haben wir zwei statische User angelegt. Mit pmeier und hmueller kann man sich nun an der Webapplikation anmelden. Was passiert aber im Hintergrund ?
Das Tag authentication-provider registriert einen DaoAuthenticationProvider. Diesem Provider wird über das Tag user-service ein UserDetailsService vom konkreten Typ InMemoryDaoImpl übergeben und mit zwei Usern statisch konfiguriert.

Alternativ hätte man für das gleiche Resultat folgendes schreiben können:

  1. <!-- DaoAuthenticationProvider with InMemoryDaoImpl -->
  2. <bean id="InMemoryProvider"
  3. class="org.springframework.security.providers.dao.DaoAuthenticationProvider">
  4.   <s:custom-authentication-provider />
  5. <property name="userDetailsService">
  6.     <bean class="org.springframework.security.userdetails.memory.InMemoryDaoImpl">
  7. <property name="userMap">
  8.         <value>
  9.           pmeier=secret,ROLE_USER, ROLE_ADMIN
  10.           hmueller=geheim,ROLE_USER
  11.         </value>
  12.       </property>
  13.     </bean>
  14.   </property>
  15. </bean>

Wichtig bei dieser Alternative ist das custom-authentication-provider Tag. Es sorgt dafür, das der ProviderManager unseren InMemoryProvider überhaupt kennt.

Im nächsten Teil sehen wir uns die Konfiguration für zwei LDAP Server an.

Domain Driven Design (2. Trivadis Java Lounge)

October 28th, 2008

Gerade komme ich von der 2. Trivadis Java Lounge zurück. Im heutigen Vortrag mit dem Titel “Domain Driven Design” zeigten Adrian Hummel und Guido Schmutz wie mit Hilfe der Java Persistance API aus einem serviceorientierten “Transaction Script”-Ansatz ein eher OO-getriebenes “Rich Domain Model” entsteht, und wo genau die Vorteile liegen (weniger redundanter Code, erhöhte Testbarkeit und “mehr” Objektorientierung).

Ebenfalls spannend fand ich eine Variante des Builderpatterns, das mit unterschiedlichen Stati und Subklassen arbeitet. Dankeschön an den Gastgeber, es war ein interessanter Abend.

Spring Security 2.0.4 – namespace-based configuration. Teil 1.

October 26th, 2008

Mit der Version 2 von Spring Security (vormals Acegi Security) wurde die Konfiguration durch die Einführung neuer Tags erheblich vereinfacht. Die neuen Tags kapseln die Erzeugung der komplexen Filter- und Service-Beans und erhöhen somit die Lesbarkeit der Konfiguration.

Mit dem http-Tag legt der Entwickler die geschützten URLs fest und bestimmt die Authentifizierungsmethode wie z.B. Http Basic oder Forms Authentication.

Im folgenden XML-Schnipsel wird der Webauftritt in einen öffentlichen und einen geschützten Bereich aufgeteilt. Die öffentlichen Seiten beginnen mit dem URL-Prefix /public, alle anderen Seiten erfordern eine Authentifizerung.

  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2.    xmlns:s="http://www.springframework.org/schema/security"
  3.    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.    xsi:schemaLocation="http://www.springframework.org/schema/beans
  5.  
  6. http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
  7.  
  8. http://www.springframework.org/schema/security
  9.  
  10.   http://www.springframework.org/schema/security/spring-security-2.0.4.xsd">
  11.  
  12.   <s:http>
  13.     <s:intercept-url pattern="/public/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
  14.     <s:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY " />
  15.     <s:http-basic />
  16.     <s:anonymous />
  17.   </s:http>

Die Reihenfolge der intercept-url-Tags ist entscheidend. Hier gilt die Regel: “most specific first”. Im access-Attribut gibt der Entwickler ein oder mehrere sog. ConfigAttribute an. Ein ConfigAttribute definiert eine Rolle oder einen Authentifizierungzustand, der benötigt wird, um eine URL aus dem Pattern aufrufen zu können. Ach ja, die Patterns müssen der Ant Pattern Notation entsprechen. Die im Beispiel verwendeten Attribute (IS_AUTHENTICATED_FULLY und IS_AUTHENTICATED_ANONYMOUS) werden in der Klasse AuthenticatedVoter definiert.

Mit dem Tag http-basic hat der Entwickler automatisch eine Http Basic Authentication registriert, d.h. beim Zugriff auf den geschützten Bereich schickt Spring Security die Http Header für die Authentifizierung mit. Der Entwickler braucht sich darum gar nicht mehr kümmern.

Allerdings ist die Konfiguration noch nicht vollständig: Es fehlt noch ein AuthenticationProvider. Zwar haben wir durch das anonymous-Tag einen AnonymousAuthenticationProvider registriert, aber es fehlt natürlich ein Provider für den nicht-anonymen Zugriff, den wir uns im nächsten Teil ansehen werden.

MvnIndex

October 7th, 2008

Der MvnIndex ist äusserst praktisch, wenn man ein neues Projekt mit Maven aufsetzt.
Neben einer “Artifact”-Websuche gibts auch ein Eclipse Plugin für die Suche nach GroupId, ArtifactId und Version.

Effective Java: Builder-Pattern

August 23rd, 2008

7 Jahre nach der ersten Auflage ist das Buch “Effective Java” von Joshua Bloch nun endlich in der zweiten Auflage erschienen. Der Klassiker wurde komplett runderneuert: Best-Practises für ‘neue’ Sprachelemente wie Generics und Enums kamen hinzu, veraltete Kapitel (wie der Ersatz von C-Strukturen) wurden gestrichen.

Neu ist auch das “Builder-Pattern”. Es kommt zum Einsatz, wenn ein Objekt viele optionale Variablen besitzt. Die bisherige, klassische Variante bei solchen Objekten ist das Schreiben von mehreren Konstruktoren (‘telescoping constructor’), die über unterschiedliche Anzahl von Parametern verfügen. Allerdings sinkt die Lesbarkeit bei mehr als 6 Parametern gewaltig (vor allem wenn die Parameter vom gleichen Typ sind).

Das “Builder-Pattern” verzichtet auf die ‘telescoping constructors’ und arbeitet stattdessen mit einer statischen inneren Klasse, die für das Erzeugen einer Instanz der äusseren Klasse zuständig ist. Der Konstruktor des Builder-Objekts enthält alle “Pflicht”-Variablen, die optionalen Variablen werden über (verkettbare) Methodenaufrufe hinzugefügt. Abschliessend wird die build Methode aufgerufen, die ein unveränderliches Objekt der äusseren Klasse erzeugt. Der Konstruktor der äusseren Klasse ist privat, d.h. nur ein Builder kann ein Objekt tatsächlich instanzieren:

Hier ein Beispiel:

  1. package ch.javablog.builderpattern;
  2.  
  3. import java.math.BigDecimal;
  4.  
  5. public class Product {
  6.         // all fields are final: immutable object
  7.         private final String id;
  8.         private final BigDecimal price;
  9.         private final String salesDescription;
  10.         private final Product baseProduct;
  11.         private final boolean approved;
  12.  
  13.         private Product (Builder builder) {
  14.                 // private Constructor can only be called from Builder
  15.                 this.id = builder.id;
  16.                 this.price = builder.price;
  17.                 this.salesDescription = builder.salesDescription;
  18.                 this.baseProduct = builder.baseProduct;
  19.                 this.approved = builder.approved;
  20.         }
  21.  
  22.         public String getId() {
  23.                 return id;
  24.         }
  25.  
  26.         public BigDecimal getPrice() {
  27.                 return price;
  28.         }
  29.  
  30.         public String getSalesDescription() {
  31.                 return salesDescription;
  32.         }
  33.  
  34.         public Product getBaseProduct() {
  35.                 return baseProduct;
  36.         }
  37.  
  38.         public boolean isApproved() {
  39.                 return approved;
  40.         }
  41.  
  42.         public static class Builder {
  43.                 // mandatory parameter
  44.                 private final String id;
  45.                 private final BigDecimal price;
  46.  
  47.                 // optional
  48.                 private String salesDescription = "";
  49.                 private Product baseProduct     = null;
  50.                 private boolean approved        = true;
  51.  
  52.                 public Builder(String id, BigDecimal price) {
  53.                         this.id = id;
  54.                         this.price = price;
  55.                 }
  56.                 public Builder salesDescription(String salesDescription) {
  57.                         this.salesDescription = salesDescription;
  58.                         return this;
  59.                 }
  60.                 public Builder baseProduct(Product baseProduct) {
  61.                         this.baseProduct = baseProduct;
  62.                         return this;
  63.                 }
  64.                 public Builder approved(boolean approved) {
  65.                         this.approved = approved;
  66.                         return this;
  67.                 }
  68.  
  69.                 public Product build() {
  70.                         return new Product(this);
  71.                 }
  72.         }
  73. }
  74.  
  75. public class ProductClient {
  76.         public static void main(String... args) {
  77.                 BigDecimal price = BigDecimal.valueOf(36.5);
  78.                 Product.Builder builder = new Product.Builder("050-0047",price);
  79.                 // create Product using the builder
  80.                 Product p = builder
  81.                                 .salesDescription("great product")
  82.                                 .approved(false)
  83.                                 .build();
  84.         }
  85. }