Awesome

unit testing Android applications

by Wojtek Erbetowski


The road


Android test support

Unit tests - understanding the need

Long way to JVM

 Shadowing Android with Robolectric

Raising the bar

Author

Community
Warsaw Java User Group
Mobile Warsaw
Warsjawa
Git Kata
Mobile Central Europe

Programming
Groovy, Scala, Python, Java

Team


Tech Lead @ Polidea

Android test framework



Getting started

public class MainActivityFunctionalTest extends
    ActivityInstrumentationTestCase2<MainActivity> {

  private MainActivity activity;

  public MainActivityFunctionalTest() {
    super(MainActivity.class);
  }
  @Override
  protected void setUp() throws Exception {
    super.setUp();
    setActivityInitialTouchMode(false);
    activity = getActivity();
  }

  public void testStartSecondActivity() throws Exception {
    
    
    
    // add monitor to check for the second activity
    ActivityMonitor monitor =
        getInstrumentation().
          addMonitor(SecondActivity.class.getName(), null, false);

    // find button and click it
    Button view = (Button) activity.findViewById(R.id.button1);
    
    // TouchUtils handles the sync with the main thread internally
    TouchUtils.clickView(this, view);

    // to click on a click, e.g., in a listview
    // listView.getChildAt(0);

    // wait 2 seconds for the start of the activity
    SecondActivity startedActivity = (SecondActivity) monitor
        .waitForActivityWithTimeout(2000);
    assertNotNull(startedActivity);

    // search for the textView
    TextView textView = (TextView) startedActivity.findViewById(R.id.resultText);
    
    // check that the TextView is on the screen
    ViewAsserts.assertOnScreen(startedActivity.getWindow().getDecorView(),
        textView);
    // validate the text on the TextView
    assertEquals("Text incorrect", "Started", textView.getText().toString());
    
    // press back and click again
    this.sendKeys(KeyEvent.KEYCODE_BACK);
    
    TouchUtils.clickView(this, view);
  }
} 

Robotium

public class EditorTest extends
        ActivityInstrumentationTestCase2<EditorActivity> {

  private Solo solo;

  public EditorTest() {
        super(EditorActivity.class);
  }

  public void setUp() throws Exception {
    solo = new Solo(getInstrumentation(), getActivity());
  }
  
  public void testPreferenceIsSaved() throws Exception {
  
    solo.sendKey(Solo.MENU);
    solo.clickOnText("More");
    solo.clickOnText("Preferences");
    solo.clickOnText("Edit File Extensions");
    Assert.assertTrue(solo.searchText("rtf"));
                
    solo.clickOnText("txt");
    solo.clearEditText(2);
    solo.enterText(2, "robotium");
    solo.clickOnButton("Save");
    solo.goBack();
    solo.clickOnText("Edit File Extensions");
    Assert.assertTrue(solo.searchText("application/robotium"));
                
  }

   @Override
   public void tearDown() throws Exception {
     solo.finishOpenedActivities();
  }
}

Project structure


MyProject/
  AndroidManifest.xml
    res/
      ... (resources for main application)
    src/
      ... (source code for main application) ...
    tests/
      AndroidManifest.xml
      res/
        ... (resources for tests)
      src/
        ... (source code for tests)

NBS project structure


MyApplicationProject
└── MyApplication
    └── src
        ├── instrumentTest
        │   └── java
        └── main
            ├── java
            └── res

Running the tests



  • compile
  • dex
  • package
  • install
  • run app
  • x2

