Skip to content

Entries from December 2013.

Classic Testing vs Design By Contract

Automated unit tests are hard to write. Software architecture must be designed carefully to allow unit testing. You have to spend time to write tests as well and it's not easy to write good tests. It's easy to make big mess that is hard to maintain after few weeks.

On the other hand automated integration tests are hard to maintain and are fragile. You can "write" them pretty easy in a record-and-replay tool, but later they show their real cost during maintenance.

But there's an answer for problems mentioned above. Do you know Eiffel language? The language has special built-in constructs that support executable contract specification. It's called Design By Contract (DBC). DBC is easier to write and to maintain because no special test cases need to be specified, just conditions for method parameters, expected results and state invariant that must be preserved. How DBC can substitute old-fashioned tests? Read on!

General concepts for DBC are:

  • pre-condition: you can specify rules for every input parameter to check contract fulfillment from caller side. If a pre-condition is not met that means there's something wrong with caller
  • post-condition: rules for post-method execution state can be specified as well. You can refer to input parameters, current object state and original object state (before method call)
  • class invariants: refers to object members and must be valid after every public class call

As an example I'll use DBC implementation called "pycontract" (Python language). It's pretty easy to introduce in a project and uses standard doc-strings (like doctest module). No preprocessing is needed.

Pre-condition is specified by pre keyword:

pre:
    someObject != None and someObject.property > 0

Post-condition syntax uses some special syntax to allow to refer to old values, you have to mention mutable variables object that should be available for state change comparisions:

post[self]:
    self.property = __old__.self.property + 1

Above statement shows that property should be incremented by one after method return.

Invariant syntax is as follows:

inv:
    self.state in [OPEN, CLOSED, SENT]

All above statements must be embedded in method/class comments, here's full example:

class SampleClass:
    """
    inv:
        self.state in [OPEN, CLOSED, SENT]
    """
    def open(self, x):
        """
        pre: x > 0
        post: self.state != CLOSED
        """

