Android Testing :: testing private methods
span.code { font-family: monospace; font-size: 1.2em; }
This article is about testing private methods in android. This is a fairly common problem in android (even in Java at large) and can be solved easily. The technique proposed here provides the additionnal benefit of using a traditional way of solving the problem in the Java world. (suspense :) )
Using the android platform, you are used to divide your application into two projects :
- one for the main source code of your application,
- one for the tests
This is a mandatory structure on android, stongly emphasized by Google. Even, if you use the android-maven-plugin, you follow this pattern, and it is also the case if you use robolectric.
Moreover, it is a common practice to name the packages of your application using the following convention (inside the AndroidManifest.xml files of both projects) :
- foo.bar
- foo.bar.tests
Where foo.bar follows more or less your web domain name in the reverse order.
If you use the same package names to name the packages of your class, and this is a fairly common and intuitive practice, then you will face a big trouble : you will only be able to test your public methods : a test class inside a package named foo.bar.tests won't be able to access the private and protected methods of a class in the package foo.bar. For instance :
In your app under test :
package foo.bar;
public class Foo {
protected int m() { return 2 };
}
In your test app :
package foo.bar.test;
import android.test.AndroidTestCase;
public class FooTest extends AndroidTestCase {
public void testFoo() {
//m() is not accessible from Foo
assertEquals( 2, new Foo().m() ); //should not compile
}
}
This code would generate a compile error. The method m() is only accessible to the classes that belong to the package foo.bar and the notion of subpackages doesn't exist in the Java programming language.
Consequently you have several options.
1. Consider testing only public methods. Actually, this makes a lot of sense. Object-Oriented programming strongly emphasize the notion of contract and testing internals of a class can appear as violation of the notion of encapsulation.
Nevertheless, there are some cases where you really want to test some internal methods as they are complex, or just as they contain more of the logic of the class than public methods, or because you divided the different problems faced by public methods into some more specialized private methods (and this is a very good practice in IT, the famous divide and conquer principle).
2. Subclassing your class under test is an alternative : you can use protected methods in your class under test. And, in your tests, whenever you need to test a protected method, although you can't invoke it directly, you can create a smaller inner class of the test class that extends the class under test and overrides every protected methods under test. This overridden method will just just invoke the super method, just to re-give it a package level visibility. The inner class protected methods will then be visible to the test class as they now belong to the same package.
Here is an example of this technique :
In your test app :
package foo.bar.test;
import android.test.AndroidTestCase;
public class FooTest extends AndroidTestCase {
public void testFoo() {
//m() is not accessible from Foo
//but it is from FooUnderTest
assertEquals( 2, new FooUnderTest().m() );
}
private class FooUnderTest extends Foo {
@Override
protected int m() { super.m() };
}
}
Although it is a common and accepted practice in Java to give a normally private method a protected visibility just to accomodate the case, purists are still under shock (and I belong to that camp), but that's nevertheless a standard in Java. But this example demonstrates the drawback of this technique as well : it requires a huge, almost useless, amount of code, just a workaround and syntactic solution to a Java problem, but contains no interesting logic and is, indeed, a useless and thus painful programming effort. Nevertheless it could please purists as there is no need to alter the system under test only for testing purposes.
3. Using introspection is also possible. Reflexion allows to bypass the built-in visibility mechanism of Java. It allows to invoke any private / protected method on an object, or to use a private / protected field.
Here is an example :
In your test app :
package foo.bar.test;
import android.test.AndroidTestCase;
public class FooTest extends AndroidTestCase {
public void testFoo() {
Method method = targetClass.getDeclaredMethod("m", new Class[0]);
method.setAccessible(true);
int actual = method.invoke( new Foo(), new Object[0]);
assertEquals( 2, actual );
}
}
Although this leads to some much more cryptic syntax and make you loose the ability to refactor your code easily (as you will have to hard code method under test names in your tests), this technique is only one that can be used to test a really private method. It will please purists as there is no need to alter the system under test only for testing purposes.
4. Avoid naming your Java packages in the same way as you name you Application package : in other words, there is absolutely no obligation to use the same name in your Java package and inside your AndroidManifest.xml.It was a bad idea from Google to call a "package" the app identifier inside an AndroidManifest file. This "package" is indeed an ID for your application inside google market for instance but it imposes no kind of constraints on Java packages inside your app and has indeed nothing to do with them.
Here is an example :
In your test app AndroidManifest.xml:
...
package="foo.bar.test"
...
In your test app Java classes :
package foo.bar; //<---That's the point !!
import android.test.AndroidTestCase;
public class FooTest extends AndroidTestCase{
public void testFoo() {
//m() is now accessible directly from Foo
assertEquals( 2, new Foo().m() );
}
}
How does this work ? Simply because test classes and classes under test will be loaded inside the same Dalvik VM, all classes loaded from both apps and test-apps will be merged at runtime. Thus there is no difference in placing a class in an app or another on this specific matter (but please note that a class under test can't depend on any class that is found in the test app dex/apk, this would result in an exception 'ClassNotFoundException : class loaded from the wrong dex').
Conclusion This latter approach is much simpler than anything else and relies on the common Java practice of giving the protected visibility level to private methods for the sole purpose of testing them.
By using the same package for your test classes and your classes under test, you can test very simply internal methods and the package structure of your test app is much clearer.
Moreover, there is an additional benefit : you can use the eclipse "create junit test" wizard on your class under test (right click on the class under test and find it in the "New >> JUnit Test Case" option). Just change the source folder field of the wizard and choose your test app source folder instead of the app folder.
This article is the first of a serie of articles that will be dedicated to testability of android applications, one of the axis of research inside the Mobile Development Team @ Octo Technology.