For additional information, see the Open Source Documentation.
Test Cases
Test cases are the driving force for Test-driven development. LimeWire runs automatic tests for every check-in (smaller subset of tests), in addition to nightly tests.
LimeWire defines automated tests in two categories, Unit and Integration tests:
- Unit tests
- Run quick test cases (no writing to disk, no setting changes)
- Only test a specific component
- Extend from BaseTestCase
- Integration tests
- May take longer than a few seconds to run
- Writes or reads from disk
- Might make setting changes (for example, changing the default Shared folder)
- Could need access to LimeWire folders
- Extend from LimeTestCase or MojitoTestCase
In addition to more assertion checks, LimeWire test classes make it easier to get to LimeWire folders and settings, to change LimeWire settings and you get extra features when running tests through ant (run individual or multiple tests, and run a test several times).
Various test classes used in LimeWire:
- TestCase - JUnit test class
- BaseTestCase - intended for fast test cases, only tests a specific component (considered unit test)
- LimeTestCase - use when you need LimeWire folders, are writing to disk, slightly slower test case
- getSaveDirectory()
- getSharedDirectory()
- getStoreDirectory()
- MojitoTestCase - use when you need to test Mojito
TestCase
TestCase extends Assert to give you numerous overloaded methods:
- assertFalse
- assertTrue
- assertEquals
- assertNotNull
- assertNotSame
- assertNull
- fail (can be used when code gets to code that should be unreachable)
It's best not to directly inherit from TestCase because you wouldn't get the added features of BaseTestCase.
BaseTestCase
BaseTestCase extends TestCase and adds the ability to run individual or multiple tests from ant. Also, with BaseTestCase you can run individual test methods with ant.
The following test class extends from BaseTestCase because the test is quick (doesn't do any writing to disk and doesn't have to wait for any external events) and doesn't use many outside LimeWire components. The class does use a BitField object, however you could have used a jMock to remove this behavior dependence.
public class NotViewTest extends BaseTestCase { private NotView nv; private BitSet bs; private BitField bf; public NotViewTest(String name) { super(name); } public static Test suite() { return buildTestSuite(NotViewTest.class); } @Override protected void setUp() throws Exception { bs = new BitSet(); bs.set(1); bs.set(3); bs.set(4); bs.set(6); bf = new BitFieldSet(bs, 9); //bf 010110100 nv = new NotView(bf); //nv 101001011 } public void testCardinality(){ assertEquals(nv.cardinality(), 5); } public void testGet(){ assertNotSame(nv.get(0), bf.get(0)); assertNotSame(nv.get(1), bf.get(1)); assertNotSame(nv.get(2), bf.get(2)); assertNotSame(nv.get(3), bf.get(3)); assertNotSame(nv.get(4), bf.get(4)); assertNotSame(nv.get(5), bf.get(5)); assertNotSame(nv.get(6), bf.get(6)); assertNotSame(nv.get(7), bf.get(7)); assertNotSame(nv.get(8), bf.get(8)); } public void testMaxSize() { assertEquals(9, nv.maxSize()); } public void testNextClearBit(){ //index 012345678 //nv = 101001011 assertEquals(nv.nextClearBit(0), 1); assertEquals(nv.nextClearBit(1), 1); assertEquals(nv.nextClearBit(2), 3); assertEquals(nv.nextClearBit(3), 3); assertEquals(nv.nextClearBit(4), 4); assertEquals(nv.nextClearBit(5), 6); assertEquals(nv.nextClearBit(6), 6); assertEquals(nv.nextClearBit(7), -1); assertEquals(nv.nextClearBit(8), -1); } public void testNextSetBit(){ //index 012345678 //nv = 101001011 assertEquals(nv.nextSetBit(0), 0); assertEquals(nv.nextSetBit(1), 2); assertEquals(nv.nextSetBit(2), 2); assertEquals(nv.nextSetBit(3), 5); assertEquals(nv.nextSetBit(4), 5); assertEquals(nv.nextSetBit(5), 5); assertEquals(nv.nextSetBit(6), 7); assertEquals(nv.nextSetBit(7), 7); assertEquals(nv.nextSetBit(8), 8); } }
LimeTestCase
The following test case needs the LimeWire shared folders and therefore it extends from LimeTestCase.
public class SharingSettingsTest extends LimeTestCase { public SharingSettingsTest(String name) { super(name); } public static Test suite() { return buildTestSuite(SharingSettingsTest.class); } /** * Tests if all settings are set to the default save directory. */ public void testUnsetMediaTypeDirectoriesLoops() { MediaType[] types = MediaType.getDefaultMediaTypes(); for (int i = 0; i < types.length; i++) { assertEquals("Should be the save directory", SharingSettings.getSaveDirectory(null), SharingSettings.getFileSettingForMediaType(types[i]).getValue()); } } /** * Tests if all settings are set to the default save directory. */ public void testUnsetMediaTypeDirectories() { assertEquals("Should be the save directory", SharingSettings.getSaveDirectory(null), SharingSettings.getFileSettingForMediaType(MediaType.getAnyTypeMediaType()).getValue()); assertEquals("Should be the save directory", SharingSettings.getSaveDirectory(null), SharingSettings.getFileSettingForMediaType(MediaType.getDocumentMediaType()).getValue()); assertEquals("Should be the save directory", SharingSettings.getSaveDirectory(null), SharingSettings.getFileSettingForMediaType(MediaType.getProgramMediaType()).getValue()); assertEquals("Should be the save directory", SharingSettings.getSaveDirectory(null), SharingSettings.getFileSettingForMediaType(MediaType.getAudioMediaType()).getValue()); assertEquals("Should be the save directory", SharingSettings.getSaveDirectory(null), SharingSettings.getFileSettingForMediaType(MediaType.getVideoMediaType()).getValue()); assertEquals("Should be the save directory", SharingSettings.getSaveDirectory(null), SharingSettings.getFileSettingForMediaType(MediaType.getImageMediaType()).getValue()); } }
MojitoTestCase
The MojitoTestCase class performs setup and cleanup tasks to create a local DHT to use in the testing environment, in addition to providing BaseTestCase features.
public class PingManagerTest extends MojitoTestCase { public PingManagerTest(String name){ super(name); } public static TestSuite suite() { return buildTestSuite(PingManagerTest.class); } @Override protected void setUp() throws Exception { super.setUp(); setLocalIsPrivate(false); } public void testParallelPings() throws Exception { PingSettings.PARALLEL_PINGS.setValue(3); NetworkSettings.MAX_ERRORS.setValue(0); MojitoDHT dht1 = null, dht2 = null; try { dht1 = MojitoFactory.createDHT(); dht1.bind(new InetSocketAddress(2000)); dht1.start(); dht2 = MojitoFactory.createDHT(); dht2.bind(new InetSocketAddress(3000)); dht2.start(); Set<SocketAddress> hosts = new LinkedHashSet<SocketAddress>(); hosts.add(new InetSocketAddress("www.apple.com", 80)); hosts.add(new InetSocketAddress("www.microsoft.com", 80)); hosts.add(new InetSocketAddress("www.google.com", 80)); hosts.add(new InetSocketAddress("www.cnn.com", 80)); assertEquals(4, hosts.size()); try { ((Context)dht2).ping(hosts).get().getContact(); fail("Ping should have failed"); } catch (ExecutionException e) { assertTrue(e.getCause() instanceof DHTTimeoutException); } hosts.add(new InetSocketAddress("localhost", 2000)); assertEquals(5, hosts.size()); try { Contact node = ((Context)dht2).ping(hosts).get().getContact(); assertEquals(dht1.getLocalNodeID(), node.getNodeID()); } catch (ExecutionException e) { fail(e); } } finally { if (dht1 != null) { dht1.close(); } if (dht2 != null) { dht2.close(); } } } }
Create the test suite
Implement suite() which returns a Test object. suite() is JUnit's way of defining what tests are run. You can specify an entire class, and JUnit then figures out which methods to call (those which start with 'test'), or you can set what methods to run.
By calling suite() you can use the extra features when running through ant.
public static Test suite() { return buildTestSuite(NotViewTest.class);//typical to specify the entire class name }
You can pass an individual test case name to run through buildTestSuite(Class cls, String tests). For example:
public static Test suite() { String test = "testCardinality"; return buildTestSuite(NotViewTest.class, test); //only calls one method }
You can pass an array to list what tests to run through buildTestSuite(Class cls, String[] tests). For example, to run testCardinality multiple times (won't run any other method that starts with 'test'):
public static Test suite() { String[] tests = new String[5]; tests[0] = "testCardinality"; tests[1] = "testCardinality"; tests[2] = "testCardinality"; tests[3] = "testCardinality"; tests[4] = "testCardinality"; return buildTestSuite(NotViewTest.class, tests); //calls the same method five times }
Automatically called methods
You can use the following methods which are called my JUnit automatically:
- constructor - pass the name to the extended class
public MyTestCaseClass(String name) { super(name); }
- setUp() - prepare the tests
@Override protected void setUp() throws Exception { //Test Case class specific setup tasks }
- tearDown() - run at the end of a test suite, this method should close network connections, etc.
protected void tearDown() throws java.lang.Exception{ //Test Case class specific tear down tasks }
Add test methods
Methods must all be public voids without any arguments and named testSomething() for the test suite to automatically call.
For example:
public void testMaxSize() { assertEquals(9, nv.maxSize()); }
Test Results
You can run tests through Eclipse, ant, or maven.
1. Eclipse Go to the test class, click "Run As > JUnit Test"
2. ant - when running from ant, you can specify the class and or method to run.
3. maven, For example, where NotViewTest is variable, but "mvn -Dtest=Variable test" is constant:
> mvn -Dtest=NotViewTest test ------------------------------------------------------- T E S T S ------------------------------------------------------- Running org.limewire.collection.NotViewTest Running test: testCardinality Running test: testUsingMocks Running test: testGet Running test: testMaxSize Running test: testNextClearBit Running test: testNextSetBit Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.125 sec Results : Tests run: 6, Failures: 0, Errors: 0, Skipped: 0
Things to avoid
- System.out.println's (not kept for overnight tests )
- Sleeps - makes the tests run longer than usual; there are thousands of tests, if each one had a second sleep, that would be bad. Instead try to use a CountDownLatch.
- Host specific tests (specific folder structure, expectation that a folder/file exists/doesn't exist)
- Don't favor more efficient code, as opposed to more readable code. It is better to manually iterate through the items because it's easier to debug.
For example:
for(int i = 0; i < 3; i+){ assertEquals(nv.get(i), i); }
vs.
assertEquals(nv.get(0), 0); assertEquals(nv.get(1), 1);
If you want to iterate, use error messages to help with debugging.
String s; for(int i = 0; i < 2; i++){ s = "i is: " + i; assertNotSame(s, nv.get(i), nv.get(i)); }
However, use your best judgment to decide when to manually iterate compared with iterating. For example in the following code you would have built in test code if more media types were added.
public void testUnsetMediaTypeDirectories() { MediaType[] types = MediaType.getDefaultMediaTypes(); for (int i = 0; i < types.length; i++) { assertEquals("Should be the save directory", SharingSettings.getSaveDirectory(null), SharingSettings.getFileSettingForMediaType(types[i]).getValue()); } }
Tips in writing tests
- Check out the test code coverage to see weak test coverage areas
- Test each code branch for a method. For example, if a method can return for various conditions, test each return block of code.
- Each test should test one feature.
- You can simplify a complex classes interactions with one of your test object by using JMock or some other proxy based testing tool to allow you to override behaviors.
- Use dependency injection to make testing easier which lets you isolate the parts of the code you actually want to test
jMock
Mocks allow developers to worry about the behavior of an object instead of the dependencies of the object. The mock mimics the object that you need and has methods which contain assertions.
public void testUsingMocks(){ Mockery context = new Mockery(); //BitField can only be mocked if it is an interface or if the class //has a default constructor final BitField mockedBitField = context.mock(BitField.class); //NotView#nextSetBit() relies on BitField's implementation of //nextClearBit. Therefore, mock the implementation of BitField //to return specific values for nextClearBit() between the range [0,8]. //For value 0 for nextClearBit, the mock will return 0. //For value 1 for nextClearBit, the mock will return 1. NotView nv = new NotView(mockedBitField); //The expectation uses the double brace, see http://www.jmock.org/expectations.html //You can tell the mocked method how many times to expect to be called. //http://www.jmock.org/cardinality.html: //one, exactly, atLeast, atMost, between, allowing, ignoring, never context.checking(new Expectations() { { //atLeast, fails if not called at least once. one(mockedBitField).nextClearBit(with(equal(0))); will(returnValue(0)); //allowing can be called any number of times, but doesn't fail if not allowing(mockedBitField).nextClearBit(with(equal(1))); will(returnValue(1)); //allowing can be called any number of times, but doesn't fail if not atLeast(1).of(mockedBitField ).nextClearBit(with(equal(2))); will(returnValue(1)); } }); assertEquals(nv.nextSetBit(0), 0);//can only run "one" time assertEquals(nv.nextSetBit(1), 1); assertEquals(nv.nextSetBit(1), 1);//ok because "allowing" set assertEquals(nv.nextSetBit(2), 1);//must run "atLeast" once }
Dependency Injection
The LimeWire code was refactored to use Google Guice's Dependency Injection. Check out our Tech blog articles to find out the decisions why (to make testing easier) and how the task was done.