:-(



  • Continuous Integration
  • Parallelization
  • Time consuming
  • DalvikVM only (Java, no ASM)

Unit Testing Applications




  • Check if the code works!
  • Fast feedback loop
  • Point the exact broken piece
  • Stable and trusted
  • Readable, short, expressive

The need





Fast and stable

How fast needed?



A better way



Why not JVM?


android.jar

|
\/

Stub!

Rewrite android.jar?

Classloaders FTW!

Meet Robolectric



Sample test


 // Test class for MyActivity
@RunWith(RobolectricTestRunner.class)
public class MyActivityTest {

  @Test
  public void clickingButton_shouldChangeResultsViewText() throws Exception {
    Activity activity = Robolectric.buildActivity(MyActivity.class).create().get();

    Button pressMeButton = (Button) activity.findViewById(R.id.press_me_button);
    TextView results = (TextView) activity.findViewById(R.id.results_text_view);

    pressMeButton.performClick();
    String resultsText = results.getText().toString();
    assertThat(resultsText, equalTo("Testing Android Rocks!"));
  }
} 

Shadows


@Implements(AlphaAnimation.class)
public class ShadowAlphaAnimation extends ShadowAnimation {
  private float fromAlpha;
  private float toAlpha;

  public void __constructor__(float fromAlpha, float toAlpha) {
    this.fromAlpha = fromAlpha;
    this.toAlpha = toAlpha;
  }

  public float getFromAlpha() {
    return fromAlpha;
  }

  public float getToAlpha() {
    return toAlpha;
  }
}

Not bad.

Awesome


  • mocking static methods
  • fast execution
  • running on JVM

Spock framework

Enterprise ready!

Raising the bar

JUnit

@Test
void schouldAggregateSevesUser() {
    // given
    User user = new User();

    // when
    aggregate.store(user);

    // then
    assertEquals(
        user,
        aggregate.findOnly()
    );
}

Spock

def 'aggregate should save user'() {
    given:
      def user = new User()

    when:
      aggregate.store user

    then:
      aggregate.findOnly() == user
}

Test parameters

JUnit

// ...

// then,
assertEquals(sum(3, 5), 8);
assertEquals(sum(1, 5), 6);
assertEquals(sum(4, 5), 9);
assertEquals(sum(5, 3), 8);
    

Spock

// ...

expect:
    sum(a, b) == c

where:
    a | b || c  
    3 | 5 || 8
    1 | 5 || 6
    4 | 5 || 9
    5 | 3 || 8
  

Mocks

JUnit

// given
User userMock = mock(User.class);
when(userMock.getEmail())
    .thenReturn("me@email.com")
    .thenReturn(null);

// ...

// then
verify(userMock, times(2)).getEmail()

Spock

given:
  def userMock = Mock(User)
  userMock.getEmail() >> ['me@email.com', null]

// ...

then:
  2 * userMock.getEmail()

Exception type

JUnit

@Test(expect=RuntimeException.class)
public void myTest() {

  thisThrowsSomething();
}

Spock

when:
thisThrowsSomething()

then:
thrown(RuntimeException)

Exception details

JUnit

@Test
public void myTest() {

  try {
    thisThrowsSomething();
    fail();

  } catch(RuntimeException e) {
    assertContains(
      e.getMessage(), 'No such user')
  }
}

Spock

when:
  thisThrowsSomething()

then:
  def e = thrown(RuntimeException)
  e.message =~ 'No such user'

Groovyness

then:
    userStorage.getAllUsers().find{it.id == id}?.name == "Simon"
Condition not satisfied:

userRepository.findAll().find{it.name == 'Simon'}?.age == 10
|              |         |                          |   |
|              |         null                       |   false
|              [A$User(Adam, 12)]                  null
A$UserRepository@22d3d11f
<Click to see difference>

Hello RoboSpock


Robolectric

classloader, shadows, DB support , res

    Spock

    runner, extension points, everything else


    Setup


    .
    ├── app
    │   ├── build.gradle
    │   ├── debug.keystore
    │   ├── local.properties
    │   └── src
    │       ├── instrumentTest
    │       ├── main
    │       └── release
    ├── robospock
    │   ├── build.gradle
    │   └── src
    │       └── test
    └── settings.gradle
    

    Setup

    buildscript {
      repositories {
        mavenCentral()
      }
      
      dependencies {
        classpath 'com.android.tools.build:gradle:0.5.+'
        classpath 'com.novoda:gradle-android-test-plugin:0.9.1-SNAPSHOT'
      }
    }
    
    apply plugin: 'groovy'
    apply plugin: 'android-test'
    
    repositories {
      mavenCentral()
    }
    
    dependencies {
      testCompile 'pl.polidea:RoboSpock:0.4'
      testCompile rootProject
    }

    Setup

    buildscript {
      repositories {
        mavenCentral()
      }
      dependencies {
        classpath 'com.android.tools.build:gradle:0.5.+'
      }
    }
    apply plugin: 'android'
    
    dependencies {
      compile 'com.android.support:support-v4:13.0.0'
      debugCompile 'com.android.support:support-v13:13.0.0'
    
      compile 'com.google.android.gms:play-services:3.1.36'
    }
    
    android {
      compileSdkVersion 15
      buildToolsVersion "17.0"
    
      testBuildType "debug"
    
      signingConfigs {
        myConfig {
          storeFile file("debug.keystore")
          storePassword "android"
          keyAlias "androiddebugkey"
          keyPassword "android"
        }
      }
    
      defaultConfig {
        versionCode 12
        versionName "2.0"
        minSdkVersion 16
        targetSdkVersion 16
      }
    
      buildTypes {
        debug {
          packageNameSuffix ".debug"
          signingConfig signingConfigs.myConfig
        }
      }
    }
    

    Are we there yet?


    1. Lower time consumption
    2. Local JVM
    3. Mocking dependencies
    4. Spock & Groovy
    5. Simple setup
    6. Better classloaders creation time

    Examples

    ORMLite


    def "should throw SQL Constraint exception on existing primary key"() {
      given:
        def dao = databaseHelper.getDao(DatabaseObject)
    
      and: 'stored object'
        def dbObject = new DatabaseObject("test", 4, 1)
        dao.create(dbObject)
    
      when: 'duplication'
        dao.create(dbObject)
    
      then:
        def exception = thrown(RuntimeException)
        exception.message =~ "SQLITE_CONSTRAINT"
        exception.cause.class == SQLException
    }

    RoboGuice

    class TaskActivityTestGroovy extends RoboSpecification {
    
      @Inject WebInterface webInterface
    
      def setup() {
        inject {
          install(TestTaskExecutorModule)
          bind(WebInterface).toInstance(Mock(WebInterface))
        }
      }
    
      def "should load text from async task"() {
        given:
          def taskActivity = new TaskActivity()      webInterface.getMainPageText() >> "WebText"
    
        when:
          taskActivity.onCreate(null)
    
        then:
          taskActivity.asyncText.text == "WebText"
      }
    }

    Sources

    Follow up


    Home/blog: erbetowski.pl
    Twitter: erbetowski
    GitHub:  wojtekerbetowski

    Awesome unit testing Android apps (Java2Days)

    By Wojtek Erbetowski

    Awesome unit testing Android apps (Java2Days)

    • 2,943