SlideShare a Scribd company logo
1 of 41
Android
 TDD &
Marcin Gryszko
 @mgryszko
New
gig
User stories
  written
Kickoff?
walking
     skeleton
“Implementation of the
thinnest possible slice of
real functionality that we
can automatically build,
deploy, and test end-to-
end”
original idea by Alistair Cockburn
Iteration “zero”
  TDD cycle
0
1. Understand the problem
2. Think about architecture
3. Automate
Android testing framework
Architecture
Automate
+ Android
  plugin
Failing acceptance test
public class TimelineTest extends ActivityInstrumentationTestCase2<TimelineActivity> {

    public void test_chirps_are_displayed_timely_ordered() throws Exception {
        timelineDriver.waitUntilTimelineLoaded(mgryszkosChirps().size());
        assertTimelineEqualTo(timelineDriver.getDisplayedTimeline(), mgryszkosChirps());
    }
}
Failing acceptance test
public class TimelineTest extends ActivityInstrumentationTestCase2<TimelineActivity> {

    public void test_chirps_are_displayed_timely_ordered() throws Exception {
        timelineDriver.waitUntilTimelineLoaded(mgryszkosChirps().size());
        assertTimelineEqualTo(timelineDriver.getDisplayedTimeline(), mgryszkosChirps());
    }
}
Page object
public 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;
    }
}
Failing acceptance test
public 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);
    }
}
Passing acceptance test
public 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);
    }
}
Tracer bullet
     hit
Let’s
 ref
 ac
 tor
Hamcrest
public class TimelineTest extends ActivityInstrumentationTestCase2<TimelineActivity> {

    private void assertTimelineEqualTo(List<Chirp> actual, List<Chirp> expected) {
        assertEquals(expected, actual);
    }
}
Hamcrest
public 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));
        }
    }
}
Roboguice
public 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);
    }
}
Roboguice
public 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);
    }
}
Falling integration test
public class ChirpJsonRepositoryTest extends TestCase {
    private ChirpRepository repository = new ChirpJsonRepository();

    public void test_returns_the_timeline_of_a_chirper() {
        assertTimelineEqualTo(repository.findTimelineOf("mgryszko"), mgryszkosChirps());
    }
}
Passing integration test
public 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));
    }
}
Passing integration test
public 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));
    }
}
Passing integration test
public class TimelineActivity extends RoboActivity {
    @Inject
    private ChirpRepository chirpRepository;

    private List<Chirp> loadTimeline() {
        return chirpRepository.findTimelineOf("mgryszko");
    }
}
Optimization
Async loading
public 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);
}
Async loading
public 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);
    }
}
First unit test
public 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);
    }
}
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();
    }
}
Really unit?
straight JUnit
IntelliJ
java.lang.RuntimeException: Stub!
at junit.runner.BaseTestRunner.(BaseTestRunner.java:5)




Maven
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.024
sec
TestNG
IntelliJ
===============================================
Custom suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================



Maven
Running TestSuite
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.298
sec
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));
    }
}
Wrap-up
Thanks!
https://github.com/mgryszko/android-
                 tdd-ci
you
              ?
  jobs     Marcin
   @
osoco.es
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/

More Related Content

What's hot

Automated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.xAutomated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.x
Tatsuya Maki
 
Software Testing - Invited Lecture at UNSW Sydney
Software Testing - Invited Lecture at UNSW SydneySoftware Testing - Invited Lecture at UNSW Sydney
Software Testing - Invited Lecture at UNSW Sydney
julien.ponge
 
Рахманов Александр "Что полезного в разборе дампов для .NET-разработчиков?"
Рахманов Александр "Что полезного в разборе дампов для .NET-разработчиков?"Рахманов Александр "Что полезного в разборе дампов для .NET-разработчиков?"
Рахманов Александр "Что полезного в разборе дампов для .NET-разработчиков?"
Yulia Tsisyk
 

What's hot (20)

