views:

91

answers:

4

Could a sensible unit test be written for this code which extracts a rar archive by delegating it to a capable tool on the host system if one exists? I can write a test case based on the fact that my machine runs linux and the unrar tool is installed, but if another developer who runs windows would check out the code the test would fail, although there would be nothing wrong with the extractor code. I need to find a way to write a meaningful test which is not binded to the system and unrar tool installed. How would you tackle this?

public class Extractor {

private EventBus eventBus;
private ExtractCommand[] linuxExtractCommands = new ExtractCommand[]{new LinuxUnrarCommand()};
private ExtractCommand[] windowsExtractCommands = new ExtractCommand[]{};
private ExtractCommand[] macExtractCommands = new ExtractCommand[]{};

@Inject
public Extractor(EventBus eventBus) {
    this.eventBus = eventBus;
}

public boolean extract(DownloadCandidate downloadCandidate) {
    for (ExtractCommand command : getSystemSpecificExtractCommands()) {
        if (command.extract(downloadCandidate)) {
            eventBus.fireEvent(this, new ExtractCompletedEvent());
            return true;
        }
    }

    eventBus.fireEvent(this, new ExtractFailedEvent());
    return false;
}

private ExtractCommand[] getSystemSpecificExtractCommands() {
    String os = System.getProperty("os.name");
    if (Pattern.compile("linux", Pattern.CASE_INSENSITIVE).matcher(os).find()) {
        return linuxExtractCommands;
    } else if (Pattern.compile("windows", Pattern.CASE_INSENSITIVE).matcher(os).find()) {
        return windowsExtractCommands;
    } else if (Pattern.compile("mac os x", Pattern.CASE_INSENSITIVE).matcher(os).find()) {
        return macExtractCommands;
    }

    return null;
}

}

+1  A: 

I would look for some pure java APIs for manipulating rar files. This way the code will not be system dependent.

A quick search on google returned this:

http://www.example-code.com/java/rar_unrar.asp

Eyal Schneider
that project seems to be windows-only as it uses a .dll
nkr1pt
+5  A: 

Could you not pass the class a Map<String,ExtractCommand[]> instances and then make an abstract method, say GetOsName, for getting the string to match. then you could look up the match string in the map to get the extract command in getSystemSpecificExtractCommands method. This would allow you to inject a list containing a mock ExtractCommand and override the GetOsName method to return the key of your mock command, so you could test that when the extract worked, the eventBus is fired etc.

private Map<String,EvenetCommand[]> eventMap;

@Inject
public Extractor(EventBus eventBus, Map<String,EventCommand[]> eventMap) {
    this.eventBus = eventBus;
    this.eventMap = eventMap;
}

private ExtractCommand[] getSystemSpecificExtractCommands() {
    String os = GetOsName();
    return eventMap.Get(os);
}

protected GetOsName();
{
    return System.getProperty("os.name");
}
Sam Holder
+1 injecting the map of "OS" to commands was going to be my suggestion as well
matt b
+1  A: 

Start with a mock framework. You'll need to refactor a bit, as you will need to ensure that some of those private and local scope properties/variables can be overridden if need be.

Then when you are testing Extract, you make sure you've mocked out the commands, and ensure that the Extract method is called on your mocked objects. You'll also want to ensure that your event got fired too.

Now to make it more testable you can use constructor or property injection. Either way, you'll need to make the private ExtractCommand arrays overriddable.

Sorry, don't have time to recode it, and post, but that should just about get you started nicely.

Good luck.

EDIT. It does sound like you are more after a functional test anyway if you want to test that it is actually extracted correctly.

pms1969
+1  A: 

Testing can be tricky, especially getting the divides right between the different types of tests and when they should be run and what their responsibilities are. This is even more so with cross-platform code.

While it's possible to think of this as 1 code base you are testing, it's really multiple code bases, the generic java code and code for each target platform, so you will need multiple tests.

To begin with unit testing, you will not be exercising the external command. Rather, each platform specific class is tested to see that it generates the correct command line, without actually executing it.

Your java class that hides all the platform specifics (which command to use) has a unit test to verify that it instantiates the correct platform specific class for a given platform. The platform can be a parameter to the core test, so multiple platforms can be "emulated". To take the unit test further, you could mock out the command implementation (e.g. having a RAR file and it's uncompressed form as part of your test data, and the command is a simple copy of the uncompressed data.)

Once these unit tests are in place and green, you then can move on to funtional tests, where the real platform specific commands are executed. Of course, these functional tests have to be run on the actual platform. Each functional test corresponds to a platform specific class that knows how to create the correct commandline to unrar.

Your build is configured to exclude tests for classes that don't apply to the current platform, for example, so LinuxUnrarer is not tested on Windows. The platform independent java class is always tested, and it will instantiate the appropriate platform specific test. This gives you a integration test to see that the system works end to end.

As to cross platform UNRAR, there is a java RAR scanner, but it doesn't decompress.

mdma