Showing posts with label maven. Show all posts
Showing posts with label maven. Show all posts

Saturday, February 16, 2013

Continous Integration with Dropwizard, ClassRule, ClassPathSuite, Maven and RestAssured

I recently had a chance to do several projects using dropwizard. I found it to be a great tool. Basically, it's a rest container built using embedded jetty, jersey, guava, logback bundled with it. Details can be seen here . This tool was developed and open sourced by yammer.

DropWizard is started as an executable jar file with java -jar .. command. By default DropWizard's main thread blocks after starting embedded instance of jetty. This causes some issues with continous integration as during build process server has to be started, and stopped so tests can be executed against container. Out of the box this could be achieved by executing jar from inside test code, (using Runtime.exec()) but this is really undesirable solution.

Fortunately, this has been addressed by several people by using ServerCommand (available since release 0.6.1 - I believe).
See links below:
forum
EmbeddableServerCommand
TestableServerCommand

I've created a project to wrap all this together in a bundle which makes it easier to get started. Project is posted on github in dropwizard-ci repository.

Key source classes
Here are key classes in the project

EmbeddableServerCommand -  pretty much copy/paste from the gist listed above.
EmbeddableService  - wraps command ensuring that EmbeddableServerCommand has been added to bootstrap, 
 Services extending it, just have to make sure to call super.initialize(..).

Key test classes
NOTE: Most of the credit for test classes listed below belong to David Drake (who's blog can be found here ), as he was leading development effort of classes below.

TestContext - starts and stops server when needed. 
Notice that TestContext is instantiated with false flag passed into constructor in each test except in the test suite (DWCITestSuite) where it's created with true value. See this piece of code in TestContext class:
    @Override
    protected void starting(Description description) {
        if (forSuite == IN_SUITE) {
            startDropWizardServer();
        }
    }
This avoids starting and stopping container for each test when running within test suite, but makes it happen when runing individual tests

Testing
See HelloResourceIT. Running it as a test from an IDE will start server (because overriden starting method of TestWatcher (TestContext) instantiated in the class ), and verifies whether endpoint returns expected response using restassurred client.

Maven builds.
Maven plugin is configured to only execute DWCITestSuite, which runs all tests.

 maven-surefire-plugin
  
    
      **/DWCITestSuite.class
    
  
 
To test the setup pull project down from the github at: https://github.com/pwyrwa/dropwizard-ci.git and execute following maven command:
mvn clean test
Check standard output from maven build to verify that both integration tests have been executed, but server has been started only once.

Setup shown above blends unit and integration tests (as they're all executed in test phase). I'll fix that in my next post and update project in github.

Sunday, July 29, 2012

Unit/Integration testing with Maven and Embedded Cassandra

I've been using Cassandra on my current project. I was able to utilize embedded cassandra feature of cassandra-unit to create a testable application without the need of installing/configuring Cassandra on developer boxes.

I've created a sample application showing setup/code skeleton I used. Project is available on github here. It requires java 5 or higher (tested it with 7) and maven 3.x installed.

Required dependencies and configuration files
1. Cassandra-unit (see pom snippets below)


        
            com.netflix.astyanax
            astyanax
            ${astyanax.version}
            
             .....
            
        
        
            org.cassandraunit
            cassandra-unit
            ${cassandra.unit.version}
            test
            
             ....
            
        
      
Note: Pull project here for the full dependency list.

3. cassandra.yaml - Configuration used to start embedded version of Cassandra. Check out file in the project for configuration details. Note that all the directories required by Cassandra configured in this file are children of target directory.

4. dataset.json - Keyspace and column family definition

Test case setup and layout
Although, I'm not a big fan of using inheritance, I found that defining base class with common functionality and having tests extend it to be the easiest, cleanest approach in this case.

BaseCassandraTest.java (fragments)
   .....
    @BeforeClass
    public static void startCassandra()
            throws IOException, TTransportException,
            ConfigurationException, InterruptedException
    {
        EmbeddedCassandraServerHelper
        .startEmbeddedCassandra("cassandra.yaml");
    }

    @Before
    public void setUp() throws IOException, 
     TTransportException, ConfigurationException, InterruptedException
    {
        DataLoader dataLoader = new DataLoader("TestCluster", 
         "localhost:9272");
        dataLoader.load(new ClassPathJsonDataSet("dataset.json"));

        AstyanaxContext context = 
         new AstyanaxContext.Builder().forCluster("TestCluster")
                .forKeyspace("test_keyspace")
                .withAstyanaxConfiguration(
                 new AstyanaxConfigurationImpl()
                 .setDiscoveryType(NodeDiscoveryType.NONE))
                .withConnectionPoolConfiguration(
                        new ConnectionPoolConfigurationImpl(
                        "testConnectionPool").setPort(9272)
                                .setMaxConnsPerHost(1)
                                .setSeeds("localhost:9272"))
                .withConnectionPoolMonitor(
                 new CountingConnectionPoolMonitor())
                .buildKeyspace(ThriftFamilyFactory.getInstance());

        context.start();
        keyspace = context.getEntity();
        cassandraAccessor = 
         new CassandraAccessor(new ObjectMapper(), keyspace);
    }

    @After
    public void clearCassandra()
    {
        EmbeddedCassandraServerHelper.cleanEmbeddedCassandra();
    }

    @AfterClass
    public static void stopCassandra()
    {
        EmbeddedCassandraServerHelper.stopEmbeddedCassandra();
    }
   ...
Class listed above defines lifecycle for each Cassandra test. Embedded Cassandra is started/stoped before, after class, and keyspace is tore down, cleaned and recreated for each test.

This setup has worked pretty well. Alternate approach would be to have a test cluster setup and just configure client to connect to the cluster during tests.