Exploring OpenFaaS autoscalability on Kubernetes with the Chaos Toolkit
Exploring OpenFaaS autoscalability on Kubernetes with the Chaos ToolkitExploring OpenFaaS autoscalability on Kubernetes with the Chaos Toolkit
Exploring OpenFaaS autoscalability on Kubernetes with the Chaos Toolkit
 
Testing Java Code Effectively
Testing Java Code EffectivelyTesting Java Code Effectively
Testing Java Code Effectively
 
Code Samples
Code SamplesCode Samples
Code Samples
 
Rxjs swetugg
Rxjs swetuggRxjs swetugg
Rxjs swetugg
 
Making the most of your gradle build - Gr8Conf 2017
Making the most of your gradle build - Gr8Conf 2017Making the most of your gradle build - Gr8Conf 2017
Making the most of your gradle build - Gr8Conf 2017
 
Automated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.xAutomated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.x
 
Making the most of your gradle build - Greach 2017
Making the most of your gradle build - Greach 2017Making the most of your gradle build - Greach 2017
Making the most of your gradle build - Greach 2017
 
GeeCON 2017 - TestContainers. Integration testing without the hassle
GeeCON 2017 - TestContainers. Integration testing without the hassleGeeCON 2017 - TestContainers. Integration testing without the hassle
GeeCON 2017 - TestContainers. Integration testing without the hassle
 
JPoint 2016 - Валеев Тагир - Странности Stream API
JPoint 2016 - Валеев Тагир - Странности Stream APIJPoint 2016 - Валеев Тагир - Странности Stream API
JPoint 2016 - Валеев Тагир - Странности Stream API
 
JEEConf 2017 - Having fun with Javassist
JEEConf 2017 - Having fun with JavassistJEEConf 2017 - Having fun with Javassist
JEEConf 2017 - Having fun with Javassist
 
Cassandra Summit EU 2014 - Testing Cassandra Applications
Cassandra Summit EU 2014 - Testing Cassandra ApplicationsCassandra Summit EU 2014 - Testing Cassandra Applications
Cassandra Summit EU 2014 - Testing Cassandra Applications
 
Adam Sitnik "State of the .NET Performance"
Adam Sitnik "State of the .NET Performance"Adam Sitnik "State of the .NET Performance"
Adam Sitnik "State of the .NET Performance"
 
RxJS Operators - Real World Use Cases - AngularMix
RxJS Operators - Real World Use Cases - AngularMixRxJS Operators - Real World Use Cases - AngularMix
RxJS Operators - Real World Use Cases - AngularMix
 
Software Testing - Invited Lecture at UNSW Sydney
Software Testing - Invited Lecture at UNSW SydneySoftware Testing - Invited Lecture at UNSW Sydney
Software Testing - Invited Lecture at UNSW Sydney
 
Easy Button
Easy ButtonEasy Button
Easy Button
 
V8
V8V8
V8
 
Testing time and concurrency Rx
Testing time and concurrency RxTesting time and concurrency Rx
Testing time and concurrency Rx
 
Testing with Kotlin
Testing with KotlinTesting with Kotlin
Testing with Kotlin
 
Akka.NET streams and reactive streams
Akka.NET streams and reactive streamsAkka.NET streams and reactive streams
Akka.NET streams and reactive streams
 
Рахманов Александр "Что полезного в разборе дампов для .NET-разработчиков?"
Рахманов Александр "Что полезного в разборе дампов для .NET-разработчиков?"Рахманов Александр "Что полезного в разборе дампов для .NET-разработчиков?"
Рахманов Александр "Что полезного в разборе дампов для .NET-разработчиков?"
 

Similar to Android TDD & CI

Fault tolerance made easy
Fault tolerance made easyFault tolerance made easy
Fault tolerance made easy
Uwe Friedrichsen
 
33rd Degree 2013, Bad Tests, Good Tests
33rd Degree 2013, Bad Tests, Good Tests33rd Degree 2013, Bad Tests, Good Tests
33rd Degree 2013, Bad Tests, Good Tests
Tomek Kaczanowski
 
Sustaining Test-Driven Development
Sustaining Test-Driven DevelopmentSustaining Test-Driven Development
Sustaining Test-Driven Development
AgileOnTheBeach
 