DBC assertions (like doc-strings) are disabled by default (they are just comments). You can decide to enable them for example in development builds (there's an overhead related to DBC). An example how to enable pycontract:

import contract
contract.checkmod(modulename1)
contract.checkmod(modulename2)

Example failure (postcondition not met) caught by DBC:

DokumentFiskalny.testDokumentu() Traceback (most recent call last):
  File "test.py", line 164, in
    run_tests()
  File "test.py", line 144, in run_tests
    run_test_module(db, dao, moduleName)
  File "test.py", line 54, in run_test_module
    exec("%s.%s(env)" % (sName, sItem))
  File "", line 1, in
  File "", line 3, in __assert_testDokumentu_chk
  File "lib/contract.py", line 1064, in call_public_function_all
    return _call_all([func], func, va, ka)
  File "lib/contract.py", line 1233, in _call_all
    result = func.__assert_orig(*va, **ka)
  File "/home/darek/public_html/kffirma/DokumentFiskalny.py", line 433, in testDokumentu
    df.zapisz(args)
  File "/home/darek/public_html/kffirma/DokumentFiskalny.py", line 254, in zapisz
    args["idPodmiotuGospodarczego"],
  File "", line 3, in __assert_DokumentFiskalny__create_chk
  File "lib/contract.py", line 1165, in call_private_method_all
    return _method_call_all(getmro(cls), method, va, ka)
  File "lib/contract.py", line 1205, in _method_call_all
    return _call_all(a, func, va, ka)
  File "lib/contract.py", line 1241, in _call_all
    p(old, result, *va, **ka)
  File "", line 3, in __assert_DokumentFiskalny__create_post
  File "/home/darek/public_html/kffirma/DokumentFiskalny.py", line 155, in _create_post
    assert len(result) >= 2

The last part of this puzzle is coverage generation. DBC assertions are evaluated at runtime, so you have to ensure enough percentage code is execute during test runs. Im my experience you can rely on:

  • integration-like scenarios (without explicit result verification)
  • random input generation

For both cases you can integrate at high, UI level (keyboard, remote controller, HTTP, ...).

Focus on quality techniques is crucial for any software project. If you (1) pay some effort in making your codebase resistant to regressions and (2) maintain that state during project lifetime you can gain more stable software. The real craft lies in selecting proper tools and having enough energy to implement them.

JPA + ObjectDB example

JPA stands for "Java Persistence API" - an API that tries to replace direct Hibernate (or other ORM) usage with more generic layer that allows to replace underlying ORM with no application code changes.

Although ORM (object relational mappers) typically use relational database under the hood JPA is not restricted to relational databases. I'll present how object database (objectdb) can be used using JPA interface.

All the source code can be located here: https://github.com/dcieslak/jee-samples/tree/master/jpa.

Classes

Firstly, you have to define your model and relations using POJO with JPA annotations. We try to make model as simple as possible, so no getters/setters pairs if not really required.

@Entity
class SystemUser implements Serializable {

    @Id @Column @GeneratedValue(strategy=GenerationType.AUTO)
    public Integer userId;

    @Column
    public String systemUserName;

    @Column
    public Departament department;

    public String toString() {
        return "SystemUser(" + systemUserName + ", " + department + ")";
    }
}

A bit of explanation:

  • @Entity: annotation marks class that will be mapped to persistent entity. If the name of corresponding table is the same as class name you don't have to specify it explicitly. It makes class description more compact
  • @Id: marks given property to be primary identifier of a class. Uniqueness is assumed for the column.
  • @GeneratedValue: primary key must be assigned an unique value when object is created. By this annotation you can select generation method for identifiers.
  • @Column: a property of the persisted entity.
  • Departament department: here we have relation many to one to Department class. It's implemented as foreign key pointing to Department table with index for speeding up access.
  • toString(): in order to be able to print object description to the stdout I've redefined toString() method, it makes debugging much easier.

Department can be defined in a very similar way.

Setup

In order to use JPA (except classpath dependencies) you have to initialize the persistence engine. As stated above we will use objectdb implementation of JPA.

        EntityManagerFactory factory = Persistence.createEntityManagerFactory(
            "$objectdb/db/samplejpa.odb");
        EntityManager em = factory.createEntityManager();

Here we will use database stored in db/samplejpa.odb file in the classpath where objectdb.jar is stored. Of course for production deployment client-server architecture will be more efficient, we leave file usage for sake of setup simplicity.

CRUD operations with JPA

CRUD operations look very familiar for anyone coming from Hibernate world:

        em.getTransaction().begin();

        System.out.println("Clearing database:");
        em.createQuery("DELETE FROM SystemUser").executeUpdate();
        em.createQuery("DELETE FROM Departament").executeUpdate();

        System.out.println("Two objects created with 1-N association:");
        Departament d = new Departament();
        d.departamentName = "Dept1";
        em.persist(d);
        SystemUser u = new SystemUser();
        u.systemUserName = "User1";
        u.department = d;
        em.persist(u);

        System.out.println("Querying database:");
        TypedQuery<Departament> dq = em.createQuery("SELECT D FROM Departament D", Departament.class);
        for (Departament d2: dq.getResultList()) {
            System.out.println("Loaded: " + d2);
        }
        TypedQuery<SystemUser> sq = em.createQuery("SELECT S FROM SystemUser S", SystemUser.class);
        for (SystemUser s: sq.getResultList()) {
            System.out.println("Loaded: " + s);
        }

        em.getTransaction().commit();

Some notes to above code:

  • Every database updates set must be enclosed in a transaction (begin() / commit())
  • TypedQuery<T> interface is a very nice extension to old-style Query interface that was not parametrized.
  • persist(obj) is responsible for storing object in a database, JPA implementation discovers target table by reflection
  • DB state mutable queries: executed by executeUpdate() call - sometimes can save much time when handling big database tables

Maven project

And finally: you have to build/execute all the example app. I've selected Maven as it makes collecting dependencies much easier (and you don't have to store jars in your source code repository).

  <repositories>
    <repository>
      <id>objectdb</id>
      <name>ObjectDB Repository</name>
      <url>http://m2.objectdb.com</url>
    </repository>
  </repositories>

  <dependencies>
    <dependency>
      <groupId>com.objectdb</groupId>
      <artifactId>objectdb</artifactId>
      <version>2.2.5</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <configuration>
              <source>1.5</source>
              <target>1.5</target>
          </configuration>
      </plugin>
      <plugin>
          <groupId>org.codehaus.mojo</groupId>
          <artifactId>exec-maven-plugin</artifactId>
          <version>1.2</version>
          <executions>
            <execution>
              <phase>test</phase>
              <goals>
                <goal>java</goal>
              </goals>
              <configuration>
                <mainClass>samplejpa.Main</mainClass>
              </configuration>
            </execution>
          </executions>
      </plugin>
    </plugins>
  </build>

In order to build application execute: mvn test and the result of program run will be presented on screen.