android

Here’s how testing can provide you with the best documentation

Android Developer at Novoda

Mobile applications are complex software systems. Automated tests can help to ensure reliability, which leads to satisfied customers - an essential ingredient for any successful product. What if you could write tests that document requirements and features in a way that everyone involved in a project could understand?

If tests are done the right way they should communicate how the system works. Even when this is achieved, reading and understanding these tests assumes knowledge of programming languages and tooling, so in the end the only group that can understand the tests is developers. Let’s look at some ways to remove this barrier to the wider team’s involvement.

A little background

In this blog post, I’ll be talking about using Espresso and Cucumber to follow behaviour-driven development (BDD) on Android. If you’re not yet familiar with BDD, I recommend reading this blog post to get an overview of what it can do: ‘Growing Android applications, guided by tests’. In short, BDD means that your software development process is driven by user stories, created with the involvement of all stakeholders.

Cucumber, in combination with Gherkin (the language that is used to write Cucumber Scenarios), enables anyone to write plain-text descriptions of the desired behaviour in any spoken language and run automated tests. Following the pattern ‘given/when/then’, the expected behaviour can be described clearly, even for non-programmers. Features like scenarios and arguments can help to reduce repetition.
Originally developed for Ruby, there are now ports for other programming languages available. Some of them use Ruby Cucumber with a bridge into the target language. Others, like the Android port, use the Gherkin parser, but implement everything else in the target language.

An example of Cucumber in action

Imagine you’re developing your own Android-based movie database app. You might want the app to display information about a movie the user has selected from an overview. You could write the following feature scenario:

Feature: Movie information  
Scenario: Show information for a selected movie  
   Given the following remote movies exist
     | movieId     | posterPath    | title         | description    |
     | 1           | deadpool.jpg  | Deadpool      | kick ass movie |
   When I select the details for movie with title Deadpool
   Then I expect to see the following movie information
     | title       | description       |
     | Deadpool    | kick ass movie    |

The result is an automated test, which also serves as feature documentation and can be read by non-programmers.

User stories could be considered as documentation, but they often don’t give enough context for people unfamiliar with the feature, like new team members or colleagues from another group. Over time these user stories can also become outdated.

Using automated tests that describe the behaviour allows you to document the current state. It’s always good to keep your test suite intact, so it’s worth updating the test and documentation if the behaviour changes.
You could write such tests together with the product owners when defining the acceptance criteria for your user stories. This way you ensure a certain level of test coverage. It can also help to gain the confidence of stakeholders as everyone is aware of the tests that you’re writing.

Sound cool? Let’s have a look at how this works in more detail.

Set up Cucumber for Android

First of all, we need to set up the correct dependencies. Besides the dependencies for Espresso, you need to add these dependencies for Cucumber to your build.gradle:

androidTestCompile 'info.cukes:cucumber-android:1.2.2'  
androidTestCompile 'info.cukes:cucumber-picocontainer:1.2.0'  

Cucumber needs its own instrumentation. Create a class in your test folder that extends from AndroidJunitRunner and initialise the CucumberInstrumentationCore:

public class CucumberInstrumentation extends AndroidJUnitRunner {

   private final CucumberInstrumentationCore instrumentationCore = new CucumberInstrumentationCore(this);

   @Override
   public void onCreate(final Bundle bundle) {
       instrumentationCore.create(bundle);
       super.onCreate(bundle);
   }

   @Override
   public void onStart() {
       waitForIdleSync();
       instrumentationCore.start();
   }
}

Next you need to tell Android to use this instrumentation. You can do this by setting the testInstrumentationRunner to your.project.package.CucumberInstrumentation. But this would mean the CucumberInstrumentation would be used for every test located in your androidTest folder, which is probably not what you want (The CucumberInstrumentation skips regular Espresso tests, so you would just run Cucumber tests with this set up).

You should consider carefully when it’s more appropriate to use Cucumber or the regular instrumentation to run your tests. You can configure this by adding a method that returns the correct instrumentation based on a project property:

defaultConfig {  
   testInstrumentationRunner getInstrumentation()
...
}

def getInstrumentation() {  
   project.hasProperty(‘cucumber’) ? 'com.tobi.movies.utils.CucumberInstrumentation' : 'android.support.test.runner.AndroidJUnitRunner'
}

Run your Cucumber tests using the command line by typing:

./gradlew connectedCheck -Pcucumber.

Cucumber executes feature definitions which are stored in *.feature files located in a folder of your choice under androidTest/assets.
To tell Cucumber where the files are located, you have to create a class in your androidTest/java folder, e.g. CucumberRunner, and specify the folder where the feature files are located using the CucumberOptions annotation. Cucumber will scan your test folder until it finds a class that uses this annotation:

@CucumberOptions(features = "features")
public class CucumberRunner {  
    // class body can be empty
}

Create Cucumber features

Now you can start writing your first feature definition, for example androidTest/assets/features/your_first_feature.feature including step definitions in Java code

The step definitions contain view assertions and view interactions. It’s worth trying to encapsulate this code so you can reuse it among different step definitions or share it with the regular Espresso tests. This avoids code duplication, helps you separate concerns and keeps your codebase clean.
One good design pattern to follow is PageObjects. A good example of a concrete implementation for Android are these testing robots introduced by Jake Wharton.

