jeudi 29 août 2013

Easy stress tests with Gatling

A few month ago, I needed to compare the response time of several webapp pages before and after upgrading a cache component. I thought the easier and faster way to do that would be to write a shell script doing several http calls to the webapp using wget, and to compute myself the average response time. I did that, it worked and I had my expected conclusion that the new cache version was faster. Of course, I wrote my shell script in a dirty way and it finally finished to the trash.

Then I hear about Gatling, a simple alternative to JMeter to do stress tests. I tested it and indeed it was very easy to use it, and even faster than creating an homemade script.

Today I would like to show you how it is easy to write a stress test with this tool.


Start easily with the recorder



Gatling provides an HTTP recorder tool allowing to generate a Gatling test. It is a good way to generate a first test.

To do that :

1. Download a Gatling archive and extract it
2. Launch the recorder : ./bin/recorder.bat or ./bin/recorder.sh
3. Configure where the test must be generated, for that you can change the fields "package", "class name", and "ouput folder" :

Gatling recorder configuration

4. Configure your web browser to use Gatling as a proxy. As see in the recorder configuration, the http port is 8000 and the https port is 8001.

Web browser configuration
5. Press the start button in the recorder
6. Go to the pages you want to test in the web browser
7. Press the stop & save button in the recorder
8. Open the generated test. In this example the test has been generated under C:\gatling-stress-tests\com\mycompany\stresstests\MyFirstStressTest.scala
9. Delete the useless requests. For example in my stress test I don't want the requests retrieving the static resources or the google analytics requests.
10. Rename the scenario and the http methods giving comprehensive titles : we will find these titles in the test reports.
11. Change the count of users to do 50 simultaneous connections to the webapp

Congratulations you have created your first Gatling test :

import io.gatling.core.Predef._
import io.gatling.core.session.Expression
import io.gatling.http.Predef._
import io.gatling.jdbc.Predef._
import io.gatling.http.Headers.Names._
import io.gatling.http.Headers.Values._
import scala.concurrent.duration._
import bootstrap._
import assertions._

class MyFirstStressTest extends Simulation {
  val httpProtocol = http.baseURL("http://mywebsite.com")
                         .acceptHeader("image/png,image/*;q=0.8,*/*;q=0.5")
                         .acceptEncodingHeader("gzip, deflate")
                         .acceptLanguageHeader("fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3")
                         .connection("keep-alive")
                         .userAgentHeader("Mozilla/5.0 (Windows NT 6.1; WOW64; rv:22.0) Gecko/20100101 Firefox/22.0")
  val headers_1 = Map("""Accept""" -> """text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8""")
  val headers_5 = Map("""Accept""" -> """text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8""",
                      """Content-Type""" -> """application/x-www-form-urlencoded""")
  
  val scn = scenario("my first test")
     .exec(http("home page").get("""/""").headers(headers_1))
     .exec(http("login page").get("""/Account/Login""").headers(headers_1))
     .exec(http("login").post("""/Account/Login""").headers(headers_5)
                        .param("""UserName""", """jsebfranck""")
                        .param("""Password""", """my password""")
     .exec(http("news").get("""/News""").headers(headers_1))

  setUp(scn.inject(atOnce(50 user))).protocols(httpProtocol)
}

As you can see, a Gatling test is a Scala script. Don't worry if you don't know Scala, this script is easy to understand, you don't have to understand all Scala mechanisms :
  • httpProtocol variable defines the global parameters to connect to the website
  • headers_1 and headers_5 defines http headers parameters 
  • scn is the test scenario with the title "my first test"
  • the test contains 4 http calls (3 GET and 1 POST) called "home page", "login page", "login" and "news"
  • setUp allows to launch the test
  • atOnce(50 user) indicates that 50 connections will be done simultaneously

Launch the test

Launch a Gatling test is very easy :

1. Launch the gatling executable : ./bin/gatling.bat or ./bin/gatling.sh
2. Select the MyFirstStressTest test
3. Open the HTML report generated in results folder

Several graphics are generated in the report. But two graphics are particularly interesting. The first one give for each HTTP request :
  • the minimum, maximum and average response time
  • the standard deviation : deviation of the requests response time compared to the average response time. A small standard deviation means that the response time is almost the same for each request
  • the 95 percentile : means that 95% of the requests are under this response time 
  • the 99 percentile : means that 99% of the requests are under this response time



If you click on a request title, for example "news", you get more information specific to the request. Here we can see the response time evolution based on the active sessions.


Conclusion

I hope you understood it is not complicated to use a stress test tool like Gatling, even if it is rare for you to do performance benchmarks. This example is very basic and doesn't show all great features of Gatling. If you want to go further, I encourage you to read the following documentations :

dimanche 25 août 2013

EJB 2 on Jboss 7.1 example

I recently have add the opportunity to migrate an historical JEE web application from JBoss 4 to JBoss 7.1. One of the more complicated point I faced was the migration of the EJB2 components. The old xml configuration was not correct anymore and it was very difficult to find documentation, tutorials or concrete examples, even on JBoss forums. Of course this is understandable because this old version of EJB is not used anymore (hopefully!), but it still exists in several historical web applications.

The goal of this new thread is not to explain all the EJB 2 mechanisms but to share what I really missed during the migration, that is a working example of EJB2 sessions and EJB2 entities on JBoss 7.1. Since JEE 6, it is possible to package the EJB directly in a war archive, so this example will be based on that.

The source code is available here.

EJB 2 session java code

I will create an EJB 2 session called HelloWorld with a simple method returning a String object. Three classes must be created to do that :

