Testing Singleton-Patterns with ANT
Michael Ingold | 19.04.2012Singleton patterns: We all know and love them! Altough I came across a problem that puzzled me for a bit, so I wanna share that little piece of experience with you guys. So here’s the first bit of code:
public class StorageLevelObserver implements IArticleObserver
{
private static StorageLevelObserver instance;
private ch.hslu.appe.stock.Stock legacy;
private StorageLevelObserver(ch.hslu.appe.stock.Stock legacy)
{
if(legacy == null)
{
this.legacy = new StockLocal();
}
else
{
this.legacy = legacy;
}
}
/**
* Gets the Singleton-Instance of a StorageLevelObserver
* @return The StorageLevelObserver to be used.
*/
public static StorageLevelObserver getStorageLevelObserver(ch.hslu.appe.stock.Stock legacy)
{
if(StorageLevelObserver.instance == null)
{
StorageLevelObserver.instance= new StorageLevelObserver(legacy);
}
else
{
//if(legacy != StorageLevelObserver.instance.legacy)
//{
// StorageLevelObserver.instance.legacy = legacy;
//}
}
return StorageLevelObserver.instance;
}
public void notify(IObservableArticle article, String property) { ... }
}
Take a moment to realize what the code actually does, ignoring the commended part of getStorageLevelObserver()! getStorageLevelObserver() is in this case of a Singleton, is the function to construct the internal instance of StorageLevelObserver and saves the reference to the private static referencevariable instance, therefor using an if to check if the reference is already set or not. I introduced a new parameter to the getStorageLevelObserver()-Method because I needed give the Observer a stock to order Items from, if the local stocklevel is too low. (This is done within the notify-Method! It isn’t important, so don’t bother)
Of course i also want to test such an important class, so i set up two new Testcase for two different scenarios to be testet. That’s probably the place where the legacy-Field comes in, that you have been asking yourselves about multiple times until now. Legacy holds a reference to an Instance of Stock, an Object that represents the central Stock, from whitch our stock has to order, if the local level of Items of a specific article is too low. The StorageLevelObserver -Class’s objective is to keep the local stocklevels within given boundaries!
When testing i inserted my own StockHelper-Objects, that implement the Stock-Interface, and help me checking that a change within an article correctly triggers an order to the Legacy-Stock-System, thereby filling the local stock. For the two testcases I also wrote two different StockHelpers. The beginning of each TestCase was the same: Get the StockLevelObserver-Instance by also directly passing the according StockHelper as an actual parameter. The Tests in my IDE succeded, using JUnit and Eclipse as an IDE. Me, quiet happy, ran the tests again using ant test. The tests failed! What!?!?!?
I could not find the solution to my problem using the eclipse Debugger, and debugging using ant…Well not so much either. It puzzeled me for a while, then I got it. The singleton pattern and it’s behavior within the JVM-Environment seem to be the problem. Eclipse apparently creates a separate instance of JVM for each TestCase, while ANT doesn’t. So what exactly happend?
Using Eclipse and thereby one separate instance of the JVM for each Testcase, the singleton was created twice, using the two different StockHelpers as intended, because the block within the IF-Statement in getStorageLevelObserver() was executed twice!!! That does not happen if one and the same JVM-Instance is used for both TestCases. In that case the internal Field instance is already set and the constructor will not be called, thereby not setting legacy to the new StockHelper-Object and cause my tests to fail, because the wrong StockHelper was used the second time!
When I realized that, I created a new IF-Block whose content will be executed if the new legacy-Object differs from the old one, thereby setting legacy to the new value, without having to call the private constructor (not necessary, creates overhead only…). It solved my problem, and hopefully keeps you from being puzzeld by the same buggy code yourself.
Edit: The problem seems to be the same even if the StockHelper will not be changed during runtime. If the instance of JVM is not destroyed after one test-Run, instance might still be set, provoking the same odd behavior as mentioned above. (Apparently ANT behaves sometimes like that – that was the problem for me too)






