I use Mockito for mocking and TestNG for the actual testing framework. This makes a big impact on the usefulness of tests.
First on TestNG. Number one is the testng.xml which is a really nice way to list your groups of tests. This is great for organizing -- fast tests vs "slow" tests like integration.
Then is @DataProvider. This is in my opinion TestNG's biggest strength. Makes writing multiple scenarios simple as a test becomes the wiring, the data provider is the scenarios. This is one thing I will focus on in this post. While I will assume you are familiar with DataProvider (just read the docs if not) there are some tricks to it. In order for it to be useful you want the scenarios to be readable and to really drive your tests.
@DataProvider(name = "traderAlertScenarios")
public Object[][] traderAlertScenarios() {
return new Object[][] {
{true, true, true, Category.JTT, times(1)},
{true, false, true, Category.JTT, times(1)},
{true, true, false, Category.JTT, never()},
};
}
@Test(dataProvider = "traderAlertScenarios")
public void trader_alert_scenarios(
final boolean entitledToTrade,
final boolean preferenceEnabled,
final boolean workspaceVisible,
final Category sessionType,
final VerificationMode expectedResult
) {
mockUserAsTrader();
setEntitledToTrade(entitledToTrade);
setPreferenceEnabled(preferenceEnabled);
setWorkspaceVisible(workspaceVisible);
Event e = buildEvent(sessionType);
toTest.handle(e);
expect(mockMessageGenerator, expectedResult).generate(e);
}
Now, this unit test sets up the environment and tests that the many levels of the handler are invoked depending on the logic, in the end a message is generated and goes somewhere. This will test that the generation happened, which means all the branching logic passed. However, look at the data provider. It is incomprehensable. You need a reference every time you stare at those boolean flags, even writing more is painful.
So I cleaned it up a bit.
@DataProvider(name = "traderAlertScenarios")
public Object[][] traderAlertScenarios() {
return new Object[][] {
{ENTITLED_TO_TRADE | PREFERENCE_ENABLED | WORKSPACE_VISIBLE, Category.Auction, times(1)},
{ENTITLED_TO_TRADE | WORKSPACE_VISIBLE , Category.Auction, times(1)},
{ENTITLED_TO_TRADE | PREFERENCE_ENABLED, Category.Auction, never()},
};
}
@Test(dataProvider = "traderAlertScenarios")
public void trader_alert_scenarios(
final int flags,
final Category sessionType,
final VerificationMode expectedResult
) {
mockUserAsTrader();
setEntitledToTrade(hasFlag(flags, ENTITLED_TO_TRADE));
setPreferenceEnabled(hasFlag(flags, PREFERENCE_ENABLED));
setWorkspaceVisible(hasFlag(flags, WORKSPACE_VISIBLE));
Event e = buildEvent(sessionType);
toTest.handle(e);
expect(mockMessageGenerator, expectedResult).generate(e);
}
There. Same thing, except this time the booleans don't murder you. Now you have 3 (actually in my case 15) scenarios and all of them are readable. I spent some time with indentation to make it really easy. These are the sort of tests that make unit testing pleasant.
Of course this goes hand in hand with dependency injection and hard core OOP design. The good separation of concerns let me mock each dependency easily via Mockito, inject it, and set up all these scenarios in a snap.
Hope this helps a bit.
No comments:
Post a Comment