Trisha gee concurrentprogrammingusingthedisruptor
Trisha gee concurrentprogrammingusingthedisruptorTrisha gee concurrentprogrammingusingthedisruptor
Trisha gee concurrentprogrammingusingthedisruptor
EthanTu
 

Similar to Android TDD & CI (20)

Anti patterns
Anti patternsAnti patterns
Anti patterns
 
RxJava и Android. Плюсы, минусы, подводные камни
RxJava и Android. Плюсы, минусы, подводные камниRxJava и Android. Плюсы, минусы, подводные камни
RxJava и Android. Плюсы, минусы, подводные камни
 
Rx workshop
Rx workshopRx workshop
Rx workshop
 
Jason parsing
Jason parsingJason parsing
Jason parsing
 
2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good Tests2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good Tests
 
Reactive programming on Android
Reactive programming on AndroidReactive programming on Android
Reactive programming on Android
 
Fault tolerance made easy
Fault tolerance made easyFault tolerance made easy
Fault tolerance made easy
 
33rd Degree 2013, Bad Tests, Good Tests
33rd Degree 2013, Bad Tests, Good Tests33rd Degree 2013, Bad Tests, Good Tests
33rd Degree 2013, Bad Tests, Good Tests
 
Jersey Guice AOP
Jersey Guice AOPJersey Guice AOP
Jersey Guice AOP
 
Durable functions 2.0 (2019-10-10)
Durable functions 2.0 (2019-10-10)Durable functions 2.0 (2019-10-10)
Durable functions 2.0 (2019-10-10)
 
Java agents are watching your ByteCode
Java agents are watching your ByteCodeJava agents are watching your ByteCode
Java agents are watching your ByteCode
 
How to Start Test-Driven Development in Legacy Code
How to Start Test-Driven Development in Legacy CodeHow to Start Test-Driven Development in Legacy Code
How to Start Test-Driven Development in Legacy Code
 
Sustaining Test-Driven Development
Sustaining Test-Driven DevelopmentSustaining Test-Driven Development
Sustaining Test-Driven Development
 
Pragmatic unittestingwithj unit
Pragmatic unittestingwithj unitPragmatic unittestingwithj unit
Pragmatic unittestingwithj unit
 
JDK Power Tools
JDK Power ToolsJDK Power Tools
JDK Power Tools
 
Trisha gee concurrentprogrammingusingthedisruptor
Trisha gee concurrentprogrammingusingthedisruptorTrisha gee concurrentprogrammingusingthedisruptor
Trisha gee concurrentprogrammingusingthedisruptor
 
Retrofit
RetrofitRetrofit
Retrofit
 
C#을 이용한 task 병렬화와 비동기 패턴
C#을 이용한 task 병렬화와 비동기 패턴C#을 이용한 task 병렬화와 비동기 패턴
C#을 이용한 task 병렬화와 비동기 패턴
 
Java programs
Java programsJava programs
Java programs
 
Protocol-Oriented Networking
Protocol-Oriented NetworkingProtocol-Oriented Networking
Protocol-Oriented Networking
 

Recently uploaded

Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Victor Rentea
 
Tales from a Passkey Provider Progress from Awareness to Implementation.pptx
Tales from a Passkey Provider  Progress from Awareness to Implementation.pptxTales from a Passkey Provider  Progress from Awareness to Implementation.pptx
Tales from a Passkey Provider Progress from Awareness to Implementation.pptx
FIDO Alliance
 
Microsoft BitLocker Bypass Attack Method.pdf
Microsoft BitLocker Bypass Attack Method.pdfMicrosoft BitLocker Bypass Attack Method.pdf
Microsoft BitLocker Bypass Attack Method.pdf
Overkill Security
 
Harnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptx
Harnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptxHarnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptx
Harnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptx
FIDO Alliance
 
TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...
TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...
TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...
TrustArc
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Victor Rentea
 

Recently uploaded (20)

