Your SlideShare is downloading. ×

Android TDD & CI

2,698

Published on

How to practice TDD (and Acceptance TDD) when developing Android applications. …

How to practice TDD (and Acceptance TDD) when developing Android applications.

Covers usage of Robotium, Roboguice, Hamcrest, JMock and Robolectric.

Published in: Technology, Business
0 Comments
9 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
2,698
On Slideshare
0
From Embeds
0
Number of Embeds
5
Actions
Shares
0
Downloads
66
Comments
0
Likes
9
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • Transcript

    • 1. Android TDD &Marcin Gryszko @mgryszko
    • 2. Newgig
    • 3. User stories written
    • 4. Kickoff?
    • 5. walking skeleton“Implementation of thethinnest possible slice ofreal functionality that wecan automatically build,deploy, and test end-to-end”original idea by Alistair Cockburn
    • 6. Iteration “zero” TDD cycle
    • 7. 01. Understand the problem2. Think about architecture3. Automate
    • 8. Android testing framework
    • 9. Architecture
    • 10. Automate
    • 11. + Android plugin
    • 12. Failing acceptance testpublic class TimelineTest extends ActivityInstrumentationTestCase2<TimelineActivity> { public void test_chirps_are_displayed_timely_ordered() throws Exception { timelineDriver.waitUntilTimelineLoaded(mgryszkosChirps().size()); assertTimelineEqualTo(timelineDriver.getDisplayedTimeline(), mgryszkosChirps()); }}
    • 13. Failing acceptance testpublic class TimelineTest extends ActivityInstrumentationTestCase2<TimelineActivity> { public void test_chirps_are_displayed_timely_ordered() throws Exception { timelineDriver.waitUntilTimelineLoaded(mgryszkosChirps().size()); assertTimelineEqualTo(timelineDriver.getDisplayedTimeline(), mgryszkosChirps()); }}
    • 14. Page objectpublic class TimelineDriver { private Solo solo; public TimelineDriver(Solo solo) { this.solo = solo; } public void waitUntilTimelineLoaded(int timelineSize) { solo.waitForView(TableRow.class, timelineSize, 5000); } public List<Chirp> getDisplayedTimeline() { ListView timelineView = (ListView) solo.getView(R.id.timelineView); List<Chirp> chirps = new ArrayList<Chirp>(); for (int i = 0; i < timelineView.getCount(); i++) { chirps.add((Chirp) timelineView.getItemAtPosition(i)); } return chirps; }}
    • 15. Failing acceptance testpublic class TimelineTest extends ActivityInstrumentationTestCase2<TimelineActivity> { private Solo solo; private TimelineDriver timelineDriver; protected void setUp() throws Exception { super.setUp(); solo = new Solo(getInstrumentation(), getActivity()); timelineDriver = new TimelineDriver(solo); } public void test_chirps_are_displayed_timely_ordered() throws Exception { timelineDriver.waitUntilTimelineLoaded(mgryszkosChirps().size()); assertTimelineEqualTo(timelineDriver.getDisplayedTimeline(), mgryszkosChirps()); } private void assertTimelineEqualTo(List<Chirp> actual, List<Chirp> expected) { assertEquals(expected, actual); }}
    • 16. Passing acceptance testpublic class TimelineActivity extends Activity { public void onCreate(Bundle savedInstanceState) { // super.onCreate & setContentView omitted List<Chirp> chirps = loadTimeline(); displayTimeline(chirps); } private List<Chirp> loadTimeline() { return executeJsonRequest("http://...", "mgryszko"); } private List<Chirp> executeJsonRequest(String uri, Object... requestParams) { RestTemplate restTemplate = new RestTemplate(); return asList(restTemplate.getForObject(uri, Chirp[].class, requestParams)); } private void displayTimeline(List<Chirp> chirps) { TimelineAdapter adapter = new TimelineAdapter(); adapter.setActivity(this); adapter.setChirps(chirps); ListView timelineView = (ListView) findViewById(R.id.timelineView); timelineView.setAdapter(adapter); }}
    • 17. Tracer bullet hit
    • 18. Let’s ref ac tor
    • 19. Hamcrestpublic class TimelineTest extends ActivityInstrumentationTestCase2<TimelineActivity> { private void assertTimelineEqualTo(List<Chirp> actual, List<Chirp> expected) { assertEquals(expected, actual); }}
    • 20. Hamcrestpublic class TimelineTest extends ActivityInstrumentationTestCase2<TimelineActivity> { private void assertTimelineEqualTo(List<Chirp> actual, List<Chirp> expected) { assertThat(actual.size(), equalTo(expected.size())); for (Chirp chirp : expected) { assertThat(format("Chirp %s not in the timeline", chirp), actual, hasItem(chirp)); } }}
    • 21. Roboguicepublic class TimelineActivity extends Activity { private void displayTimeline(List<Chirp> chirps) { TimelineAdapter adapter = new TimelineAdapter(); adapter.setActivity(this); adapter.setChirps(chirps); ListView timelineView = (ListView) findViewById(R.id.timelineView); timelineView.setAdapter(adapter); }}
    • 22. Roboguicepublic class TimelineActivity extends RoboActivity { @InjectView(R.id.timelineView) private ListView timelineView; private void displayTimeline(List<Chirp> chirps) { TimelineAdapter adapter = getInjector() .getInstance(TimelineAdapter.class) .withChirps(chirps)); timelineView.setAdapter(adapter); }}
    • 23. Falling integration testpublic class ChirpJsonRepositoryTest extends TestCase { private ChirpRepository repository = new ChirpJsonRepository(); public void test_returns_the_timeline_of_a_chirper() { assertTimelineEqualTo(repository.findTimelineOf("mgryszko"), mgryszkosChirps()); }}
    • 24. Passing integration testpublic class ChirpJsonRepository implements ChirpRepository { public List<Chirp> findTimelineOf(String chirper) { return executeJsonRequest("http://...", chirper); } private List<Chirp> executeJsonRequest(String uri, Object... requestParams) { RestTemplate restTemplate = new RestTemplate(); return asList(restTemplate.getForObject(uri, Chirp[].class, requestParams)); }}
    • 25. Passing integration testpublic class TimelineActivity extends Activity { private List<Chirp> loadTimeline() { return executeJsonRequest("http://...", "mgryszko"); } private List<Chirp> executeJsonRequest(String uri, Object... requestParams) { RestTemplate restTemplate = new RestTemplate(); return asList(restTemplate.getForObject(uri, Chirp[].class, requestParams)); }}
    • 26. Passing integration testpublic class TimelineActivity extends RoboActivity { @Inject private ChirpRepository chirpRepository; private List<Chirp> loadTimeline() { return chirpRepository.findTimelineOf("mgryszko"); }}
    • 27. Optimization
    • 28. Async loadingpublic class TimelineActivity extends RoboActivity implements TimelineLoadListener { @Inject private AsyncTimelineLoader timelineLoader; private void loadTimeline() { timelineLoader.loadChirperTimeline("mgryszko", this); } public void timelineLoaded(List<Chirp> timeline) { displayTimeline(timeline); }}public interface TimelineLoadListener { void timelineLoading(); void timelineLoaded(List<Chirp> timeline);}
    • 29. Async loadingpublic class TimelineLoadTask extends RoboAsyncTask<List<Chirp>> implements AsyncTimelineLoader { public void loadChirperTimeline(String chirper, TimelineLoadListener loadListener) { this.execute(); } @Override protected void onPreExecute() throws Exception { loadListener.timelineLoading(); } public List<Chirp> call() throws Exception { return repository.findTimelineOf(chirper); } @Override protected void onSuccess(List<Chirp> chirps) throws Exception { loadListener.timelineLoaded(chirps); }}
    • 30. First unit testpublic class TimelineLoadTaskTest extends RoboUnitTestCase<Application> { private Mockery context = new Mockery(); private ChirpRepository repository = context.mock(ChirpRepository.class); private TimelineLoadListener loadListener = context.mock(TimelineLoadListener.class); public void test_loads_timeline_successfully_and_notifies_the_listener() { TimelineLoadTask task = createTimelineLoadTask(); context.checking(new Expectations() {{ List<Chirp> timeline = asList(A_CHIRP); oneOf(loadListener).timelineLoading(); oneOf(repository).findTimelineOf(CHIRPER); will(returnValue(timeline)); oneOf(loadListener).timelineLoaded(with(timeline)); }}); executeInFakeUIThread(task); }}
    • 31. First unit test private CountDownLatch taskDone = new CountDownLatch(1); private TimelineLoadTask createTimelineLoadTask() { return new TimelineLoadTask(repository) { @Override protected void onFinally() { taskDone.countDown(); } }; } private void executeInFakeUIThread(final TimelineLoadTask task) { new RoboLooperThread() { public void run() { task.loadChirperTimeline(CHIRPER, loadListener); } }.start(); taskDone.await(); }}
    • 32. Really unit?
    • 33. straight JUnitIntelliJjava.lang.RuntimeException: Stub!at junit.runner.BaseTestRunner.(BaseTestRunner.java:5)MavenTests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.024sec
    • 34. TestNGIntelliJ===============================================Custom suiteTotal tests run: 1, Failures: 0, Skips: 0===============================================MavenRunning TestSuiteTests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.298sec
    • 35. Robolectric@RunWith(InjectingTestRunner.class)public class TimelineUnitTest { @Inject private TimelineActivity activity; @Inject private AsyncTimelineLoaderStub timelineLoader; @Test public void triggers_timeline_loading() { activity.onCreate(NO_SAVED_INSTANCE_STATE); assertThat(timelineLoader.isTimelineLoaded(), is(true)); } @Test public void progress_dialog_is_displayed_when_timeline_is_loaded() { activity.timelineLoading(); assertThat(Robolectric.shadowOf(activity).getLastShownDialogId(), is(TimelineActivity.PROGRESS_DIALOG_ID)); }}
    • 36. Wrap-up
    • 37. Thanks!
    • 38. https://github.com/mgryszko/android- tdd-ci
    • 39. you ? jobs Marcin @osoco.es
    • 40. Image sources:http://www.flickr.com/photos/sarah_mccans/219287847/http://www.flickr.com/photos/f-oxymoron/5005146417/http://www.bluebison.net/content/2007/a-skeleton-walking-his-pets/http://www.flickr.com/photos/familymwr/5009855774/http://www.flickr.com/photos/zbraineater/2213219097/http://www.flickr.com/photos/stusev/3296738594/http://www.flickr.com/photos/hadamsky/293310259/http://www.flickr.com/photos/qthomasbower/3392847831/http://www.flickr.com/photos/mikecogh/5113779851/

    ×