  • The EJB interface
package com.jsebfranck.jboss.ejb2.session;

import java.rmi.RemoteException;
import javax.ejb.EJBObject;

public interface HelloWorldEJB extends EJBObject {
 public String helloWorld() throws RemoteException;
}

  • The implementation
package com.jsebfranck.jboss.ejb2.session;

import java.rmi.RemoteException;
import javax.ejb.EJBException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;

public class HelloWorldEJBBean implements SessionBean {
 private static final long serialVersionUID = 1L;

 public String helloWorld() throws RemoteException {
  return "Hello world, I am an EJB2 session";
 }
 
 public void ejbActivate() throws EJBException, RemoteException {}
 public void ejbPassivate() throws EJBException, RemoteException {}
 public void ejbRemove() throws EJBException, RemoteException {}
 public void setSessionContext(SessionContext arg0) throws EJBException, RemoteException {}
}

  • And the EJB Home
package com.jsebfranck.jboss.ejb2.session;

import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;

public interface HelloWorldEJBHome extends EJBHome {
 public HelloWorldEJB create() throws RemoteException, CreateException;
}

EJB 2 entity java code

For the EJB 2 entity example, I will create an object called Member with two fields : an id and a login. Three java objects are also needed to do that :

  • The EJBLocalObject
package com.jsebfranck.jboss.ejb2.entity;

import javax.ejb.EJBLocalObject;

public interface Member extends EJBLocalObject {
    public Long getId();
    public String getLogin();
}
  • The EntityBean
package com.jsebfranck.jboss.ejb2.entity;

import javax.ejb.CreateException;
import javax.ejb.EntityBean;
import javax.ejb.EntityContext;

public abstract class MemberBean implements EntityBean {

  private static final long serialVersionUID = 1L;
  private transient EntityContext ctx;

  public MemberBean() {
  }

  public Long ejbCreate(Long id, String login) throws CreateException {
    setId(id);
    setLogin(login);
    return id;
  }

  public void ejbPostCreate(Long id, String login) {
  }

  public abstract Long getId();
  public abstract void setId(Long id);

  public abstract String getLogin();
  public abstract void setLogin(String login);

  public void setEntityContext(EntityContext ctx) {
    this.ctx = ctx;
  }

  public void unsetEntityContext() {
    this.ctx = null;
  }

  public void ejbActivate() {}
  public void ejbPassivate() {}
  public void ejbLoad() {}
  public void ejbStore() {}
  public void ejbRemove() {}
}

