SlideShare une entreprise Scribd logo
1  sur  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/

Contenu connexe

Tendances

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 ToolkitSylvain Hellegouarch
 
Testing Java Code Effectively
Testing Java Code EffectivelyTesting Java Code Effectively
Testing Java Code EffectivelyAndres Almiray
 
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 2017Andres Almiray
 
Automated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.xAutomated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.xTatsuya Maki
 
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 2017Andres Almiray
 
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 hassleAnton Arhipov
 
JPoint 2016 - Валеев Тагир - Странности Stream API
JPoint 2016 - Валеев Тагир - Странности Stream APIJPoint 2016 - Валеев Тагир - Странности Stream API
JPoint 2016 - Валеев Тагир - Странности Stream APItvaleev
 
JEEConf 2017 - Having fun with Javassist
JEEConf 2017 - Having fun with JavassistJEEConf 2017 - Having fun with Javassist
JEEConf 2017 - Having fun with JavassistAnton Arhipov
 
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 ApplicationsChristopher Batey
 
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"Yulia Tsisyk
 
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 - AngularMixTracy Lee
 
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 Sydneyjulien.ponge
 
Testing time and concurrency Rx
Testing time and concurrency RxTesting time and concurrency Rx
Testing time and concurrency RxTamir Dresher
 
Akka.NET streams and reactive streams
Akka.NET streams and reactive streamsAkka.NET streams and reactive streams
Akka.NET streams and reactive streamsBartosz Sypytkowski
 
Рахманов Александр "Что полезного в разборе дампов для .NET-разработчиков?"
Рахманов Александр "Что полезного в разборе дампов для .NET-разработчиков?"Рахманов Александр "Что полезного в разборе дампов для .NET-разработчиков?"
Рахманов Александр "Что полезного в разборе дампов для .NET-разработчиков?"Yulia Tsisyk
 

Tendances (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-разработчиков?"
 

Similaire à Android TDD & CI

RxJava и Android. Плюсы, минусы, подводные камни
RxJava и Android. Плюсы, минусы, подводные камниRxJava и Android. Плюсы, минусы, подводные камни
RxJava и Android. Плюсы, минусы, подводные камниStfalcon Meetups
 
2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good Tests2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good TestsTomek Kaczanowski
 
Reactive programming on Android
Reactive programming on AndroidReactive programming on Android
Reactive programming on AndroidTomáš Kypta
 
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 TestsTomek Kaczanowski
 
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)Paco de la Cruz
 
Java agents are watching your ByteCode
Java agents are watching your ByteCodeJava agents are watching your ByteCode
Java agents are watching your ByteCodeRoman Tsypuk
 
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 CodeDaniel Wellman
 
Sustaining Test-Driven Development
Sustaining Test-Driven DevelopmentSustaining Test-Driven Development
Sustaining Test-Driven DevelopmentAgileOnTheBeach
 
Pragmatic unittestingwithj unit
Pragmatic unittestingwithj unitPragmatic unittestingwithj unit
Pragmatic unittestingwithj unitliminescence
 
Trisha gee concurrentprogrammingusingthedisruptor
Trisha gee concurrentprogrammingusingthedisruptorTrisha gee concurrentprogrammingusingthedisruptor
Trisha gee concurrentprogrammingusingthedisruptorEthanTu
 
Retrofit
RetrofitRetrofit
Retrofitbresiu
 
C#을 이용한 task 병렬화와 비동기 패턴
C#을 이용한 task 병렬화와 비동기 패턴C#을 이용한 task 병렬화와 비동기 패턴
C#을 이용한 task 병렬화와 비동기 패턴명신 김
 
Protocol-Oriented Networking
Protocol-Oriented NetworkingProtocol-Oriented Networking
Protocol-Oriented NetworkingMostafa Amer
 

Similaire à 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
 

Dernier

[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdfhans926745
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking MenDelhi Call girls
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slidevu2urc
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking MenDelhi Call girls
 
A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?Igalia
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonAnna Loughnan Colquhoun
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxMalak Abu Hammad
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Enterprise Knowledge
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsMaria Levchenko
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Miguel Araújo
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Igalia
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonetsnaman860154
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...Neo4j
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreternaman860154
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘RTylerCroy
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processorsdebabhi2
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Servicegiselly40
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfsudhanshuwaghmare1
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationSafe Software
 

Dernier (20)

[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
 
A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptx
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreter
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Service
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 

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

Notes de l'éditeur

  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