ERP Contender Series: Acumatica vs. Sage Intacct
ERP Contender Series: Acumatica vs. Sage IntacctERP Contender Series: Acumatica vs. Sage Intacct
ERP Contender Series: Acumatica vs. Sage Intacct
 
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
 
Tales from a Passkey Provider Progress from Awareness to Implementation.pptx
Tales from a Passkey Provider  Progress from Awareness to Implementation.pptxTales from a Passkey Provider  Progress from Awareness to Implementation.pptx
Tales from a Passkey Provider Progress from Awareness to Implementation.pptx
 
Navigating the Large Language Model choices_Ravi Daparthi
Navigating the Large Language Model choices_Ravi DaparthiNavigating the Large Language Model choices_Ravi Daparthi
Navigating the Large Language Model choices_Ravi Daparthi
 
الأمن السيبراني - ما لا يسع للمستخدم جهله
الأمن السيبراني - ما لا يسع للمستخدم جهلهالأمن السيبراني - ما لا يسع للمستخدم جهله
الأمن السيبراني - ما لا يسع للمستخدم جهله
 
Stronger Together: Developing an Organizational Strategy for Accessible Desig...
Stronger Together: Developing an Organizational Strategy for Accessible Desig...Stronger Together: Developing an Organizational Strategy for Accessible Desig...
Stronger Together: Developing an Organizational Strategy for Accessible Desig...
 
Human Expert Website Manual WCAG 2.0 2.1 2.2 Audit - Digital Accessibility Au...
Human Expert Website Manual WCAG 2.0 2.1 2.2 Audit - Digital Accessibility Au...Human Expert Website Manual WCAG 2.0 2.1 2.2 Audit - Digital Accessibility Au...
Human Expert Website Manual WCAG 2.0 2.1 2.2 Audit - Digital Accessibility Au...
 
Design Guidelines for Passkeys 2024.pptx
Design Guidelines for Passkeys 2024.pptxDesign Guidelines for Passkeys 2024.pptx
Design Guidelines for Passkeys 2024.pptx
 
Design and Development of a Provenance Capture Platform for Data Science
Design and Development of a Provenance Capture Platform for Data ScienceDesign and Development of a Provenance Capture Platform for Data Science
Design and Development of a Provenance Capture Platform for Data Science
 
Introduction to use of FHIR Documents in ABDM
Introduction to use of FHIR Documents in ABDMIntroduction to use of FHIR Documents in ABDM
Introduction to use of FHIR Documents in ABDM
 
Event-Driven Architecture Masterclass: Integrating Distributed Data Stores Ac...
Event-Driven Architecture Masterclass: Integrating Distributed Data Stores Ac...Event-Driven Architecture Masterclass: Integrating Distributed Data Stores Ac...
Event-Driven Architecture Masterclass: Integrating Distributed Data Stores Ac...
 
Microsoft BitLocker Bypass Attack Method.pdf
Microsoft BitLocker Bypass Attack Method.pdfMicrosoft BitLocker Bypass Attack Method.pdf
Microsoft BitLocker Bypass Attack Method.pdf
 
JohnPollard-hybrid-app-RailsConf2024.pptx
JohnPollard-hybrid-app-RailsConf2024.pptxJohnPollard-hybrid-app-RailsConf2024.pptx
JohnPollard-hybrid-app-RailsConf2024.pptx
 
Frisco Automating Purchase Orders with MuleSoft IDP- May 10th, 2024.pptx.pdf
Frisco Automating Purchase Orders with MuleSoft IDP- May 10th, 2024.pptx.pdfFrisco Automating Purchase Orders with MuleSoft IDP- May 10th, 2024.pptx.pdf
Frisco Automating Purchase Orders with MuleSoft IDP- May 10th, 2024.pptx.pdf
 
Continuing Bonds Through AI: A Hermeneutic Reflection on Thanabots
Continuing Bonds Through AI: A Hermeneutic Reflection on ThanabotsContinuing Bonds Through AI: A Hermeneutic Reflection on Thanabots
Continuing Bonds Through AI: A Hermeneutic Reflection on Thanabots
 