  • And the EJBHome
package com.jsebfranck.jboss.ejb2.session;

import java.rmi.RemoteException;

import javax.ejb.CreateException;
import javax.ejb.EJBHome;

public interface HelloWorldEJBHome extends EJBHome {
 public HelloWorldEJB create() throws RemoteException, CreateException;
}

XML configuration


Now we have to declare the EJB entity and the EJB session in the xml configuration. A first file is needed for that, ejb-jar.xml. This file must be put in the /WEB-INF folder of the war archive.

<ejb-jar version="3.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns="http://java.sun.com/xml/ns/javaee" 
xsi:schemalocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_1.xsd">
  <enterprise-beans>
    <session>
      <ejb-name>HelloWorldEJB</ejb-name>
      <home>com.jsebfranck.jboss.ejb2.session.HelloWorldEJBHome</home>
      <remote>com.jsebfranck.jboss.ejb2.session.HelloWorldEJB</remote>
      <ejb-class>com.jsebfranck.jboss.ejb2.session.HelloWorldEJBBean</ejb-class>
      <session-type>Stateless</session-type>
      <transaction-type>Container</transaction-type>
    </session>
    <entity>
      <ejb-name>MemberEJB</ejb-name>
      <local-home>com.jsebfranck.jboss.ejb2.entity.MemberHome</local-home>
      <local>com.jsebfranck.jboss.ejb2.entity.Member</local>
      <ejb-class>com.jsebfranck.jboss.ejb2.entity.MemberBean</ejb-class>
      <persistence-type>Container</persistence-type>
      <prim-key-class>java.lang.Long</prim-key-class>
      <reentrant>false</reentrant>
      <cmp-version>2.x</cmp-version>
      <abstract-schema-name>member</abstract-schema-name>
      <cmp-field>
        <field-name>id</field-name>
      </cmp-field>      
      <cmp-field>
        <field-name>login</field-name>
      </cmp-field>
      <primkey-field>id</primkey-field>
    </entity>
  </enterprise-beans>
</ejb-jar>

Now let's configure the mapping of the EJB entity. This is done in the jbosscmp-jdbc.xml file which must be put in the META-INF folder of the war archive.

<jbosscmp-jdbc>
  <defaults>
    <datasource>java:jboss/datasources/hsqldbDS</datasource>
  </defaults>
  <enterprise-beans>
    <entity>
      <ejb-name>MemberEJB</ejb-name>
      <row-locking>false</row-locking>
      <table-name>simple</table-name>
      <cmp-field>
        <field-name>id</field-name>
        <column-name>id</column-name>
      </cmp-field>      
      <cmp-field>
        <field-name>login</field-name>
        <column-name>login</column-name>
      </cmp-field>
    </entity>
  </enterprise-beans>
</jbosscmp-jdbc>


JBoss configuration

As you noticed in the jbosscmp-jdbc.xml file, a datasource is required for the entity. This datasource is configured directly in the jboss configuration. As I launch JBoss 7.1 in a standalone mode, I put the following configuration in the $JBOSS_HOME/standalone/configuration/standalone-full.xml file.

In this example, a use a hsql database :

<subsystem xmlns="urn:jboss:domain:datasources:1.0"> <datasources> <datasource enabled="true" jndi-name="java:jboss/datasources/hsqldbDS" jta="true" pool-name="hsqldbDS" use-ccm="true" use-java-context="true"> <connection-url>jdbc:hsqldb:file:/Users/jsebfranck/Documents/database/standaloneHsqldb</connection-url> <driver>hsqldb</driver> <pool> <prefill>false</prefill> <use-strict-min>false</use-strict-min> <flush-strategy>FailingConnectionOnly</flush-strategy> </pool> <security> <user-name>sa</user-name> </security> </datasource>


Deployment in jboss

When you deploy the web application on jboss (standalone.sh --server-config=standalone-full.xml), you can see the JNDI names of both EJB in the logs. This will help us to do our lookups in the client.


19:07:56,689 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-2) JBAS015876: Starting deployment of "WarWithEJB2.war"
19:07:57,069 INFO  [org.jboss.as.ejb3.deployment.processors.EjbJndiBindingsDeploymentUnitProcessor] (MSC service thread 1-4) JNDI bindings for session bean named MemberEJB in deployment unit deployment "WarWithEJB2.war" are as follows:

 java:global/WarWithEJB2/MemberEJB!com.jsebfranck.jboss.ejb2.entity.Member
 java:app/WarWithEJB2/MemberEJB!com.jsebfranck.jboss.ejb2.entity.Member
 java:module/MemberEJB!com.jsebfranck.jboss.ejb2.entity.Member
 java:global/WarWithEJB2/MemberEJB!com.jsebfranck.jboss.ejb2.entity.MemberHome
 java:app/WarWithEJB2/MemberEJB!com.jsebfranck.jboss.ejb2.entity.MemberHome
 java:module/MemberEJB!com.jsebfranck.jboss.ejb2.entity.MemberHome

