Showing posts with label tdd. Show all posts
Showing posts with label tdd. Show all posts

Wednesday, December 1, 2010

Creating a Shadow for Robolectric in 6 steps

Some days ago I asked for a pull request in Robolectric's github repo. I finished my ShadowConnectivityManager. The discussion can be read here. I will explain how I did it so someone else can continue creating Shadows :).

Why do I need a ShadowConnectivityManager?
The app we are doing right now requires internet to work so we did a nice SplashScreen that checks if internet is available.
If there is internet go to the main Activity, else show a cute msg saying that you don't have internet.

So we created a class called ConnectionStatus which has a method:

boolean isOnline();


NOTE: We should have created the test first but since we are also giving a try to roboguice we did the code before the test.


public class ConnectionStatus implements IConnectionStatus {

private ConnectivityManager cm;

@Inject
public ConnectionStatus(ConnectivityManager cm) {
this.cm = cm;
}

public boolean isOnline() {
NetworkInfo netInfo = cm.getActiveNetworkInfo();
if (netInfo != null && netInfo.isConnectedOrConnecting()) {
return true;
}

return false;
}
}


Instead of doing:
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
we can inject it :)

Ok, time to test. When I try to inject a ConnectivityManager to a test there was an exception saying that my variable should have a @Nullable annotation.

Conclusion:
Null instance in test scope == "Robolectric needs your help! Create a Shadow object!"

STEP 1: Understand how Android works
Robolectric basically mocks Android's behavior. You need to understand how it works for you to mock it correctly.

In our case:

* You use an Android's context to get the ConnectivityManager which is a SystemService.
* You ask the ConnectivityManager to give you the ActiveNetworkInfo.
* The class NetworkInfo has a method isConnectedOrConnecting() which will determine if your are online or not.

STEP 2: Decide how the Shadow should be used

We want to be able to set if we are connected or not and check if our ConnectionStatus class is returning things accordingly.
To do that we will leave a flag inside the NetworkInfo's Shadow to let the user decide what it should return.

STEP 3: Code the Shadow
In our case we need to code ShadowConnectivityManager and ShadowNetworkInfo

// With the Implements annotation we tell Robolectric which Android's class this is shadow of.
@Implements(ConnectivityManager.class)
public class ShadowConnectivityManager {

// We will hold here an instance of the ActiveNetworkInfo.
private NetworkInfo networkInfo;


// All methods that exists in the real Android's class and overridden by the shadow
// must have an Implementation annotation. I don't know what for :(
@Implementation
public NetworkInfo getActiveNetworkInfo() {
return networkInfo == null ? networkInfo = newInstanceOf(NetworkInfo.class) : networkInfo;
}
}

Ok, my first big doubt was: "Ok, I need to return a NetworkInfo but I need it to be a shadow, how do I do it?"
That what Robolectric.newInstanceOf() is for :)



@Implements(NetworkInfo.class)
public class ShadowNetworkInfo {

private boolean isConnected = true;

@Implementation
public boolean isConnectedOrConnecting() {
return isConnected;
}

/**
* Non-Android accessor
* Sets up the return value of {@link #isConnectedOrConnecting()}.
*
* @param isConnected the value that {@link #isConnectedOrConnecting()} will return.
*/
public void setConnectionStatus(boolean isConnected) {
this.isConnected = isConnected;
}
}


Pretty easy as well. We have an Non-Android accesor that we will use to set the wanted connectivity status.


STEP 4: Modify Robolectric.java

* Add your Shadow classes to the list in the getDefaultShadowClasses() method.
* Create the shadowOf methods:

public static ShadowNetworkInfo shadowOf(NetworkInfo instance) {
return (ShadowNetworkInfo) shadowOf_(instance);
}

public static ShadowConnectivityManager shadowOf(ConnectivityManager instance) {
return (ShadowConnectivityManager) shadowOf_(instance);
}

* Optional: I needed to modify ShadowApplication as well.

Why?
Application is in charge of the getSystemService's method.
We need to modify it to return our ShadowConnectivityManager just adding this:

if (name.equals(Context.CONNECTIVITY_SERVICE)) {
return connectivityManager == null ? connectivityManager = newInstanceOf(ConnectivityManager.class) : connectivityManager;
}



STEP 5: Test your Shadow
I will just copy and paste the test we did. It's quite simple:

@RunWith(WithTestDefaultsRunner.class)
public class ConnectivityManagerTest {
private ConnectivityManager connectivityManager;
private ShadowNetworkInfo networkInfo;

@Before
public void setUp() throws Exception {
Robolectric.bindDefaultShadowClasses();
Robolectric.application = new Application();
connectivityManager = (ConnectivityManager) Robolectric.application.getSystemService(Context.CONNECTIVITY_SERVICE);
networkInfo = Robolectric.shadowOf(connectivityManager.getActiveNetworkInfo());
}

@Test
public void getConnectivityManagerShouldNotBeNull() {
assertNotNull(connectivityManager);
assertNotNull(connectivityManager.getActiveNetworkInfo());
}

@Test
public void networkInfoShouldReturnTrueCorrectly() {
networkInfo.setConnectionStatus(true);

assertTrue(connectivityManager.getActiveNetworkInfo().isConnectedOrConnecting());
}

@Test
public void networkInfoShouldReturnFalseCorrectly() {
networkInfo.setConnectionStatus(false);

assertFalse(connectivityManager.getActiveNetworkInfo().isConnectedOrConnecting());
}
}


STEP 6: Share it!
Ask for a pull request!


How did our testcase end up being?


@RunWith(XTestRunner.class)
public class ConnectionStatusTest {

@Inject
private ConnectivityManager cm;

@Test
public void connectionStatusShouldBeFalseWhenOffline() {
ConnectionStatus connectStatus = new ConnectionStatus(
getConnectivityManager(false));
assertFalse(connectStatus.isOnline());
}

@Test
public void connectionStatusShouldBeTrueWhenOnline() {

ConnectionStatus connectStatus = new ConnectionStatus(
getConnectivityManager(true));
assertTrue(connectStatus.isOnline());

}

private ConnectivityManager getConnectivityManager(boolean connected) {

ShadowNetworkInfo ni = Robolectric.shadowOf(cm.getActiveNetworkInfo());
ni.setConnectionStatus(connected);

return cm;
}
}



Conclusion:

While I code this simple Shadow for ConnectiviyManager someone else created a Shadow for AsyncTask and SQLite!
Shadows amount is increasing version by version => Android TDD is getting easier and easier thanks to Robolectric.

Robolectric it's a very cool fw. I consider myself a newbie to it but I could place some commits to it ;)
The support in the mailing list is excellent and I guess in the next release we will be able to add mvn support completely.

What are you waiting to give it a try?

Sunday, November 28, 2010

Robolectric with Roboguice in the TDD mix

My last blog post was about a little skeleton I did with every piece of fw I needed to start a new Android application using TDD. Phil Goodwin post a comment saying I should try Robolectric. So here's my blog post about it :)

Robolectric is a unit test framework that de-fangs the Android SDK jar so you can test-drive the development of your Android app. Tests run inside the JVM on your workstation in seconds.


So before adding it to my skeleton I did a little test. In one of my projects I have an util class that reads some cars from a json file located at /res/raw. You can see how it went in this mail list thread.

Ok, next step to place Robolectric inside my skeleton was to mavenize it.
I opened an issue, created a new mail list thread and started working on it. Fortunately Christian Williams was interested and we started hacking the pom together.

At the time I am writing this the mvn support is working and we are missing the upload to mvn central. I guess next release of Robolectric will have the jar available through mvn central.

For the test project I was using robotium and Android-mock. I will remove them since Robolectric is ready to join my test project's pom as a dependency :)

Shorts steps to install robolectric in your mvn repo.

* Clone https://github.com/mosabua/maven-android-sdk-deployer
* mvn clean install
* Clone https://github.com/pivotal/robolectric/tree/master
* mvn clean install

My test-app's pom.xml looks like this. Finally tests don't need an android emulator to run.

My new project uses Roboguice and the first Activity we made is a SplashScreen. The layout is just a RelativeLayout with a background set, an ImageView with the app's logo in the center of the screen and a progressBar. The Activity's code looks like this:

public class SplashActivity extends GuiceActivity implements ISplashView {

@InjectView(R.id.splash_progress_bar)
private ProgressBar mProgressBar;

@Inject
private SplashPresenter mPresenter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter.setView(this);
setContentView(R.layout.splash);
}
....
}



So our first test in the application was the simple shouldHaveALogo() mentioned in the robolectric's user guide. We struggle a little with @mgv making Roboguice with Robolectric work together but we did it. This is how:

To make it work, we needed to extend RobolectricTestRunner.

public class XTestRunner extends RobolectricTestRunner {

public XTestRunner(Class testClass) throws InitializationError {
super(testClass, "../X-app/AndroidManifest.xml",
"../X-app/res");
}

@Override
protected Application createApplication() {
return new IoCApplication();
}
}


IoCApplication is just the class that extends GuiceApplication. Yep, the one that overrides addApplicationModules.

The test ends being:

@RunWith(XTestRunner.class)
public class SplashActivityTest {

private SplashActivity splashActivity;

@Before
public void setUp() throws Exception {
splashActivity = new SplashActivity();
splashActivity.onCreate(null);
}

@Test
public void shouldHaveLoadingLogo() throws Exception {
ImageView logo = (ImageView) splashActivity.findViewById(R.id.splash_logo);
ShadowImageView shadowLogo = Robolectric.shadowOf(logo);
assertThat(shadowLogo.getResourceId(), equalTo(R.drawable.loading_logo));
}
}



The constructor is used to point Robolectric to our app project's AndroidManifest and res folder. Nothing hard there.

The magic comes in the overriden method createApplication(). The parent class use it to set the application context to Robolectric.java class in this method:

public void setupApplicationState(String projectRoot, String resourceDir) {
ResourceLoader resourceLoader = createResourceLoader(projectRoot, resourceDir);

Robolectric.bindDefaultShadowClasses();
Robolectric.resetStaticState();
Robolectric.application = ShadowApplication.bind(createApplication(), resourceLoader);
}



What do we need the application for?
Because it's used in GuiceActivity. If you check the onCreate() method, it does this:

@Override
protected void onCreate(Bundle savedInstanceState) {
final Injector injector = getInjector();
scope = injector.getInstance(ContextScope.class);
scope.enter(this);
injector.injectMembers(this);
super.onCreate(savedInstanceState);
}


getInjector()? Where's does it came from?

public Injector getInjector() {
return ((GuiceApplication) getApplication()).getInjector();
}


Conclusion: Every injection with Roboguice uses GuiceApplication.
Robolectric looks like a very powerful test fw. We will definitely use it for this new project we are working on.

Next step: ConnectivityManager Shadow!

Wednesday, November 17, 2010

TDD skeleton for Android

During this days at work my task was to research about adding tests to an Android application. The truth is that right now we aren't doing tests at all. In this entry I will try to comment on every framework I have been looking into. Let's start!

My first step was getting one of our applications and try adding black box tests. My first stop is robotium.

Robotium is a test framework created to make it easy to write powerful and robust automatic black-box test cases for Android applications. With the support of Robotium, test case developers can write function, system and acceptance test scenarios, spanning multiple Android activities.

Robotium has full support for Activities, Dialogs, Toasts, Menus and Context Menus.


Robotium is founded by the same company that created the maven-android-plugin that we have been using for a long time.

This framework looks great for blackbox testing but I didn't go too deep with it. I just made a test to browse around the app and check that the app shows the screen correctly. I had an issue with a SplashScreen but the feedback was great. I am really pleased with that.

When I reached this point my thought was: "Ok, now I can add blackbox test, but I also need unit tests!"
I try adding simple unit tests to the app but I failed. Classes were too dependent and there was lot's of logic inside the Activities. So I leave the real project away for a while and create a new one to start from the scratch with a new Architecture approach.

I present my last application: ioc_app. The ultimate app to turn a String to uppercase.


Link to the source code.

This project has two modules
a) ioc_app. The android application.
b) ioc_test. The test application.

Let me introduce some of the new stuff I added to this project:

1) Presenter first
I implemented this pattern based on the paper hosted in the link above. It's quite clear.

2) Proguard
When packaging an apk, all classes of all libraries used by the program will be included, this makes the apk very huge, even exceeds the capacity of dex. ProGuard can strip unused classes and methods, make it as small as possible.


I am missing adding proguard in an specific profile. It takes some time to compile and I don't want it to run unless I am building a release. Manfred, a committer in the maven-android-plugin project, told me he already did that and he uploaded to a sample project. I need to see that.

Manfred also mentioned he was trying to add obfuscation but he couldn't do it yet. Right now it's not my first priority but it's a nice to have feature.

3) Roboguice
Just to put it in a sentence: It's Google Guice for Android. I love the fact that from now on instead of writing:

mButton = (Button) findViewById(R.id.submit);


I will write:

@InjectView(R.id.submit)
private Button mButton;


Unfortunately Guice doesn't provide a mvn repo, so to use roboguice you will need to install guice-2.0-no_aop.jar in your maven repo.

4) Android-mock
I have some issues with this one. Running tests with mocks in Eclipse i'ts a PITA. Once again I got an excellent feedback but I preferred to go for the mvn approach.

Android-mock needs two jars to work. One for compile time and another for runtime. Right now it just work from mvn. I couldn't make it work from eclipse even though I have the m2eclipse-android-integration.

What's next?
* I am willing to read more about TDD. I have now everything configured to start doing it.
* I need to setup a Hudson to take care of the CI.
* I would like to start using IntelliJ IDEA but I couldn't manage to make my project run with it. If you did it, please let me know.

I'm hoping you give me some feedback about the skeleton and how to improve it!

Other blogs with useful info:
- http://dtmilano.blogspot.com/2008/03/test-driven-development-and-gui-testing.html
- http://www.rosscode.com/blog/index.php
- http://simpleprogrammer.com/