UiPath manufacturing technology benefits and AI overview
UiPath manufacturing technology benefits and AI overviewUiPath manufacturing technology benefits and AI overview
UiPath manufacturing technology benefits and AI overview
 
Harnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptx
Harnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptxHarnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptx
Harnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptx
 
TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...
TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...
TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
 
How to Check CNIC Information Online with Pakdata cf
How to Check CNIC Information Online with Pakdata cfHow to Check CNIC Information Online with Pakdata cf
How to Check CNIC Information Online with Pakdata cf
 

Android TDD & CI

  • 1. Android TDD & Marcin Gryszko @mgryszko
  • 3. User stories written
  • 5. walking skeleton “Implementation of the thinnest possible slice of real functionality that we can automatically build, deploy, and test end-to- end” original idea by Alistair Cockburn
  • 7. 0 1. Understand the problem 2. Think about architecture 3. Automate
  • 11. + Android plugin
  • 12.
  • 13. Failing acceptance test public class TimelineTest extends ActivityInstrumentationTestCase2<TimelineActivity> { public void test_chirps_are_displayed_timely_ordered() throws Exception { timelineDriver.waitUntilTimelineLoaded(mgryszkosChirps().size()); assertTimelineEqualTo(timelineDriver.getDisplayedTimeline(), mgryszkosChirps()); } }
  • 14. Failing acceptance test public class TimelineTest extends ActivityInstrumentationTestCase2<TimelineActivity> { public void test_chirps_are_displayed_timely_ordered() throws Exception { timelineDriver.waitUntilTimelineLoaded(mgryszkosChirps().size()); assertTimelineEqualTo(timelineDriver.getDisplayedTimeline(), mgryszkosChirps()); } }
  • 15. Page object public 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; } }
  • 16. Failing acceptance test public 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); } }
  • 17. Passing acceptance test public 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); } }
  • 20. Hamcrest public class TimelineTest extends ActivityInstrumentationTestCase2<TimelineActivity> { private void assertTimelineEqualTo(List<Chirp> actual, List<Chirp> expected) { assertEquals(expected, actual); } }
  • 21. Hamcrest public 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)); } } }
  • 22. Roboguice public 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); } }
  • 23. Roboguice public 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); } }
  • 24. Falling integration test public class ChirpJsonRepositoryTest extends TestCase { private ChirpRepository repository = new ChirpJsonRepository(); public void test_returns_the_timeline_of_a_chirper() { assertTimelineEqualTo(repository.findTimelineOf("mgryszko"), mgryszkosChirps()); } }
  • 25. Passing integration test public 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)); } }
  • 26. Passing integration test public 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)); } }
  • 27. Passing integration test public class TimelineActivity extends RoboActivity { @Inject private ChirpRepository chirpRepository; private List<Chirp> loadTimeline() { return chirpRepository.findTimelineOf("mgryszko"); } }
  • 29. Async loading public 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); }
  • 30. Async loading public 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); } }
  • 31. First unit test public 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); } }
  • 32. 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(); } }
  • 34. straight JUnit IntelliJ java.lang.RuntimeException: Stub! at junit.runner.BaseTestRunner.(BaseTestRunner.java:5) Maven Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.024 sec
  • 35. TestNG IntelliJ =============================================== Custom suite Total tests run: 1, Failures: 0, Skips: 0 =============================================== Maven Running TestSuite Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.298 sec
  • 36. 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)); } }
  • 40. you ? jobs Marcin @ osoco.es

Editor's Notes

  1. \n
  2. \n
  3. \n
  4. \n
  5. \n
  6. \n
  7. \n
  8. \n
  9. \n
  10. \n
  11. \n
  12. \n
  13. \n
  14. \n
  15. \n
  16. \n
  17. \n
  18. \n
  19. \n
  20. \n
  21. \n
  22. \n
  23. \n
  24. \n
  25. \n
  26. \n
  27. \n
  28. \n
  29. \n
  30. \n
  31. \n
  32. \n
  33. \n
  34. \n
  35. \n
  36. \n
  37. \n
  38. \n
  39. \n
  40. \n
  41. \n