19:07:57,073 INFO  [org.jboss.as.ejb3.deployment.processors.EjbJndiBindingsDeploymentUnitProcessor] (MSC service thread 1-4) JNDI bindings for session bean named HelloWorldEJB in deployment unit deployment "WarWithEJB2.war" are as follows:

 java:global/WarWithEJB2/HelloWorldEJB!com.jsebfranck.jboss.ejb2.session.HelloWorldEJB
 java:app/WarWithEJB2/HelloWorldEJB!com.jsebfranck.jboss.ejb2.session.HelloWorldEJB
 java:module/HelloWorldEJB!com.jsebfranck.jboss.ejb2.session.HelloWorldEJB
 java:jboss/exported/WarWithEJB2/HelloWorldEJB!com.jsebfranck.jboss.ejb2.session.HelloWorldEJB
 java:global/WarWithEJB2/HelloWorldEJB!com.jsebfranck.jboss.ejb2.session.HelloWorldEJBHome
 java:app/WarWithEJB2/HelloWorldEJB!com.jsebfranck.jboss.ejb2.session.HelloWorldEJBHome
 java:module/HelloWorldEJB!com.jsebfranck.jboss.ejb2.session.HelloWorldEJBHome
 java:jboss/exported/WarWithEJB2/HelloWorldEJB!com.jsebfranck.jboss.ejb2.session.HelloWorldEJBHome

Simple servlet client

We are now able to test our EJBs. The following code calls the helloWorld method of the EJB session, then it creates a new line in the Member table.

package com.jsebfranck.jboss.servlet;

import java.io.IOException;

import javax.naming.InitialContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.jsebfranck.jboss.ejb2.entity.Member;
import com.jsebfranck.jboss.ejb2.entity.MemberHome;
import com.jsebfranck.jboss.ejb2.session.HelloWorldEJB;
import com.jsebfranck.jboss.ejb2.session.HelloWorldEJBHome;

@WebServlet("/Ejb2Servlet")
public class Ejb2Servlet extends HttpServlet {
  private static final long serialVersionUID = 1L;

  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    try {
      // EJB 2 session
      HelloWorldEJBHome helloWorldEJBHome = (HelloWorldEJBHome) new InitialContext().lookup("java:global/WarWithEJB2/HelloWorldEJB!com.jsebfranck.jboss.ejb2.session.HelloWorldEJBHome");
      HelloWorldEJB helloWorldEjb = helloWorldEJBHome.create();
      response.getWriter().println("EJB session test : " + helloWorldEjb.helloWorld());

      // EJB 2 entity
      MemberHome memberHome = (MemberHome) new InitialContext().lookup("java:global/WarWithEJB2/MemberEJB!com.jsebfranck.jboss.ejb2.entity.MemberHome");
      Member member = memberHome.create(25L, "jsebfranck");
      response.getWriter().println("EJB entity test : " + member.getId() + " - " + member.getLogin());
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
}

And we have the prove that this example is working :-)


Conclusion

I hope this example helped you to migrate your EJB2 to a newer version of your application server. Don't hesitate to contact me if you need further details to advance in your migration.


dimanche 21 juillet 2013

ElasticSearch quick start

In the beginning of July, I assisted in an ElasticSearch workshop organized by Xebia. It was a great event which gave me the opportunity to understand this tool basis. If you never heard a word about ElasticSearch, go to http://www.elasticsearch.org. To resume quickly, it is a NoSQL search engine based on Apache Lucene.

Today I would like to show you how it is easy to begin with this tool.

First launch 

  1. Download an ElasticSearch archive on http://www.elasticsearch.org/download/
  2. Extract the archive
  3. Launch elasticsearch : 
$ ./bin/elasticsearch -f
[2013-07-12 17:09:08,776][INFO ][node     ] [Centurius] {0.90.2}[2741]: initializing ...
[2013-07-12 17:09:08,785][INFO ][plugins  ] [Centurius] loaded [], sites []
[2013-07-12 17:09:11,253][INFO ][node     ] [Centurius] {0.90.2}[2741]: initialized
[2013-07-12 17:09:11,253][INFO ][node     ] [Centurius] {0.90.2}[2741]: starting ...
[2013-07-12 17:09:11,372][INFO ][transport] [Centurius] bound_address {inet[/0.0.0.0:9300]}, publish_address {inet[myComputer/127.0.0.1:9300]}
[2013-07-12 17:09:14,470][INFO ][cluster.service] [Centurius] new_master [Centurius][cuT7Cb_eQbaHyQ9fW9aCng][inet[myComputer.local/127.0.0.1:9300]], reason: zen-disco-join (elected_as_master)
[2013-07-12 17:09:14,514][INFO ][discovery] [Centurius] elasticsearch/cuT7Cb_eQbaHyQ9fW9aCng
[2013-07-12 17:09:14,542][INFO ][http     ] [Centurius] bound_address {inet[/0.0.0.0:9200]}, publish_address {inet[myComputer.local/127.0.0.1:9200]}
[2013-07-12 17:09:14,543][INFO ][node     ] [Centurius] {0.90.2}[2741]: started
[2013-07-12 17:09:14,595][INFO ][gateway  ] [Centurius] recovered [0] indices into cluster_state

