rosshambrick

Unit Testing – Continuous Delivery for Android (part 3)

March 17, 2015

In part 2, I covered how to get a GitHub project set up in Travis CI that automatically builds pull requests and changes to the master branch. But just validating that your project builds successfully doesn’t mean it actually works as expected when it’s run. We want to add more value to this process by implementing and unit testing to validate the business logic inside the app. After all, our goal is to have continuous delivery to build in which we have a reasonably high level of confidence actually works.

For the purposes of this post, let’s define unit tests as automated tests that:

  1. are fairly limited in scope
  2. do not rely on disk or network i/o
  3. do not test the UI

IMHO, “unit tests” that attempt to test things close to the UI are often of high cost, low value and fragile. With this in mind, we will put all the interesting domain logic inside POJO models instead of in Activities and Fragments that are closely coupled to the UI. In a later post, we will cover using Espresso to perform integration tests that drive the UI from a virtual users perspective.

Let’s get started

Luckily for us, better unit testing support was recently added to Android Studio in release 1.1, making things much easier to set up than before. Go ahead and follow the steps here to get your initial project set up. No need to re-hash everything in this post, just come back here when you’re done.

Once you’ve completed those steps:

unit-testing-continuous-delivery

  1. Create a run configuration that you can use to run all the unit tests in your project. To do this, click the drop-down in the top bar that by default says ‘app’ and select ‘Edit Configurations…’.
  2. Click the ‘+’ button to add a new JUnit run configuration as seen here.
    unit-testing-continuous-delivery
  3. Set up this new run configuration to run all tests in the test/java folder:
                   a. Open the “Test kind” drop-down and choose “All in directory”
                   b. Browse to the test/java directory that you created previously
                   c. Select your application module for classpath resolution
                   d. Verify that your ‘Before launch’ is set up with ‘Gradle-aware
                   e. Make’ instead of the default ‘Make’ to speed up our build times. 

unit-testing-continuous-delivery

 

Adding tests

After you have your project set up to run all unit testing, it’s time to add the first test. I like to add a Hello World style test just to make sure everything is hooked up correctly before continuing with the real tests. So add a file called HelloWorldTest in your newly created test/java/[package-name] folder that contains this code:

public class HelloWorldTest {
  @Test
  public void trueShouldBeTrue() {
    Assert.assertTrue(true);
  }
}

Simple enough. Now, in the top bar, select the new JUnit test configuration you just created and press run. You should see a window pop up at the bottom that looks something like this:

unit-testing-continuous-delivery

Green is good! You have successfully run your first unit test inside Android Studio.

Now let’s add a more realistic test. To do that we will start evolving our sample project to something real. We like to play darts at our office, so let’s build a cricket game scoring engine. Building a scoring engine lends itself quite nicely to a TDD workflow and we will go forward in that manner.

First, create a test that will validate that:
  Given a new cricket game
  When no players have thrown yet
  Then both players should have a score of zero

public class CricketGameTest {
  @Test
  public void newGameHasScoresOfZero() {
    CricketGame cricketGame = new CricketGame();
    assertEquals(0, cricketGame.getPlayer1().getScore());
    assertEquals(0, cricketGame.getPlayer2().getScore());
  }
}

Since this code will be mostly red, we will need to create the following CricketGame and CricketPlayer classes in the main directory so the test will compile and run.

public class CricketGame {
  private CricketPlayer player1;
  private CricketPlayer player2;

  public CricketGame() {
    this.player1 = new CricketPlayer();
    this.player2 = new CricketPlayer();
  }

  public CricketPlayer getPlayer1() {
    return player1;
  }

  public CricketPlayer getPlayer2() {
    return player2;
  }
}

public class CricketPlayer {
  private int score;

  public int getScore() {
    return score;
  }
}

Run all tests again and you will see an additional passing test result. Normally, we would make sure our tests are failing first but, since the scores are ints and initialize to zero, it will pass.

Now take it a step further and add a new test that ensures that:
  Given a new cricket game
  When a player throws two triple twenties and a single eleven
  Then the first player should have closed out the twenties bracket
    And the second player should have a score of 60.

Note: Since we will have many tests starting with a fresh game, I moved the game creation out to a @Before setup method.

@Before
public void setUp() {
  cricketGame = new CricketGame();
}

@Test
public void player1StartsWithTwoTripleTwenties() {
  CricketPlayer player1 = cricketGame.getPlayer1();
  player1.add(new Throw(TRIPLE, TWENTY));
  player1.add(new Throw(TRIPLE, TWENTY));
  player1.add(new Throw(ELEVEN, SINGLE));

  assertEquals(60, cricketGame.getPlayer2().getScore());
  assertEquals(3, player1.getHitCount(TWENTY));
  assertEquals(0, player1.getScore());
}

Implement just enough to get the project to build and the test to run without any exceptions. When you do, you will see the following results. Android studio calls out failing tests on the left with an orangish color and on the right shows you the expected vs. actual results of the failing test.

unit-testing-continuous-delivery

Now, reference the cut-throat style cricket rules and add just enough code to make this test pass. I won’t bother walking through this in detail here, but you can look at the latest branch of our previous GitHub project to see a working version of this.

Red -> Green -> Refactor

Once you’ve implemented the code that gets your test to pass and run green, it might feel like you’re done, but don’t stop there. There is a flow known as Red, Green, Refactor that says: Once you’ve created a failing test and got it to pass by implementing some code, you should then take some time to see if there are any opportunities to refactor the code to clean it up. Maybe you’ve created some duplication as part of this implementation, or the code isn’t as readable as it should be, or there’s a code smell of some variety. This is your opportunity to improve the code while remaining confident that the code still works after you’ve made some changes. All you have to do is kick off the test suite and make sure all the tests are still green.

Integrating with the CI server

We now have some level of test coverage that increases our confidence that our application is actually shippable, let’s integrate that into the build process. Actually, just kidding, we’re done. The Gradle build task that we are using to build the project already includes the test task. So there’s nothing additional to do here other than commit the code and push it up to GitHub.

Unit Testing Takeaway

Now that we’ve added automated unit tests to our project, we are protecting the most important business logic in our application from accidental regressions when things start changing down the road. And if when at a future point, you or someone else on your team makes a change that breaks the tests, the build will fail and the team will be notified. This allows us to continuously evolve and refactor the code base while maintaining a high level of confidence that nothing broke along the way.

To take this a step further, in a future post we will add automated UI tests that drive the UI as an actual user would. This will give us added confidence that our continuously delivered app works as expected. Until then…

Published March 17, 2015

Tags:

Leave a Reply

Your email address will not be published. Required fields are marked *