Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TDD for Android

Alex Zhukovich
September 14, 2016

TDD for Android

Overview of base principle Test-Driven Development. How to use TDD for Android project. Tips for creating well tested Android projects.

Project:
https://github.com/AlexZhukovich/TemperatureConverterTDD

Blog:
http://alexzh.com

Alex Zhukovich

September 14, 2016
Tweet

More Decks by Alex Zhukovich

Other Decks in Technology

Transcript

  1. TDD for Android

    View Slide

  2. Why do we test?
    •Testing ensure the Quality of the product;
    •Tests help improve design code;
    •Tests check that code works;

    View Slide

  3. Observations
    • Huge activities / fragments
    • Hard to add a new feature
    • Hard to test
    • Many apps tested in manual
    way
    • Fragmentation
    • Slow Android tests
    • Required device
    • Robolectric
    • Espresso
    • etc

    View Slide

  4. Bad vs Good tests
    @Test
    public void test1() {
    mPresenter.attachView(null);
    mPresenter.convertTemperature();
    mPresenter.detachView();
    verify(mView, never()).getInputValue();
    verify(mView, never()).getFromTemperatureUnit();
    verify(mView, never()).getToTemperatureUnit();
    verify(mView, never()).setOutputValue(anyDouble());
    when(mTemperatureFactory.getTemperatureConverter())
    .thenReturn(mConverter);
    doNothing().when(mConverter).convertData(any(InputData.class));
    mPresenter.attachView(mView);
    mPresenter.convertTemperature();
    mPresenter.detachView();
    verify(mEventBus).register(any());
    verify(mView).displayProgress();
    verify(mView, times(1)).getInputValue();
    verify(mView, times(1)).getFromTemperatureUnit();
    verify(mView, times(1)).getToTemperatureUnit();
    verify(mEventBus).unregister(any());
    }
    @Test
    public void shouldVerifyConvertDataSuccessful () {
    mPresenter.attachView(mView);
    mPresenter.convertTemperature();
    mPresenter.detachView();
    verify(mEventBus).register(any());
    ...
    }
    @Test
    public void shouldVerifyConvertDataWithNullView() {
    mPresenter.attachView(null);
    mPresenter.convertTemperature();
    mPresenter.detachView();
    verify(mView, never()).getInputValue();
    ...
    }
    Don’t Do

    View Slide

  5. Bad vs Good UI tests
    @Test
    public void shouldVerifyGravity() {
    View view = ...;
    assertEquals(Gravity.START,
    view.getForegroundGravity());
    ...
    }
    @Test
    public void shouldVerifyOrderCoffee() {
    onView(withId(R.id.name_edit))
    .perform(typeText("User"),
    closeSoftKeyboard());
    onView(withId(R.id.pay))
    .perform(click());
    onView(withId(R.id.detail_text))
    .check(matches(isDisplayed()));
    }
    Don’t Do

    View Slide

  6. Value

    View Slide

  7. What is Test Driven Development (TDD)
    Green
    Refactor
    Red

    View Slide

  8. Code separations
    Android
    App
    Core
    Acceptance
    tests
    Unit
    tests

    View Slide

  9. Model-View-Presenter
    View Presenter Model

    View Slide

  10. Application overview
    Web
    service
    Local

    View Slide

  11. Application overview
    Presenter
    Use case
    Repository
    Web
    service
    Local

    View Slide

  12. Recommendation for setting up the device /
    emulator
    One small recommendation for avoiding flakiness is turning off
    animation on real or virtual devices.
    • Window animation scale
    • Transition animation scale
    • Animator duration scale
    • Settings / Developer Options/

    View Slide

  13. Hamcrest library

    View Slide

  14. Hamcrest + Espresso
    public static Matcher withToolbarTitle(final Matcher textMatcher) {
    return new BoundedMatcher(Toolbar.class) {
    @Override public boolean matchesSafely(Toolbar toolbar) {
    return textMatcher.matches(toolbar.getTitle());
    }
    @Override public void describeTo(Description description) {
    description.appendText("with toolbar title: ")
    textMatcher.describeTo(description);
    }
    };
    }
    onView(withId(R.id.toolbar))
    .check(matches(withToolbarTitle( is (TITLE))));
    startsWith
    endsWith
    ...

    View Slide

  15. UI test
    @RunWith(AndroidJUnit4.class)
    public class TemperatureConverterActivityTest {
    @Test
    public void shouldVerifyConvertingFromCelsiusToFahrenheit() {
    onView(withId(R.id.inputView))
    .perform(typeText(String.valueOf(CELSIUS_VALUE)),
    closeSoftKeyboard());
    setTemperatureUnit(R.id.inputTemperatureSpinner, CELSIUS_STR);
    setTemperatureUnit(R.id.outputTemperatureSpinner, FAHRENHEIT_STR);
    onView(withId(R.id.convertButton))
    .perform(click());
    onView(withId(R.id.outputView))
    .check(matches(isDisplayed()));
    onView(withId(R.id.outputView))
    .check(matches(withText(getOutputString(FAHRENHEIT_VALUE))));
    }
    }

    View Slide

  16. Presenter test
    @RunWith(MockitoJUnitRunner.class)
    public class TemperatureConverterPresenterTest {
    @Mock TemperatureConverterView mView;

    private TemperatureConverterPresenter mPresenter;
    @Test
    public void shouldVerifyConvertDataSuccessfulWithCorrectView() {
    mPresenter.attachView(mView);
    mPresenter.convertTemperature();
    mPresenter.detachView();
    verify(mEventBus).register(any());
    ...
    }
    }

    View Slide

  17. Asynchronous calls using Mockito (Retrofit2)
    @RunWith(MockitoJUnitRunner.class)
    public class OnlineConverterTemperatureTest {
    private OnlineConverterTemperature mConverter;
    @Mock EventBus mEventBus;
    @Mock TemperatureConverterApiService mApiService;
    @Mock Call mCall;
    @Mock ResponseBody mResponseBody;
    @Captor ArgumentCaptor> argumentCapture;
    @Test
    public void shouldGetConvertedResult() {
    when(mApiService.getConvertedData(anyString(), anyString(), anyString()))
    .thenReturn(mCall);
    Response response = Response.success(mConvertedResult);
    mConverter.convertData(mInputData);
    verify(mCall).enqueue(argumentCapture.capture());
    argumentCapture.getValue().onResponse(null, response);
    verify(mEventBus).post(new TemperatureConvertedSuccessful(mConvertedResult));
    }
    }

    View Slide

  18. Testing system stuff
    • Notifications;
    • Press hardware buttons;
    • Interacting with any applications.
    UI Automator

    View Slide

  19. Continuous Integration (CIRCLE CI)
    circle.yml

    View Slide

  20. Testing tips

    View Slide

  21. Focus on App
    behaviour

    View Slide

  22. Use available
    stuff

    View Slide

  23. Use hamcrest
    library

    View Slide

  24. Do not copy
    your code
    between tests

    View Slide

  25. Launch directly
    desire screen

    View Slide

  26. Many small
    tests are
    better that big
    ones

    View Slide

  27. Test your
    code, not using
    libraries

    View Slide

  28. Test-Driven
    Development is
    NOT a silver
    bullet

    View Slide

  29. Thank you
    1. Getting Started with Testing (Android)
    https://developer.android.com/training/testing/start/index.html
    2. Espresso
    https://google.github.io/android-testing-support-library/docs/espresso/
    3. Ui Automator
    https://developer.android.com/training/testing/ui-testing/uiautomator-testing.html
    4. Robolectric
    http://robolectric.org/
    5. Project
    https://github.com/AlexZhukovich/TemperatureConverterTDD
    6. Blog
    http://alexzh.com/
    @Alex_Zhukovich

    View Slide