ElasticSearch is now running! You can check that by going to http://localhost:9200 in your web browser. Centurius is the name of the launched instance. You can override it by changing the property node.name in the elasticsearch.yml file.

You should only try to change the configuration if you encounter a problem. The default configuration is good enough to bring the server in production.

Basic queries

ElasticSearch provides a restful API allowing to create, search or delete documents. So to create documents you have to do a HTTP POST or PUT request, to search documents you have to do a GET request, and to delete documents you have to do a DELETE request.

Create a document


I want to create a simple database containing e-commerce products. Each document is created in a specific index under a specific type. From whose coming from SQL world like me, the index is similar to a SQL database instance whereas the type is similar to a SQL database table. To store my products documents, I call the index “catalog” and the type “product”. The document content must be in JSON.

To create a document, you have to respect the following syntax :

$ curl -XPOST 'http://<host name>/<index>/<type>/' -d '{<your json document>}'

For example :

$ curl -XPOST 'http://localhost:9200/catalog/product/' -d '{ 
"title" : "Ipad 2", "manufacturer" : "Apple", "submissionDate" : "2013-07-12", 
"text" : "tablette tactile" }'
{"ok":true,"_index":"catalog","_type":"product",
"_id":"iZ_SSvyBRbOBYeRHWIjSaA","_version":1} 

Congratulations, you have created your first document. Each document has an unique identifier. Here the identifier has been generated automaticaly because I did a POST request, it is "iZ_SSvyBRbOBYeRHWIjSaA".

List all documents


To check that the document has been correctly created, you can list all the indexed documents with the following command :

$ curl -XGET 'http://localhost:9200/catalog/_search'
{"took":2,"timed_out":false,"_shards":{"total":5,"successful":5,"failed":0},
"hits":{"total":1,"max_score":1.0,"hits":[{"_index":"catalog","_type":"product",
"_id":"iZ_SSvyBRbOBYeRHWIjSaA","_score":1.0, "_source" : { "title" : "Ipad 2", 
"manufacturer" : "Apple", "submissionDate" : "2013-07-12", 
"text" : "tablette tactile" }}]}} 

Search documents by term


If you want to search every document with a title equals to "ipad" :

$ curl -XGET 'http://localhost:9200/catalog/product/_search' -d '{
   "query" : {
     "match" : {
       "title" : "ipad"
     }
   }
 }'

Please note that you can search documents in an entire index (/catalog) or just on a type (/catalog/product).

Search documents with wildcard query


If you want to search every document with a title starting with "ip" :

$ curl -XGET 'http://localhost:9200/catalog/product/_search' -d '{
   "query" : {
     "wildcard" : {
       "title" : "ip*"
     }
   }
 }'

Search documents with fuzzy query


If you want to search a document from a term potentially misspelled, you can do a fuzzy query. For example the following query allows to search the products with a title like ipod or ipad :

$ curl -XGET 'http://localhost:9200/catalog/product/_search' -d '{
   "query" : {
     "fuzzy" : {
       "title" : "ipud"
     }
   }
 }'

Delete a document


If you want to delete a document, you have to do a DELETE HTTP request :
$ curl -XDELETE 'http://localhost:9200/catalog/product/_query' -d '{
    "match" : {
      "title" : "Iphone 5"
    }
}'

Understand ElasticSearch clustering

The ElasticSearch-Head plugin allows to see the cluster state. To install it :

$ ./bin/plugin -install mobz/elasticsearch-head
-> Installing mobz/elasticsearch-head...
Trying https://github.com/mobz/elasticsearch-head/zipball/master... (assuming 
site plugin)
Downloading ...........DONE
Identified as a _site plugin, moving to _site structure ...
Installed head

Now, without restarting ElasticSearch, you can check the cluster state here : http://localhost:9200/_plugin/head.





We can find our ElasticSearch instance called Centurius. This instance is composed, by default, of five shards (property index.number_of_shards). Each shard is a Lucene instance automatically managed by ElasticSearch. When you create a document, the document id is used to determine in which shard it must be stored.

Now we can launch a new instance of ElasticSearch (elasticsearch -f in a new tab of your console). If you refresh the ElasticSearch-Head page, you constat that a new instance apperead in the cluster :


If you check the logs of the first instance :

[2013-07-12 17:35:42,209][INFO ][cluster.service] [Centurius] added 
{[Reaper][Zqma1_5qQLCxbA0JQAO1BA][inet[/192.168.0.37:9301]],}, 
reason: zen-disco-receive(join from node[[Reaper][Zqma1_5qQLCxbA0JQAO1BA]
[inet[/192.168.0.37:9301]]])


