Selenium is well know for automatic testing web pages. It does support many browsers, operating systems and languages. A Selenium IDE exists to aid you creating automated tests.

Selenium IDE
It is possible to write extensions in JavaScript to have data driven tests. If you organize your selenium tests in a way that you split the pages and forms in components, you can load up new data for the tests (written in xml) to fill out forms differently for each use case. The Selenium IDE will be of a great help here since you start develop the tests in the IDE and later ‘just’ parametrize the pages. With a couple of more javascript magic you can run these as well in Selenium Remote Control (Selenium RC for short). This even works for running the tests in continuous integration systems like Hudson or Teamcity.
Great Stuff.
The drawback coding up your tests with the Selenium IDE is: HTML.
You will end up with some test suites including many test scenarios. It will be one big ball of
<tr> <td>type</td> <td>accountnumber:Value</td> <td>82892892811</td> </tr> <tr> <td>type</td> <td>accountAmount:Value</td> <td>2345.33</td> </tr>
You will build components and you will end up with GotoIf and Label statements. Back to Basic programming from the 80ties. Without more javascript extensions you also can’t reuse combinations of scenarios.
While this works pretty ok: I’m a programmer and do have a problem with the quality of the produced code. It’s easy to mess up. It’s not safe for refactoring. To extend functionality you need to have javascript knowledge. Depending on your company profile (e.g. java shop) , this might be a problem. Having a dedicated tester on the team certainly helps to overcome these obstacles, but if everybody in the team just once in a while looks into the Selenium IDE tests, the quality and the reliability of the code will not improve. Not to mention all the copy and paste. Been there, done that.
A much nicer approach for me would be something like this:
WebDriver driver = new FirefoxDriver();
AppWebTest test = new AppWebTest(driver, "http://myapp.com/login");
LoggedInUser user = test.login("user", "password");
Account account = user.createNewAccount("DemoAccount");
account.addPerson(PersonFactory.createPersonWithNoIncome());
...
account.verify();
This would log in a user, creates an account and assigns a person to it. Everybody could read and understand it. Furthermore you can easily reuse components and logic. I would imagine that creating a DSL, matchers and finders is everything one would need to have a very nice test scenario.
All that is already possible with Selenium 1 and the language bindings (more or less).
Just with the release of the first alpha of Selenium 2 mid december 2009 it became just a little bit easier.
Selenium 2 is the combined effort of Google’s Webdriver and Selenium. Webdriver tries to tackle Seleniums problems with Javascript and the security model as well as the complex Selenium RC API.
Selenium is written in JavaScript which causes a significant weakness: browsers impose a pretty strict security model on any JavaScript that they execute in order to protect a user from malicious scripts.
…
Additionally, being a mature product, the API for Selenium RC has grown over time, and as it has done so it has become harder to understand how best to use it. For example, it’s not immediately obvious whether you should be using “type” instead of “typeKeys” to enter text into a form control. Although it’s a question of aesthetics, some find the large API intimidating and difficult to navigate.
Webdriver does not use JavaScript to control the web browser but uses native controls, e.g. an extension for Firefox or the native automation extensions of IE. Furthermore it introduces an object based API instead of following Seleniums directory based approach.
So given the above example the login method would look something like this:
public LoggedInUser login(String userName, String password) {
driver.get(url);
driver.findElement(By.id("user")).sendKeys(userName);
driver.findElement(By.id("password")).sendKeys(password);
driver.findElement(By.id("login")).click();
LoggedInUser user = new LoggedInUser(driver);
WebElement startPage = driver.findElement(By.id("startPage"));
assertThat(startPage().getText()).isEqualTo("Start");
return user;
}
You can take this to any level. It took me just a few hours to model the above use case with componentized input elements.
With a bit more abstraction addPerson looks something like the following code. Whatever is needed to add a person to an account will be replayed. This is what Selenium IDE would normally do.
public void addPerson(Person person) {
jumpToAccountOverView();
clickOnLink("menu:Overview");
clickOnLink("table_personTable:link_newPerson");
inputGuiElement(person.getTitle());
inputGuiElement(person.getLastName());
...
clickSave();
And now for the best part. You don’t really need a real web browser. Selenium 2 supports 4 WebDriver:
With enabled JavaScript on HtmlUnit (it uses Rhino, so not all JavaScript tricks might work, could be a drawback depending on your scenario) I can run my test as TestNG or JUnit test case. No need for a Selenium RC anymore. No browsers running locally or on remote machine listing to ports to execute tests and transmitting results back.
It is very useful to have the Selenium RC take screen shots during it’s run. Selenium 2 can’t do this right now (and with HtmlUnit never can). But by simply integrating some logging in the components and elements and you know right away where things went wrong.
Developing with the Selenium IDE might seem like a must, but during the work on my prototype all I needed was Firebug and a XPath Searcher. I did not miss it at all. To do integration tests you could test all components independently and one at a time. Once these work, chain them together and let them run as unit tests. Did I mention HtmlUnit is fast ?
I barely dug into the webdriver code, I played around with it for a couple of hours and I’m sure there more is stuff (and bugs) to find. Given that Selenium 2 is in alpha 1 stage right now I expect very nice things coming out of it. It certainly looks like the way to go.
Another strong contender is Canoo WebTest. I will have a look at this one in another post.
Update Ajax and HtmlUnit 1/5/2010
It does not seem to work. You need a real browser to get dynamically content re-rendered through Ajax.
A method waiting for an id to be rendered (or timeout after x seconds) could look like this:
public void waitForElementOrTimeout(String idToWaitFor, int timeout) {
long end = System.currentTimeMillis() + timeout * 1000;
RenderedWebElement resultsDiv = null;
while (System.currentTimeMillis() < end) {
try {
resultsDiv = (RenderedWebElement) driver.findElement(By.id(idToWaitFor));
}
catch (NoSuchElementException e) {
//
}
if (resultsDiv != null && resultsDiv.isDisplayed()) {
break;
}
}
assertThat(resultsDiv).isNotNull();
}
This will either find the element specified or throw an assertion (updated example from the selenium 2 page).
If you want your Business Analyst to provide some acceptance tests for your implementation it would be best to make sure you get it in some form your tests can read. That way you can make sure your code does really do that what the customers wants.
Two tools to achieve that are Concordion and Cucumber (and cuke4duke). Both taking the approach of Fit a step further. You can find very good documentation for the tools on their web pages or as examples along with the code.
Concordion let’s you write the specifications in html. You can instrument them with some Concordion code. Since the instrumentation code is in html, it is hidden when you render it.
<html xmlns:concordion="http://www.concordion.org/2007/concordion"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.concordion.org/2007/concordion ">
<body>
<p>
<span concordion:execute="setCurrentTime(#TEXT)">09:00AM</span>
The greeting for user <span concordion:set="#firstName">Bob</span>
will be:
<span concordion:assertEquals="greetingFor(#firstName)">Hello Bob!</span>
</p>
</body>
</html>
Concordion can instrument free text as well as tables to have some sort of data provider for tests. It integrates directly with JUnit (no TestNG support yet).

Concordion Specification/Fixture/System
The idea is that your specifications is stable. This will not change as much as maybe your code to fulfill the requirements of the customer. Your fixture bridges these two together.
package com.maxheapsize.concordion;
import org.concordion.integration.junit3.ConcordionTestCase;
public class DemoTest extends ConcordionTestCase {
public String greetingFor(String firstName) {
return "Hello " + firstName + "!";
}
public void setCurrentTime(String time) {
System.out.println(time);
}
}
Your java code provides the methods which are called from the specification. At that point you are most likely to use a DSL to build up your business logic. Concordion will assert the expected values.
After you run the unit test you will get a nice looking html page.

Concordion output
Passed tests will be green, failed red, pretty easy.
Pro Concordion:
Con Concordion:
Cucumber is a ruby based behaviour driven development tool. Since you can run Ruby code inside the JVM with the help of JRuby it is possible to use it for your Java code. The usually way so far is that you use maven or ant to startup JRuby and the Cucumber ruby code.
In Cucumber you write your specification (or feature) in plain english (or russian, german,lolcode or 36 or so more supported languages). No instrumentation is necessary but your customer has to stick to a certain layout (or better: structure). It follows the well known given-when-then pattern.
Feature: Demo
Scenario: A Car with 4 wheels
Given I have a car
When I add 4 wheels
Then I should be able to drive the car
You can write your step definitions (like the fixture in Concordion) with the help of annotations.
package com.maxheapsize.cucumber;
import cuke4duke.Given;
import cuke4duke.Then;
import cuke4duke.When;
import org.junit.Assert;
public class CarFeature {
private Car car;
@Given("I have a car")
public void iHaveACar() {
car = new Car();
}
@When("I add (\\d+) wheels")
public void iAddWheels(int numberOfWheels) {
car.setWheels(numberOfWheels);
}
@Then("I should be able to drive the car")
public void iShouldBeAbleToDriveTheCar() {
Assert.assertTrue(car.canDrive());
}
}
The regular expressions defined in the annotations must match the feature definitions.

Cucumber results
You can define information in a table like structure as well to provide more data in an easy way. The given when then structure can be used to have multiple when’s or then’s. So it is very flexible that way.
Pro Cucumber
Con Cucumber
It is easy to get started with both tools. I spent about a couple of hours with each of them and was able to get simple and a bit more complex examples running. They get you going with BDD and acceptance based testing.
I like parts of both concepts. I want to be able to write a feature like in Cucumber. The instrumentation should be directly on my code and not in the part of the customer. The easy setup and integration of Concordion with JUnit (need TestNG here) and the output of it is very nice. So where is the tool which has these features
.
So which tools will I use?
I have yet to see the use case where the business analyst writes the (and updates) the acceptance tests. I don’t mind add some extra code to the specification and I know html. I might get started with Concordion for now.
You might also check my post about Behavior driven development with EasyB (and vs. TestNG).
Do you use any of these tools? Or a different one? What’s your expierence?
Whenever you write some code you better make sure you have it covered by some tests. There are different possibilities on how to achieve this (and you might want to use all of them).
Unit tests are a classic. You usually write them while you code. Write tests, run failing test, write code and run passing test. Repeat. Keep this small and simple. Test the smallest unit which makes sense. You might end up with hundreds if not thousands of them. Make sure you have no external dependencies in them, like databases or external webservices. Mock them away. They are usually written in TestNG or JUnit.
These tests can run at every check in in the VCS.
Integrations Test are one step higher. You want to make sure your applications runs with all or some dependencies and things plays well together. These usually have a much higher run time since they might involve some external services. Usually you would use a framework like TestNG, nUnit for them.
These tests are run not so often since they can be very expensive.
Many applications have some sort of graphical user interface. To make sure everything works alright there are several tools like Selenium or HP Quality Center to simulate a user going through the GUI. These tests are written more towards the end of the projects since in the beginning the interface might change fast and often and the overhead to change them might be significant. You usually can not run through all paths of your applications. Choose the ones which are the most critical.
They can be run a couple of times a day depending on the size of the application and the tool (e.g. Quality Center tends to be sort of slow) you use.
If you have to implement some business rules or calculations and your client can specify these in Excel you might want to use a framework like Fit (or Fitnesse). You run your system against the values in the sheets and verify that it meets the expectation.
There are many more test possibilities and techniques to make sure the system meets the demands of the customer. These are just the ones I tend to use in my projects and I found them very useful.
Tests (and specifications) with tools like Concordion or the use of a Behavior Driven Design frameworks like EasyB are offering nice
ways to make sure the customers gets what he pays for (and he understands it). I still need to play with them.
Here is a small update on my little fun project.
I released version 0.2 of quant. Now it will recognize all TestNG annotations which do not have a TestNG group (like @BeforeMethod, @BeforeClass etc.). The method ‘reportViolation’ on ClassTester will now report whats wrong with the examined class.
[/java]
assertFalse(classTester.isInvalidTestClass(),
classTester.reportViolation());
[java]
Above code will now report:
java.lang.AssertionError: Report for Class com.maxheapsize.quant.testclasses.SetupMethodWithoutTestGroup Ignore abstract classes: true Specified TestGroups : + testUnitTest * Methods with wrong test group: - testSetUp
In this way you know what went wrong and where to look for it.
Quant is now also available in my repository. To include it add the following code to your pom.xml.
<dependency>
<groupId>com.maxheapsize</groupId>
<artifactId>quant</artifactId>
<version>0.2</version>
</dependency>
For now you need to add my mvn repository to your pom. I’m about to have it mirrored to the official servers.
Have fun.
Did you ever wondered if all tests your team wrote are really running ? How many disabled tests does your code base have? How many public void methods do not have a @Test annotation (or at the class) ?
I saw all of that in the last years.
To overcome this situation I wrote a couple of java classes which will scan your java test sources and will examine the annotations (for TestNG, sorry JUnit). The code will detect if all public void methods do have a @Test annotation (either direct on the method or on the class) or if there are disabled tests. Both signals are most likely a sign of rotten code.
If you have different test groups defined like ‘unitTest’ or ‘integrationTest’ you want to make sure all tests are in at least one test group. This ensures if you run all test groups all tests are executed.
The usage is really simple. To make sure all your tests are ok is to write just another test which will check all tests.
ClassFinder classFinder =
new ClassFinder.Builder(sourceDirectory).build();
for (Class klass : classFinder.getClassList()) {
TestClassTester testClassTester =
new TestNGTestClassTester.Builder(klass).build();
Assert.assertTrue(testClassTester.allTestMethodsHaveValidTestGroup());
}
You need to specify the source files of your code so that all java files can be scanned. It is possible to exclude certain packages to be checked (via TestNGTestClassTester.Builder(klass).addExcludedPackage("testclasses").build()). After gathering all class names from the sources the code will check for @Test annotations in the test classes. You can also specify the test groups which all test should belong to (via addTestGroup("unitTest") in the builder).
Finding disabled test methods is very simple too (one of the unit tests)
@Test
public void testNoDisabledTest() {
DisabledTestFinder unitUnderTest =
new TestNGDisabledTestFinder.Builder(TwoTestGroups.class).build();
assertFalse(unitUnderTest.hasDisabledTests());
}
This would test a single class (but you can of course feed it with the results of the ClassFinder).
If you combine the possibility to break your build because of defect test classes and a continuous integration system, you can make sure everybody will annotate their classes correctly or never disables tests (or even better use a CI with delayed check in and personal build like Jetbrains Teamcity). Of course you can define your own thresholds for e.g. 30 disabled tests are allowed (I know sometimes you just can’t avoid it).
The code depends on testng and commons-io and is released under the Apache v2 license.
I still need to figure out where to put the maven2 sources. Either Google code or GitHub ? Any ideas?
Update: Code is available at Google Code Hosting.
Things to implement:
In the last couple of month I was writing some flows using Spring Webflow 2. I knew that I (in theory) I could test them but I never did. The overall documentation of Webflow 2 is not that great at the moment, so I hope to improve that a little bit.
Spring Webflow 2 supports only JUnit tests so far. I guess this will change at some point but until then you have to pull out that junit.jar again.
Sorry for the awkward line breaks. Spring just does love long method and class names (which is not that bad).
Your Testclass needs to extends AbstractXmlFlowExecutionTests and to implement protected FlowDefinitionResource getResource(FlowDefinitionResourceFactory resourceFactory).
public class MyWebflowTest extends AbstractXmlFlowExecutionTests
protected FlowDefinitionResource getResource(
FlowDefinitionResourceFactory resourceFactory) {
FlowDefinitionResource flowDefinitionResource =
resourceFactory.createResource("flows/administration/skinManagement.flow.xml");
return flowDefinitionResource;
}
If your flow now happen to have a parent flow you are in a bit of trouble. You just can include one flow in the FlowDefinitionResource. The parent tag in your flow points to an id of the parentflow which is usually defined in a webflow configuration file. If this parent flow again has parents as well…yes, same game again.
To get a hold of these flows you need to override getModelResources[].
@Override
protected FlowDefinitionResource[]
getModelResources(FlowDefinitionResourceFactory resourceFactory) {
FlowDefinitionResource[] flowDefinitionResources =
new FlowDefinitionResource[3];
flowDefinitionResources[0] =
resourceFactory.createResource("flows/common.flow.xml");
flowDefinitionResources[1] =
resourceFactory.createResource("flows/common-exceptionHandling.flow.xml");
flowDefinitionResources[2] =
resourceFactory.createResource("flows/common-menu.flow.xml");
return flowDefinitionResources;
}
If one of your flows is using beans you want to mock them in the test. You can do that by registering with the MockFlowBuildContext. The method configureFlowBuilderContext will be called by AbstractXmlFlowExecutionTests
@Override
protected void configureFlowBuilderContext(
MockFlowBuilderContext builderContext) {
builderContext.registerBean("validationExceptionHandler",
new ValidationExceptionHandler());
builderContext.registerBean("infastructureExceptionHandler",
new InfastructureExceptionHandler());
builderContext.registerBean("springSecurityExceptionHandler",
new SpringSecurityExceptionHandler());
}
With this setup you now can start testing the flow.
...
public void testSkinManagementView() {
MockExternalContext mockExternalContext = new MockExternalContext();
startFlow(mockExternalContext);
assertFlowExecutionActive();
assertCurrentStateEquals("showSkinManagementOverview");
}
...
I think the code is pretty self explanatory.
You have several possibilities including start and resume flow and assert certain states. You can test action methods doing something (like calling a service) and making sure the correct flow is executed.
Figuring out the initial setup with the parent flow and beans was the most time consuming task. You need to make sure that you only put flow logic in the definition of the flow since that is what webflow is for. I think with some clever usage of set and result you can make things complicated and a bit tricky.
A couple of days ago I had the case that I needed to test a method with different parameters. I ended up writing a couple of test methods differing only in passing various arguments to the unit under test.
Tonight I was at a TestNG talk and while I knew most of the stuff already the DataProviders (which I heard of before but unfortunately never really payed attention to) really caught me.
Now I can create a DataProvider which generates test data. Each of these data sets will result in call of the test method with the corresponding arguments.
The following code will test a String to Property Converter if it works correctly. It takes two parameters, first the string to be converted, second the result which I will assert.
@Test(dataProvider = "convertTestDataProvider")
public void testConvert(String property, String result)
{
Properties properties =
stringPropertyConverter.convertString(property);
Assert.assertTrue(properties.get("A").equals(result));
}
Now the DataProvider must return an array of array of objects. TestNG will cast the return values to the method signature of all the tests with the corresponding annotation.
@DataProvider(name = "convertTestDataProvider")
public Object[][] convertTestDataProvider()
{
return new Object[][]{
{"A=", ""},
{"A=1", "1"},
{"A=2=3", "2=3"},
{"A=2" + StringPropertyConverter.ideaLineSeperator +
"# Comment" +
StringPropertyConverter.ideaLineSeperator + "C=1", "2"},
};
}
Here I cover 4 test cases. It is very easy to add more tests just by adding one more line with the values to test and the expected result.

TestNG results with DataProvider
This will save me some amount of time.
Good Job Cedrik & friends.
Next step: How to make sure you cover all the relevant test cases (and having a systematic way of getting there). Anybody has an Idea?
You use Spring in your application everywhere. You love it. Everything gets injected and is configured by Spring. Great. Why not use the same technology to wire up your tests?
The bottom line is: Starting the Spring Context all the time in your tests drags you down at the costs of development time.
Here is why:
1. Turnaround times are much faster
When you discover a bug which might have not been covered by tests yet (this happens to me all the time) you are much faster rerunning your tests without the application context. In the application I’m working on it takes about 20-25 seconds to run a test with application context, whereas a pure unit test just takes 1 second. Now imagine this: changing some code and rerunning tests like 100 times saves you a lot of time. The tests run faster, you don’t get distracted because you could do something else in those 20-25 seconds, like browsing some web pages (you want to be efficient and use the ’spare’ time to read up the newest stories on infoq.com). But then you need to switch windows, read something, switch back, wonder what you did, rethink the problem and so on. In the end it costs you much more time than it seems.
2. Enables you pratice Test Driven Development
Since you can run test faster without the Spring context, you can start doing Test Driven Development. Before any line of production code gets typed into your IDE, write the test. That the code does not compile is the first test, go fix it by writing the actual stub for the class which you want to test. Now onto the next test. It fails, you implement the feature and rerun the test. This goes on and on and on until you fully implemented the requirements. It forces you only to implements the things needed and not more.
Uncle Bob’s three rules of Test Driven Development
1. You are not allowed to write any production code unless it is to make a failing unit test pass.
2. You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test.
Learn more about Test Driven Development with Uncle Bob.
Without fast unit tests you just can’t do TDD.
3. No corrupt test configuration
Depending on the setup of the project you might have an extra Spring application context configuration for tests. You have to configure and keep it up to date. Things can go wrong here and will be detected very late.
If you use the application context in tests you get the beans injected but sometimes you need to mock them, because you depend on an application server, a database or an external service which you don’t want to start up every time you run tests. You must be very careful with that. Using mocks with Spring application context can be very powerful but also very dangerous. You have to make sure that you remove the mocks after you are done with your tests (and reinject the right ones) or mark the methods which are injecting mocks with the DirtiesContext annotation so Spring can reload them. I did debug forgotten DirtiesContext annotations one too many times. The mock object lives on in your context and 282 test later an exception occurs for no reason and you start debugging at a wrong place.
You will not discover those mock problems until you run the full suite of tests. Since your tests are slow you not very likely to run all the on a regular basis to see if everything plays well together. No, you run all tests right before checkin and then start to wonder why some tests somewhere fail. Depending on the size of the changes and the project it will require some substantial amount of time to find and fix the problem.
You might say: “But I need my application context in the tests. My Service which I want to test has so many dependencies, I just can’t mock them all!”
While this happened to me as well it might turn out that this service is maybe doing too much. Split the functionality into smaller packages and test them individually (that’s the S in SOLID principles). The danger in testing a complex service with mocks only is also that you might end up mocking everything and you don’t do any real tests anymore (happened to me, I threw hours of coding away).
There is one reason to run tests with a Spring application context. It’s just not in unit tests. If you want to test the integration with external services (like databases or webservices) you need to use it of course. Just make sure you really just test the interaction between your service and the external interface and not more (but even that is debatable in some cases). That’s what I call an integration test.
Do you use Spring in your unit tests? What experiences did you make?
I heard about BDD a couple of months ago and did not really pay attention. Just some weeks ago I thought that maybe I could improve our test and/or documentation. So I decided to give it a shot with the BDD implementation easyb.
According to Wikipedia:
Behavior Driven Development (or BDD) is an Agile software development technique that encourages collaboration between developers, QA and non-technical or business participants in a software project. It was originally conceived in 2003 by Dan North as a response to Test Driven Development, and has evolved over the last few years.
So I took the chance to sketch out a little example and to test it against readability.
This is the class which I would to test with BDD and classic TestNG.
package net.wehrens.easyb;
import java.util.ArrayList;
import java.util.List;
public class Car {
private boolean engineInstalled;
private int doors;
private List<Wheel> wheels = new ArrayList<Wheel>();
public void addDoor() { doors++; }
public void addEngine() { engineInstalled = true; }
public boolean isEngineInstalled() { return engineInstalled; }
public int getDoors() { return doors; }
public void addWheel(Wheel p_wheel) { wheels.add(p_wheel); }
public List<Wheel> getWheels() { return wheels; }
}
The testcode in easyb looks very descriptive (and even my Product Owner can understand what’s happening). It uses Groovy to create it’s own DSL for writing tests. Even if I don’t explain any more you have a good chance to understand it at the first read.
import net.wehrens.easyb.Car
import net.wehrens.easyb.Wheel
scenario "make a new car with one door", {
given "a new car", {
car = new Car()
}
when "a door is added", {
car.addDoor()
}
then "car should have one door", {
car.doors.shouldBe 1
}
}
scenario "make a car with an engine", {
given "a new car", {
car = new Car()
}
when "an engine is added", {
car.addEngine()
}
then "the car should have an engine", {
ensure(car.engineInstalled)
}
}
The TestNG version of this test looks a little bit tighter but not as easy to understand for non programmers.
package net.wehrens.easyb;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class CarTest {
private Car _car;
@BeforeMethod
public void setUp() {
_car = new Car();
}
@Test
public void testAddEngine() {
_car.addEngine();
Assert.assertTrue(_car.isEngineInstalled());
}
@Test
public void testAddDoor() {
_car.addDoor();
Assert.assertEquals(_car.getDoors(), 1);
}
}
The big advantage I see here is that non technical people could be able to design test, how the code should behave, what the results should be. This is an often underestimated problem when communicating with the requirements engineers. Just write the BDD tests and the developers make sure they will turn green. The neat thing is that easyb has maven 2 integration and an IntelliJ Idea plugin. This makes working with it a breeze.
Sure sounds easy, but I by myself did not had a chance yet to try that out in a more complex environment…maybe stuff for a later post.