Back

Android Unit Testing Concepts

Testing is a key activity in any software development effort. Manual testing (e.g., with QA personnel) allows a lot of flexibility, but testing old features repeatedly can be mind-numbing and cause testers to miss things. Manual testing also usually involves a full end-to-end system, so it can be difficult to test edge cases and error conditions because they can be difficult or impossible to recreate on-demand in the real system.

Automated testing is a powerful practice that allows one to establish a set of tests that can all be run programmatically. This can greatly reduce testing time compared to manual testing, and it’s also a very good approach for repetitive tests against existing features. It can be especially effective when combined with Continuous Integration.

Within the realm of automated testing are two important sub-types: functional testing and unit testing. Unit testing is the practice of exercising portions of one’s code in isolation from any external dependencies (such as servers) and usually from the rest of the application. It’s a powerful form of testing because the developer has complete control over all aspects of the test, including pre-conditions and post-conditions, and it’s easy to setup and tear down the tests since no external environments are involved.

Unit testing is good at testing whether you built the thing right. This can be contrasted with functional testing, which verifies if you built the right thing. One way to think about this is that a unit test can determine if one correctly parses a sample JSON message from the server. However, the unit test will not alert you to the fact that your method is parsing the wrong type of message from the server. That would only be revealed in manual or functional testing.

Part one of this post will explore some key unit testing concepts in more detail, and parts two and three will discuss some of the tools Metova uses for unit testing its Android applications.

 

Unit vs Functional Testing: Did you build the thing right vs. did you build the right thing? [Source: @Metova] (Click to Tweet!)

 

Mocking

In unit testing, one of the core concepts is that a developer will need to be able to control the behavior of other units of code in order to isolate the code under test. Commonly, this happens by replacing the objects in question with something the test itself controls. This concept is generally called a “test double.” One popular implementation of the test double concept is a “stub” class, where the developer writes an entire class meeting the is-a requirement (usually through inheritance or by implementing the same required interface as the production code) to control a test case. This can become cumbersome, though, and sometimes requires a substantial bit of code that is itself subject to bugs.

Although there are instances where stubbing makes sense, overwhelmingly we use “mock” objects to define the behavior and verify the interaction with other units of code during a test. Mock objects are dynamic classes created at test time that are defined with expectations for how many times they will be called, what sort of arguments match, and what return behavior they will produce. Mocking is generally much cleaner because it requires only enough code or behavior to satisfy the test rather than re-implementing or overriding all of a type’s methods. Many mocking libraries include utility methods that allow an almost sentence-like description of what the mock object should do when presented with various conditions.

A mock object example from Martin Fowler:

class OrderInteractionTester extends TestCase {  private static String TALISKER = "Talisker";  private static String HIGHLAND_PARK = "Highland Park";  private Warehouse warehouse = new WarehouseImpl();  public void testOrderSendsMailIfUnfilled() {    Order order = new Order(TALISKER, 51);    Mock warehouse = mock(Warehouse.class);    Mock mailer = mock(MailService.class);    order.setMailer((MailService) mailer.proxy());    mailer.expects(once()).method("send");    warehouse.expects(once()).method("hasInventory")      .withAnyArguments()      .will(returnValue(false));    order.fill((Warehouse) warehouse.proxy());  }}

 

Dependency Injection

Dependency Injection is an architectural design pattern that goes a long way toward enabling unit testing because it structures one’s code in such a way that makes it significantly easier to introduce test doubles (e.g., mocks) to isolate the behavior of the code under test. Some key ideas that go with Dependency Injection (aka DI):

  • – Discourages hard-coded dependencies within your modules.
  • – Focuses on the separation of behavior from dependency resolution.
  • – Allows selection among multiple implementations of a given dependency implementation at runtime instead of at compile time.
  • – Particularly useful for providing stub test implementations of complex components when testing.
  • – Couples code to the interface, not to the implementation!
  • – DI is the opposite of Dependency Lookup, where classes use something like a Service – Locator pattern (e.g., Android’s Context class) to find dependencies.
  • – The Hollywood Principle: Don’t call us. We’ll call you.

 

Dependency Injection is the Hollywood Principle: Don’t call us. We’ll call you. [Source: @Metova] (Click to Tweet!)

class Foo {  //injected via setter method rather than instantiated  private Bar bar;  public void setBar(Bar bar) {    this.bar = bar;  }  public void usefulMethodWeCanTest() {    //do stuff with Bar, etc.  }}class Assembler {  public void assemble() {    Foo foo = new Foo();    /*      the Bar that Foo requires (its dependency) is provided from      outside Foo (injected)     */    foo.setBar(new Bar());    foo.usefulMethodWeCanTest();  }}

 

Next week I will review how we use unit testing here at Metova with information on JUnit testing and Mockito!

Ron Unger
Ron Unger