The new ElasticSearch instance has been automatically discovered by unicast (by default). Each ElasticSearch instance from the same network and with the same cluster name is gathered in the same cluster. You can change the cluster name of an instance in the file config/elasticsearch.yml, property cluster.name.

Now let's add a third instance of ElasticSearch :






By default, each shard is replicated twice (property index.number_of_replicas) : one primary shard used for read and write operations, and one replica shard used for read operations.

Here the node Centurius contains the primary shards 1, 3 and 4. The node Noh-Varr contains the primary shards 0 and 2. Finally, the node Reaper contains only replica shards.

Conclusion


I hope this thread gave you the basis to work with ElasticSearch and to understand its clustering mechanism. Don't hesitate to play with ElasticSearch, change its properties and do more complicated queries! Besides this thread, a good way to start with this tool and to understand every concept is the ElasticSearch glossary page. Have fun!

lundi 1 juillet 2013

Why it is faster to write unit tests?

"We don't have the time to write unit tests", "we write the unit tests because it is mentioned in the definition of done", "we write the unit tests at the end of the sprint"... How many times did you hear or maybe say that?

The purpose of this thread is to prove that writing unit tests, particularly before testing in runtime, can help you to be more productive and efficient. To begin, let's compare the development process with and without unit tests.

Without unit tests

How a developer works without unit tests?
  1. Development phase
    • Write the entire code of the feature
  2. Runtime test phase
    1. Constat a bug
    2. Analyse where the bug comes from
    3. Fix the bug
    4. Test if the correction is correct
    5. Continue the test phase until all tests are correct

With unit tests

Now, how a developer works with unit tests?
  1. Development phase
    1. Write a unit test
    2. Write the corresponding code
    3. Write another unit test until the feature is completely developed
  2. Runtime test phase
    1. Constat a bug
    2. Analyse where the bug comes from
    3. Write a unit test reproducing the bug
    4. Fix the bug
    5. Test if the correction is correct
    6. Continue the test phase until all tests are correct

Runtime test phase is expensive

The runtime test phase (with or without unit tests) takes a lot of time for several reasons :
  • For each test, you have to prepare a set of data, even for the margin cases
  • You have to launch your application
  • You have to play your test and maybe find a bug
  • For each bug, you have to find where to fix the bug, possibly using a debugger
  • Then, you have to fix your code
  • Finally, you test again to be sure that your fix is correct. For that, of course, you have to prepare a new set of data

Runtime test phase is longer without unit tests

With unit tests, you can be sure to find most of the bugs directly during the development phase. Of course there are still some bugs but less than if you write no unit tests at all.

So if you have more bugs without unit tests, the runtime test will be longer.

Now, how can be sure without unit tests that a bug fix hasn't broke a functional case you have already test? Are you sure all your code is still working? Maybe you should restart the runtime test phase from the beginning? And what about refactoring your code?

With unit tests, you can be very confident to haven't broke anything because all the tests you have already written can prove you that. Plus, you won't be afraid to do refactoring in order to have a cleaner and more comprehensible code, what will help you to work more efficiently during the next developments.

Conclusion

Of course it is faster to write a code without doing units tests. But how much extra time do you spend testing in runtime or fixing bugs in production?

Less bugs also mean less stress and more time to work on interesting things.

In bonus, you do more code and less functional tests. That's cool, coding is what you are good for!

What about integration tests?

An answer to resolve the cost of the manual tests are the integration tests. Do you still need to do unit tests with integration tests? It is enough to do only unit tests? It is a very interesting question I will try to answer in another thread. But yes, unit tests and integration tests are complementary.

samedi 29 juin 2013

How to test dates in java unit tests?

Testing dates in unit tests is not always an easy stuff. Take a look to the following method, I will then explain you what is the main problem you can encounter.

public class UserService {
  @Inject
  private UserEventDao userEventDao;

  @Inject
  private DateUtility dateUtility;

  public void createCreationUserEvent(User user) {
    UserEvent event = new UserEvent();
    event.setUser(user);
    event.setUserEventType(UserEventType.CREATION);
    event.setEventDate(new Date());
    userEventDao.create(event);
  }
}

To test this method, you have to check that the UserEvent object passed to the UserEventDao.create method is correctly filled. For that you can use a mock framework like Mockito and write the following test :