To make Cucumber aware of your steps you need to annotate your methods using @Given/@When/@Then:

@When("^I select the movie poster at position (\\d+)$")
public void I_select_a_movie_poster_at(final int position) {  
   // put here your test code
}

As you can see in the example, the step definition takes a position as argument. You can pass almost any type of argument, including domain objects. More options can be seen here.

You can even transform your arguments into other types using the transform annotation. In this example I convert a String into a Date:

@Given("^I want to transform this (\\S+) to a date")
public void transform_string_to_date(@Transform(DateFormatter.class) Date date) {  
    // you can use the date directly
}

In the step implementation:

public static class DateFormatter extends Transformer<Date> {

   @Override
   public Date transform(String s) {
       SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM/dd/yyyy", Locale.ENGLISH);
       try {
           return simpleDateFormat.parse(s);
       } catch (ParseException e) {
           // handle error
       }
   }
}

As an entry point for your test, you could create a step to launch the application:

@Given("^I start the application$")
public void I_start_app() {  
  //launch your activity test rule here
}

If you need to execute some code before or after your tests run, Cucumber comes with @Before and @After annotations.

Tips

Scenario Outlines

Cucumber offers a similar feature to parametrised JUnit tests called scenario outlines which allow you to execute the same ocenario with different parameters. Instead of defining the parameters as part of the scenario, you can add and an examples section.

Scenario Outline: Show movie descriptions for all movies  
 Given the following movies exist
   | movieId     | title         | description               |
   | 100         | Deadpool      | awesome movie             |
   | 200         | X-Men         | wolverine rocks           |
   | 300         | Star Wars     | may the force with you    |
 When I launch the movie overview screen
 And I select the movie at position <pos>
 Then I expect to see the following movie description
   | title       | description   |
   | <title>     | <description> |

 Examples:
   | pos     | title     | description                   |
   | 0       | Deadpool  | awesome movie                 |
   | 1       | X-Men     | wolverine rocks               |
   | 2       | Star Wars | may the force be with you     |

Run Cucumber Tests by Tags

Cucumber provides the option to run tests marked by annotations or by scenario name, which gives you plenty of flexibility. You can run tests related to the feature you’re currently working on, or you could annotate tests with a @smoke annotation and run these tests as part of your build pipeline on the CI.

To do this you’d need to make some changes.
First, extend your build.gradle - to pass the information to the CucumberInstrumentation:

debug {  
   buildConfigField ‘String’, ‘TEST_TAGS’, ‘“‘+getTestTags()+’”’
   buildConfigField ‘String’, ‘TEST_SCENARIO’, ‘“‘+getTestScenario()+’”’
}

def getTestTags() {  
   project.getProperties().get(‘tags’) ?: ‘’
}

def getTestScenario() {  
   project.getProperties().get(‘scenario’) ?: ‘’
}

Next, access the information in your CucumberInstrumentation and pass it to Cucumber in the expected format:

public class CucumberInstrumentation extends AndroidJUnitRunner {

   private static final String CUCUMBER_TAGS_KEY = "tags";
   private static final String CUCUMBER_SCENARIO_KEY = "name";

   @Override
   public void onCreate(final Bundle bundle) {
       String tags = BuildConfig.TEST_TAGS;
       if (!tags.isEmpty()) {
           bundle.putString(CUCUMBER_TAGS_KEY, tags.replaceAll("\\s", ""));
       }

       String scenario = BuildConfig.TEST_SCENARIO;
       if (!scenario.isEmpty()) {
           scenario = scenario.replaceAll(" ", "\\\\s");
           bundle.putString(CUCUMBER_SCENARIO_KEY, scenario);
       }
       ...
   }

 ...
}

Then run the test you want via the command line:

./gradlew connectedCheck -Pcucumber -Ptags="@smoke"
./gradlew connectedCheck -Pcucumber -Pscenario="Your scenario name comes here"

Android Studio

Last but not least, there is support for Cucumber and Gherkin in Android Studio. You can install the Cucumber for Java and Gherkin plugins which give you syntax highlighting, autocompletion, navigation between steps and step definitions and much more:

Conclusion

Cucumber on Android works pretty well and results in maintainable and readable tests and up-to-date feature documentation. The setup is straightforward and you can smoothly migrate existing tests or start with new ones in addition to your test suite.

From here it’s up to you how you use it. Try to integrate Cucumber into your software creation process by writing scenarios together with your team when grooming a new feature. Or just use it to document complex business rules for your colleagues. There are many ways to use this tool to fit your needs.

That’s it! Have fun with Cucumber on Android.

If you’re looking for a running example, check out my repo.

Enjoyed this article? There's more...

We send out a small, valuable newsletter with the best stories, app design & development resources every month.

No spam, no giving your data away, unsubscribe anytime.

About Novoda

We plan, design, and develop the world’s most desirable software products. Our team’s expertise helps brands like Sony, Motorola, Tesco, Channel4, BBC, and News Corp build fully customized Android devices or simply make their mobile experiences the best on the market. Since 2008, our full in-house teams work from London, Liverpool, Berlin, Barcelona, and NYC.

Let’s get in contact

Stay in the loop!

Hear about our events, blog posts and inspiration every month

Subscribe to our newsletter