@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
  @InjectMocks
  private UserService userService;

  @Mock
  private UserEventDao userEventDao;

  @Test
  public void createCreationUserEvent_withCorrectParameters_shouldCreateAnEvent() {
    // Given
    User user = new User();

    // When
    userService.createCreationUserEvent(user);

    // Then
    UserEvent expectedUserEvent = new UserEvent();
    expectedUserEvent.setUser(user);
    expectedUserEvent.setUserEventType(UserEventType.CREATION);
    expectedUserEvent.setEventDate(new Date());

    verify(userEventDao).create(expectedUserEvent);
    verifyNoMoreInteractions(userEventDao);
  }
}

The problem is that this test is not in success all the time because the Date object precision is in milliseconds and you can have a little difference between the date created in the method and the date created in the test.

The solution to resolve that is to be sure to manipulate the same Date object between the method and your test. Of course you could add a Date argument in your createCreationUserEvent method but this would just move our problem to the calling method.

I recommend you two solutions to do that : the first one is to use PowerMock and the second one is to create a DateUtility class.

Solution 1 : Using PowerMock

PowerMock is a mock framework allowing you to mock what cannot be mocked by others frameworks : static method calls, private methods, object constructions etc. To do that, Powermock manipulates the bytecode of the class to test.

So in our example, it can mock the new Date() call :
@RunWith(PowerMockRunner.class)
@PrepareForTest(UserService.class)
public class UserServiceTest {

  @InjectMocks
  private UserService userService;

  @Mock
  private UserEventDao userEventDao;

  @Test
  public void createCreationUserEvent_withCorrectParameters_shouldCreateAnEvent()
  throws Exception {
    // Given
    User user = new User();
    Date eventDate = new Date();
    whenNew(Date.class).withNoArguments().thenReturn(eventDate);

    // When
    userService.createCreationUserEvent(user);

    // Then
    UserEvent expectedUserEvent = new UserEvent();
    expectedUserEvent.setUser(user);
    expectedUserEvent.setUserEventType(UserEventType.CREATION);
    expectedUserEvent.setEventDate(eventDate);

    verify(userEventDao).create(expectedUserEvent);
    verifyNoMoreInteractions(userEventDao);
  }
}

As you surely noticed, we use the PowerMock runner and a PrepareForTest annotation which indicates to Powermock that the UserService class bytecode must be modified.

You can now be sure that your test will be in success at each execution.

Solution 2 : Creating a date utility class

In this solution, the concept is to delegate the creation of the date to a new class :

public class DateUtility {
  public Date getCurrentDate() {
    return new Date();
  }
}

And to use this class in UserService :

public class UserService {

  @Inject
  private UserEventDao userEventDao;

  @Inject
  private DateUtility dateUtility;
  
  public void createCreationUserEvent(User user) {
    UserEvent event = new UserEvent();
    event.setUser(user);
    event.setUserEventType(UserEventType.CREATION);
    event.setEventDate(dateUtility.getCurrentDate());
    userEventDao.create(event);
  }
}

Then we can mock the call to DateUtility in our unit test :

@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {

  @InjectMocks
  private UserService userService;

  @Mock
  private UserEventDao userEventDao;
  
  @Mock
  private DateUtility dateUtility;

  @Test
  public void createCreationUserEvent_withCorrectParameters_shouldCreateAnEvent() {
    // Given
    User user = new User();
    Date eventDate = new Date();
    doReturn(eventDate).when(dateUtility).getCurrentDate();

    // When
    userService.createCreationUserEvent(user);

    // Then
    UserEvent expectedUserEvent = new UserEvent();
    expectedUserEvent.setUser(user);
    expectedUserEvent.setUserEventType(UserEventType.CREATION);
    expectedUserEvent.setEventDate(eventDate);

    verify(userEventDao).create(expectedUserEvent);
    verifyNoMoreInteractions(userEventDao);
  }
}

Like in the Powermock solution, you can now be sure that your test will be in success at each execution.

Conclusion : Powermock vs DateUtility

Firstly, these two solutions can be applied to every other case where you have to manipulate dates. It works all the time.

The advantage of the Powermock solution is that you don't have to modify your business code to write a good test. However, the bytecode manipulation done by the framework is expensive in term of execution time : in my environment, this simple test needs about 400 ms to be executed whereas the test with Mockito needs almost 0 ms. On more complicated tests with several static classes mocked, this execution time can be even worst : I already saw entire tests classes executed in almost 8 seconds, which can be very problematic in term of productivity.

Concerning the DateUtility solution,  I think it is elegant to have a class with the responsability to manipulate dates. With that, you shouldn't have any "new Date()" call or Calendar manipulation in other classes. And of course, the main bonus is that you can write a good unit test. I would so recommend you this solution!

I hope you enjoyed this thread and I would be very glad to hear which others tricks do you use to test your dates. Also, I you encounter a case where the DateUtility solution cannot help you, I would love to help you.

jeudi 13 juin 2013

Tips to write maintainable unit tests

Because unit tests are considered by a lot of developers as a waste of time, they make no efforts to write a code of quality. It is perhaps not a problem for the developer to understand his tests while he is developing his feature. But when he needs to modify the feature behavior and therefore his tests several weeks later, he will encounter some difficulties to understand his own tests and it will be expensive to modify them.

However it is easy to write a readable and maintainable unit test. You just have to follow several tips.

Tip #1 : give a comprehensive name to each test

Your unit test name should be comprehensive enough to understand which method you are testing and which case you are testing. To achieve that, three information are mandatory in your method name :
  • the name of the tested method
  • the input parameters of the tested method
  • what is the expected behavior of the tested method
If you respect this rule, it is not mandatory anymore to write other documentation (like javadoc in Java). The method name is comprehensive enough. For example :

 @Test  
 public void filterInvalidUsers_withAnInvalidUser_shouldReturnAnEmptyList() {  
 }   

Ok I understand you can be chocked to see an underscore in the method name. But it is a little trick to improve the visibility of what your test is doing. Moreover you can easily identify if you have forgotten a case using the method listing of your favorite IDE :


Tip #2 : use Given / When / Then template

Every unit test has three sections of instructions :
  • the test preparation
  • the call to the tested method
  • the assertions
In some unit tests, it is not always obvious to find these sections. So a little trick to easily identify them is to comment the beginning of each section. "Given" for the test preparation, "When" for the call to the tested method, and "Then" for the assertions. Doing that for each of yours unit tests give a kind of template easily understandable by every developer. For example :

@Test  
public void filterInvalidUsers_withAnInvalidUser_shouldReturnAnEmptyList() {  
   // Given  
   User user = new User();  
   user.setName("userName");  
   user.setBirthDate(new Date());  
   user.setStatus(-1);

   // When  
   List<User> users = userService.filterInvalidUsers(Arrays.asList(user));       

   // Then  
   assertNotNull(users);  
   assertTrue(users.isEmpty());   
 }   

Tip #3 : use reusable methods for the Given and Then sections 

Even if it is logical to factorize your code, this basic coding principle is not always applied to the unit tests. Some developers prefer to copy & paste an entire unit test, just change the test name and one of the assertions, whereas they could create an utility method used by several tests.

An exemple of factorization of our test method could be :

@Test  
public void filterInvalidUsers_withAnInvalidUser_shouldReturnAnEmptyList() {  
   // Given  
   User user = createUserWithCorrectParameters();  
   user.setStatus(-1);

   // When  
   List<User> users = userService.filterInvalidUsers(Arrays.asList(user));       

   // Then
   assertThatThereIsNoUser(users);     
 } 

private User createUserWithCorrectParameters() {
   User user = new User();  
   user.setName("userName");  
   user.setBirthDate(new Date());  
   user.setStatus(10);
   return user;
}

private void assertThatThereIsNoUser(List<User> users) {
   assertNotNull(users);  
   assertTrue(users.isEmpty()); 
}      

 

Tip #4 :  mock all interactions to other objects

An important principle of the unit tests is to test only the behavior of the tested class, and not the behavior of all the objects used by this class. If you don't respect this principle, you could have impacts on several test classes each time you change the behavior of a single class, which is a waste of time.

To do that you can mock every other objects used by the tested class. Mock an object means simulate his behavior and not do a real call to the object. Great Java librairies allow to do this job, for example Easymock or Mockito. I will do a comparative of these two solutions in another thread.

 

Tip #5 : use partial mocking to have smaller tests 

Imagine you want to add a createUsers method in the same class of the filterInvalidUsers method :

public void createUsers(List<User> users) {
    List validUsers = filterInvalidUsers(users);
    if (! validUsers.isEmpty()) {
        userDao.createUsers(validUsers);
    }
}

You have already written your unit tests on filterInvalidUsers and you don't want to write them again. How can you avoid that? A solution is to use partial mocking. Whereas classic mocking allows you to mock the objects used in your tested class, the partial mocking allows you to mock a method in the class you are testing. So you can simulate the call to the method then verify that the call has been performed. For example, in the createUsers unit tests, you can mock the call to the filterInvalidUsers. In Java, EasyMock and Mockito both allow to do partial mocking.

 

Conclusion

 I hope theses few tips will help you to write more maintainable unit tests. Don't hesitate to share your own techniques by commenting this thread. I would be very glad to share with you.