Placeholder Image

Subtitles section Play video

  • [MUSIC PLAYING]

  • JONATHAN GERRISH: Hello, everyone.

  • Welcome to this morning session on test-driven development

  • for the Android platform.

  • My name is Jonathan Gerrish and I'm part of the Mobile Ninjas.

  • We're a small team within Google who

  • are passionate about software testing.

  • Can I get a quick show of hands in the audience?

  • How many of you are actually writing tests

  • as part of your normal software development practice?

  • That's fantastic.

  • OK.

  • So if you've written tests for Android before,

  • you've probably used some of our tools.

  • We developed the Android testing support library,

  • which includes the JUnit for test runner

  • and rules, the Espresso UI testing framework,

  • and we're also active contributors

  • to Roboelectric, the open source Android unit testing framework.

  • So everyone is telling you to write tests,

  • but why should you really do it?

  • It's true that tests take time to write.

  • They're adding code to your code base.

  • And perhaps you've been in this situation

  • before, where your manager or client has been telling you

  • that they're slowing you down.

  • But there's so many compelling reasons to write tests.

  • Tests give you rapid feedback on failures.

  • So failures that are spotted earlier on in the development

  • cycle are far easier to fix than ones that have gone live.

  • Secondly, tests give you a safety net.

  • With a good suite of tests, you're

  • free to refactor, clean up, and optimize

  • your code, safe in the knowledge that you're not going

  • to break existing behavior.

  • Tests are really the backbone of sustainable software

  • development.

  • You'll be able to maintain a stable velocity

  • throughout the lifetime of your project,

  • and you're going to avoid the boom-bust cycles of crunch

  • feature time and the aggregation of technical debt.

  • So in software testing, there exists the concept

  • of the testing pyramid.

  • And this is made up of a number of layers.

  • And each layer brings with it its own trade-offs that you're

  • going to have to weigh.

  • At the lowest layer is the small tests, or the unit tests.

  • And these need to be very fast and highly focused.

  • That's why we recommend you run these kind of tests, what

  • is known as local unit tests.

  • And these are going to run on your local desktop machine.

  • The trade-off you're making with these kind of tests

  • is infidelity because you're not running

  • on a realistic environment and you're probably substituting

  • in a bunch of mocks and fakes.

  • As we move up the pyramid, we're now

  • into the realms of integration testing and end-to-end testing.

  • And the key with these kind of tests is to bring in fidelity.

  • That's why we recommend that you run

  • these kinds of tests on a real device or an emulator.

  • These are the kinds of tests that

  • are going to tell you that your software actually works.

  • However, they are less focused, so a failure

  • in one of these kind of tests might take a little longer

  • to track down than it would in a unit test.

  • And one of the big trade-offs you're making

  • is in test execution speed.

  • Because you're assembling multiple components,

  • they all have to be built and then packaged,

  • shipped to a device where the tests are run,

  • and the results are collected back.

  • That's going to take extra time.

  • There's no single layer in this testing pyramid

  • that can suffice, so what you need to do

  • is to blend in tests at each different tier,

  • leveraging the strengths of one category

  • to weigh off the trade-offs in another.

  • There's no real hard and fast rule here,

  • but Google's own internal testing experts

  • recommend the 70-20-10 rule of thumb

  • as the ratio between small, medium, and large tests.

  • Let's take a look at our workflow.

  • So with test-driven development, the idea

  • is that you start by writing your tests,

  • then you implement the code to make those tests pass.

  • And then when your tests are green, you can submit.

  • Again, a quick show of hands.

  • Who out there has test-driven their code,

  • tried test-driven development in the past?

  • OK.

  • Cool.

  • We like test-driven development because it

  • makes you think about the design of your application up front.

  • It gives due consideration to APIs

  • and the structure of your code.

  • With test-driven development, you're

  • also going to be writing less code because you only

  • write the code necessary to satisfy your tests.

  • This will enable you to release early and often.

  • As you're constantly green, you'll

  • be able to deploy a working application at a moment's

  • notice.

  • If you're following the test pyramid,

  • the workflow is going to look something like this.

  • First of all, we have a larger outer iteration

  • that's concerned with feature development.

  • Here, it's driven by a UI test, and the mantra

  • with test-driven development is Red, Green, Refactor.

  • We start off with a failing test,

  • we implement the code to make that test pass,

  • and then we refactor.

  • Inside the larger iteration are a series

  • of smaller iterations and these are

  • concerned with the unit tests.

  • Here, you're building the units required

  • to make the feature pass.

  • And again, you use the same mantra here.

  • Red, Green, Refactor.

  • Red, Green, Refactor.

  • Let's take a look at an example application.

  • The feature we're going to implement today

  • is the Add Notes flow to a sample note-taking application.

  • If we take a look at our mock-ups,

  • we can see that we start on a notes list screen

  • full of some existing notes.

  • There's a floating action button down at the bottom.

  • And the user will click this, taking them

  • onto the new add notes screen.

  • Here, they can enter a title and a description for their note

  • before clicking Save.

  • The note will be persisted and then

  • they'll return back to their notes list screen,

  • where they can see their newly added note,

  • along with any other notes that previously existed.

  • Coming back to our workflow for a moment,

  • remember that we start with a failing UI test.

  • So let's take a look at how this test would look using Espresso,

  • the UI testing framework.

  • The first step is to click on the Add Note button.

  • Then we enter the title and description and click Save

  • before returning to the notes list screen.

  • And here, we're going to verify that the note that we just

  • added actually shows up.

  • Now remember, with test-driven development,

  • we'll not implemented code just yet.

  • All we have to do is implement enough

  • of the application to satisfy the specification of our tests.

  • So an empty activity, and just the resources that we need,

  • will suffice.

  • Once we have that, we can run our test

  • and we'll see it'll fail.

  • Now we have to implement this feature.

  • So applications are built up of many small units.

  • These are small, highly focused, specialized components

  • that do one thing and they do it well.

  • Collections of these small units are then

  • assembled together so that their collaborations will

  • satisfy our feature.

  • Let's take a moment to summarize the key characteristics that

  • make up a good unit test.

  • As well as the normal conditions,

  • you're wanting to test your failure conditions, invalid

  • inputs, and boundary conditions.

  • You're going to end up writing a lot of unit tests.

  • Unit tests must always give you the same result every time.

  • So avoid depending on things that might change--

  • For example, an external server or the current time of day--

  • because this is going to bring flakiness into your unit tests.

  • Unit tests should exercise one specific aspect of your code

  • at a time.

  • You're wanting to see that a failure in a unit test

  • will lead you, very quickly, to a natural bug in your code.

  • And when you write unit tests, avoid

  • making too many assumptions on the actual implementation

  • of your code.

  • You want your unit test to test behavior.

  • That way, you avoid rewriting your test

  • when your implementation is changing.

  • And one of the most important aspects of unit tests

  • is they've got to be fast, especially because you're

  • writing so many of them and, during TDD workflow, running

  • them rapidly.

  • It would be terrible if you were discouraged

  • from writing tests or refactoring your code because

  • of the pain in the execution time of those tests.

  • And finally, unit tests are an excellent source

  • of documentation and the way it's constantly

  • evolving with the code as it changes,

  • unlike static documents that will stagnate over time.

  • Let's try a unit test for our Add Notes activity.

  • This activity is going to take in user input

  • and then we're going to persist it

  • to local storage on the device.

  • OK.

  • So we're going to create the Add Note activity class,

  • and this will extend Activity, which is an Android framework

  • class.

  • It has a view which is going to be inflated with a layout.

  • The user will enter their data here.

  • And then we're going to persist that note into Android

  • SharedPreferences mechanism.

  • It's conceivable that, as our application evolves,

  • so did our requirement.

  • And perhaps our storage requirements

  • evolve to persist the notes onto cloud storage

  • and we have to build some kind of a synchronization mechanism

  • for local storage for the offline use case.

  • And in these cases, we see opportunities for abstraction.

  • We might, in this example, see that we

  • can extract a notes repository.

  • However, one of the key aspects of test-driven development

  • is that we only start by writing the simplest case first,

  • and then we iterate.

  • So we're going to resist the temptation to do this early.

  • Let's take a look at a sample of what an idealized unit

  • test would look like.

  • They're generally built up into three stages.

  • The first stage is you're setting

  • the conditions for the test, and this

  • includes preparing the environment,

  • setting up your dependencies with their required state,

  • and preparing any input data.

  • Next, we'll exercise the code under test, before finally,

  • making assertions on the results or the state.

  • I like to clearly separate each of these three

  • stages of the test and bring the pertinent aspects of each test

  • front and center to make for a really readable test.

  • Up until now with the Android platform,

  • you're writing your unit tests using

  • the mockable jarring conjunction with a mocking

  • library, such as Marketo.

  • And let's take a look at an example

  • of a test written with Marketo.

  • OK.

  • Wow.

  • That's a lot of code.

  • OK.

  • So because we have so many interactions with the Android

  • framework, we're going to need to provide

  • stubbing behavior for all of them in order just to make--

  • just to satisfy the execution paths of our test.

  • And furthermore, because Android uses a lot of static methods,

  • we're forced to introduce a second mocking

  • library, PowerMock, that will handle

  • this special case for us.

  • And there are also some pretty bad code [INAUDIBLE] here.

  • Let's take a look.

  • You see, we're forced to spy on the activity on the test

  • and we're needing to do this to modify its behavior.

  • And stubbing it out and providing some no ops.

  • So we're moving out of the realms of black box testing

  • here.

  • And finally, at the end, we're making

  • assertions about the implementation details.

  • And if these change, our test will need to change, too.

  • So remembering the characteristics of a good unit

  • test, let's take a moment to score this particular test.

  • While it is very focused, we're just

  • testing the happy path of our Add Notes flow,

  • and it's certainly fast because it's running on the local JVM.

  • However, we were making rather a lot

  • of assumptions about the implementation in that test.

  • And with this, if any of our implementation changes,

  • it's likely we'll need to rewrite

  • that test substantially.

  • And finally, all that excess boilerplate stubbing

  • is really distracting.

  • It's distracting away from the key aspects

  • of the test, the conditions of the test

  • that you're trying to document.

  • Well luckily, there's a tool that helps

  • address some of these issues.

  • So introducing Roboelectric.

  • Roboelectric is an Android unit testing

  • tool that's open sourced that we are actively contributing to.

  • And to tell you more about how you

  • can write great tests with Roboelectric,

  • I'm going to hand you over to Christian Williams,

  • the original author of Roboelectric.

  • [APPLAUSE]

  • CHRISTIAN WILLIAMS: Thanks, Jonathan.

  • It's awesome to see so many people who are

  • into Android testing and TDD.

  • Yeah, Roboelectric is this scrappy little open source

  • project that I started hacking on back

  • in the early days of Android Testing

  • because I was just super annoyed at how long it took to deploy

  • and run tests on an emulator.

  • And it's kind of been a side project

  • of a bunch of different people until last year, when

  • I had the privilege of joining my friend Jonathan at Google,

  • where he was already working, on improving Roboelectric

  • for Google's own internal test suites.

  • And since then, we've been really beefing up Roboelectric

  • and contributing back to the open source project.

  • Today, Roboelectric isn't an officially supported part

  • of the Android testing platform, but we

  • found that, when it's used correctly,

  • it can be a really useful part of your testing strategy.

  • And I'm going to show you a little bit about how

  • you can do that, too.

  • Let's go back to our notes unit test

  • and see how we might approach it with Roboelectric.

  • Since Roboelectric runs as a local unit test,

  • it'll still be running on your workstation,

  • not on an emulator.

  • But Roboelectric provides a little Android sandbox

  • next to your test, where the actual SDK code is running.

  • You'll have access to your activities, your layouts,

  • and views, and resources.

  • And you can generally just call most Android methods

  • and they'll kind of work like you'd expect.

  • There are parts of the Android framework

  • that rely on native code or collective hardware or interact

  • with external system services.

  • So for that, Roboelectric provides a sort of test stubble

  • that we call Shadows.

  • And those provide alternative limitations

  • of that code that's appropriate for unit testing.

  • Remember that test that we just saw that had 20 lines of code,

  • of mock set-up code?

  • Let's see how that looks in Roboelectric.

  • That's a lot less.

  • We've gotten rid of all the boilerplate.

  • The test is about half the size and much more concise.

  • We're not forced to think about the implementation details

  • as we're writing the test, which is quite nice.

  • Roboelectric is going to set up the application

  • according to your manifest.

  • And if we were asking it to set up our activity,

  • it runs it through the appropriate life cycle

  • to get it into the right state.

  • Inflates views, all that stuff that we expect on a device.

  • So we can just interact with it as if you're on a device.

  • So we add some text to some fields, click on it,

  • and assert that it adds a note to the repository.

  • Now, notice that we're not actually

  • going as far as the UI test that we wrote at the very beginning.

  • We're not asserting that the new note

  • appears on the view screen.

  • That would be the job of another unit test.

  • Now, I mentioned Roboelectric's shadows.

  • They actually give extended testing

  • APIs to some Android classes that

  • let us query internal state and sometimes change

  • their behavior.

  • In this example, we were asking the application

  • if any of our activities requested that an intent be

  • launched during the test.

  • We could use that to assert that, after saving

  • the note to the repository, we're

  • going to go to the View Notes activity.

  • Similar testing APIs exist for simulating hardware responses

  • or external services, things like that.

  • At this point, we have a failing unit test.

  • And now we get to--

  • we're ready for the easy part, writing the production code.

  • In the spirit of TDD, we're only going

  • to write exactly as much as is needed to make the test pass.

  • No more, no speculative coding.

  • So we inflate a layout, attach a click handler,

  • and when the click happens, we fade a note

  • and add it to the repository.

  • Now we can run the test, see it pass.

  • If there's some improvement we can make to the code,

  • we'll go back and refactor, and then we repeat.

  • This is where you get the thoroughness.

  • And Roboelectric is super handy for this

  • because it gives you nice, fast test runs.

  • You can get into a comfy cycle.

  • We want to not just test the happy path here.

  • We're going to test all the different cases that our code

  • is likely to encounter.

  • So for example, input validation and external conditions

  • like the network being down and stuff like that.

  • Roboelectric can also help with simulating device conditions

  • that you'll encounter.

  • For example, you can specify qualifiers

  • that the test should run with.

  • Here, we're saying a certain screen size and orientation,

  • which might change the layout a bit.

  • You can ask Roboelectric to run your test under a specific SDK.

  • So we'll say Jelly Bean here.

  • And it actually uses of the SDK code from that version.

  • And you can also tell Roboelectric,

  • I want to run this test under every SDK

  • that you support, or some range of them

  • that you're interested in.

  • And we support Jelly Bean through O right now.

  • At Google, we rely really heavily on Roboelectric

  • and we're investing in making it better.

  • We've got dozens of apps, including

  • these, that have hundreds of thousands

  • of unit tests running internally.

  • So it's well battle-tested.

  • And we've also recently started running the Android CTS, which

  • is the official Android test suite against Roboelectric.

  • And we're about 70% passing right now, getting better

  • with every release.

  • So if you used Roboelectric in the past

  • and found that it's come up short,

  • or if you're stuck in an old version,

  • I definitely recommend that you get up to the latest

  • because it's come a long way.

  • We've been working on reducing friction

  • in integrating Roboelectric with the Android tool chain.

  • It works now very well with Android Studio, with Gradle.

  • And we've got support for Bazel, Google's own open source

  • build system coming soon.

  • Roboelectric isn't a one-size-fits-all testing tool.

  • It's fast, but it's not 100% identical to Android

  • in every way, so you want to use it judiciously.

  • As I said before, avoid writing unit tests that link

  • multiple activities together.

  • That's not so much a unit test.

  • That's much better for Espresso.

  • If you find yourself dealing with multiple threads,

  • synchronization issues, stuff like

  • that, you're also probably not writing a unit tests, so not

  • good for electric.

  • And particularly, avoid using Roboelectric

  • to test your integration with Android APIs and things

  • like Google Play services.

  • You really need to have higher-level tests to give you

  • confidence that that's working.

  • So now that we've got some passing unit tests,

  • I'm going to hand you over to my colleague, Stefan

  • to talk about higher level testing.

  • [APPLAUSE]

  • STEFAN: Thank you, Christian.

  • Let's go back to our developer workflow diagram.

  • At this point, we hopefully have a ton of unit tests

  • and they thoroughly test all our business logic.

  • But let's switch gears and try to see how we can actually

  • write some integration tests to see how these units integrate,

  • and how they actually integrate with Android

  • and how they run in a real environment.

  • On Android, these tests are usually referred to

  • as instrumentation tests.

  • And I'm pretty sure most of you have written an instrumentation

  • test before.

  • And even though they look super simple on the surface,

  • there's actually a lot going on under the hood,

  • if you think about it.

  • You have to compile the code, you

  • have to process your resources, you

  • have to bring up a full system image and then run your test.

  • And there's a lot of things that go on on various levels

  • of the Android stack.

  • So these tests give you high fidelity,

  • but as John was mentioning, they come

  • at a cost, which is they are slower

  • and sometimes, they're more flaky than unit tests.

  • So let's actually see how this works

  • in your day-to-day development flow.

  • Let's say you're an Android Studio.

  • You've just written your new Espresso test

  • and you hit the Run button to run the test.

  • So the first thing that Android Studio is going to do

  • is it's going to install two APKs for you, the test

  • APK and the app on your test.

  • Now, the test APK contains Android JUnit Runner,

  • it contains the test cases, and your test manifest.

  • And then, in order to run the test,

  • Android Studio calls, under the hood, ADB Shell AM Instrument.

  • And then Android JUnit Runner will use instrumentation

  • to control your app on your test.

  • What is instrumentation?

  • I think you guys may have noticed this.

  • It's a top-level tag in your manifest, and why is that?

  • Instrumentation is actually something

  • that's used deeply inside the Android framework,

  • and it's used to control the lifecycle of your activities,

  • for instance.

  • So if you think about it, it's a perfect interception point

  • that we can use to inject the test runner.

  • And that's why Android JUnit Runner is nothing more or less

  • than instrumentation.

  • Let's go a little bit deeper and see

  • what happens when Android Studio actually runs your test.

  • It runs ADB Shell AM Instrument, which

  • will end up calling out to Activity Manager.

  • Activity manager will then call, at one point,

  • onCreate on your instrumentation.

  • Now that we know that Android JUnit Runner is

  • our instrumentation, at this point,

  • it will call onCreate on the runner.

  • And then the runner is going to do a few things for you.

  • It's going to collect all your tests.

  • Then it's going to run all these tests sequentially

  • and then it's reporting back the results.

  • One thing to note here is that Android JUnit runner--

  • and you may have noticed this--

  • runs in the same process than your application.

  • And more importantly, if you usually

  • use Android JUnit Runner, it runs all the tests

  • in one single instrumentation invocation.

  • Android JUnit runner is heavily used inside Google.

  • We run billions of tests each month

  • using Android JUnit runner.

  • And while doing so, we saw some challenges that we faced

  • and that we had to solve.

  • One thing that we see a lot is Shared State.

  • And I'm not talking about the kind of shared state

  • that you control and that you code in your app.

  • I'm talking about the shared state that builds up on memory,

  • builds up on disk, and makes your tests fail

  • for no reason or unpredictable conditions.

  • And this, among other things, will, at one point,

  • lead to crashes.

  • But in the previous module that I just showed you,

  • if one of your tests crashes your instrumentation,

  • it will take the whole app process with it

  • and all the subsequent tests will not run anymore.

  • And this is obviously a problem for large test suites.

  • Similarly, if you think about debugging,

  • if you run a couple of thousand tests in one invocation,

  • just think about what your lock head

  • will look like when you have to go through it for debugging.

  • So that's why inside of Google, we have

  • taken a different approach.

  • Inside of Google, every test method

  • runs in its own instrumentation and location.

  • Now, you can do this today, right?

  • You can make multiple ADB calls.

  • You can use a runner arc and maintain your custom script.

  • But the problem is it might not really

  • integrate well with your development environment.

  • That's why, today, I'm happy to announce the Android Test

  • Orchestrator.

  • And the Android Test Orchestrator

  • is a way that allows you to run tests like we do in Google.

  • It's a service APK that runs in the background

  • and runs its test in a single instrumentation invocation.

  • And this, obviously, has benefits, right?

  • There's no shared state anymore.

  • And in fact, the Android Test Orchestrator

  • runs PM clear before it runs its tests.

  • More so, crashes are now completely isolated

  • because we have single instrumentation invocations.

  • If a crash happens, all the subsequent tests

  • will still run.

  • And similarly, for debugging, all the debugging information

  • that you collect and pull off the device

  • is now scoped to a particular test.

  • This is great and we benefit a lot from it inside of Google.

  • Let's see how it actually works.

  • On top of installing the test APK and on our tests,

  • what we do now is we install a third APK on our device,

  • and it's a service APK running in the background containing

  • the orchestrator.

  • And then, instead of running multiple ATB commands,

  • we run a single ATB command.

  • But we don't instrument the app under test.

  • We instrument the orchestrator directly.

  • And then the orchestrator is going

  • to do all its work on the device.

  • So it's going to use Android JUnit Runner to collect

  • your tests, but then it's going to run

  • each of those tests in its own invocation.

  • And it's amazing and I'm pretty sure you will like this a lot.

  • And it will be available in the next Android Testing Support

  • library release.

  • And more importantly, we will have integration

  • with Android Studio.

  • It will be available in Gradle and we will also

  • have integration with Firebase Test

  • Lab coming later this year.

  • Now that we know how to run our test,

  • let's actually look at how we can write these integration

  • tests.

  • And usually, if you write a [INAUDIBLE] test on Android,

  • you're using the Espresso testing framework.

  • And as you can see, espresso has this nice and simple API.

  • And it actually works pretty simple.

  • What it does is you give us a view matcher

  • and we find a view in the hierarchy that

  • matches that matcher.

  • And then, we either perform a view action

  • or check a view assertion.

  • And because this API is so simple,

  • it's the perfect tool, too, for fast TDD prototyping

  • of UI tests.

  • But in order to provide you such a simple API,

  • there's a lot of things that need to go on under the hood.

  • So let's actually look at how Espresso works.

  • So when you call onView and give us your matcher,

  • the first thing that we're going to do

  • is we're going to create a view interaction for you.

  • And then the next thing is we make sure

  • that your app is in an idling, sane state

  • before we are ready to interact with it.

  • And you can think of it, this is at the core of Espresso.

  • And Espresso is well-known for its synchronization guarantees.

  • And the way we do it is we loop the message

  • queue until there are no messages

  • for a reasonable amount of time.

  • We look at all your idling resources

  • and make sure they're idle.

  • And we also look at Async Tasks to make sure there's

  • no background work running.

  • And only if we know that your app is

  • in a sane and stable state and we're ready to interact,

  • we're going to move on.

  • And then we're going to traverse the view hierarchy

  • and find the view that matches your matcher.

  • And once we have the view, we're then

  • going to perform a view action or a view assertion.

  • And this is great.

  • So now let's circle back to the test

  • that we showed you in the beginning

  • and have a closer look now that we know how Espresso works.

  • So in the first line, as you may remember,

  • we tried to click on the Add Note button.

  • And here, we're just going to use a with ID matcher, which

  • is a simple matcher that is matching a view in the view

  • hierarchy according to its ID.

  • The next thing we want to do is we want to click on the View

  • and we use a Click View action for this.

  • Now, where it gets interesting is the next line.

  • Because on this line, we want to type the title and description.

  • And we want to use a type text action for that.

  • But here, all the espresso synchronization guarantees

  • will kick in and only if we know that we

  • are ready to interact with your application,

  • we're going to invoke the type test action.

  • And this is great because it frees you

  • from adding additional boilerplate

  • code and additional slipping code to your test.

  • So similarly, we're going to save the note

  • and then we're going to verify that it's displayed on screen.

  • And this is great.

  • Now we know how Espresso works and we

  • know how it's a great tool to do test-driven development.

  • And now I'm going to hand it over to Nick

  • to talk a little bit more on how you can improve your UI tests

  • and how to improve your large and medium testing strategy.

  • [APPLAUSE]

  • NICK KOROSTELEV: Thank you, Stephan.

  • So One good attribute of a UI test

  • is a test that never sleeps.

  • So let's go back to our example to illustrate

  • this point a little bit further.

  • In our example, as you remember, we

  • have a note that we save into memory,

  • which is pretty fast and pretty reliable.

  • However, in reality, as your app grows,

  • you probably want extend this functionality

  • and save your note to the cloud or Google Drive, for example.

  • So when running our large end-to-end test,

  • we want to use a real environment

  • where we hit the real server.

  • And depending on your network connection,

  • this may take a long time, so you probably

  • want to do is in the background.

  • Now the problem is that Espresso synchronization is not

  • aware of any of your long-running tasks.

  • This is somewhere where developers would probably

  • do something as ugly as putting a thread sleep in their code.

  • But with Espresso, it is not actually required

  • because you can write an Idling Resource, where

  • an idling resource is a simple interface for you

  • as a developer to implement to teach Espresso

  • synchronization of any of your custom, long-running tasks

  • of your app.

  • So with this Idling Resource, we made our large end-to-end test

  • more reliable.

  • So let's see how we can add some more medium-sized tests

  • to your test suite.

  • So for a medium-sized test, we want

  • to keep them small and focused on a single UI component, where

  • a single UI component may be a specific view, fragment,

  • or an activity.

  • So let's go back to our example to see

  • how we can isolate our large end-to-end to more

  • isolated components.

  • Here in this example, again, you may have noticed

  • that there are two activities.

  • The List activity on the left and the Add

  • Note activity on the right.

  • So until now, we wrote a large end-to-end test

  • that gives us a lot of confidence

  • because it touches upon a lot of your code

  • in your app, which is great for large end-to-end tests,

  • but it's not so great for an iterative test-driven

  • development cycle.

  • So let's see how we can isolate these

  • and have isolated tests for each activity in isolation.

  • To isolate the left-hand side, the List activity,

  • we can use Espresso Intent, where Espresso Intent is

  • a simple API that allows you to intercept

  • any of your ongoing intents, verify their content,

  • and provide back a mock activity result.

  • Great.

  • Let's see how that API actually looks like.

  • So as you can see, it's very straightforward.

  • You have an intent matcher that will match your growing intent,

  • and you can provide a version of your activity result

  • back to the caller.

  • OK.

  • Let's use this API to write our first isolated test.

  • In this test, you can see, on the first line,

  • we do exactly that.

  • We intercept our content and we provide

  • a stub version of our activity result.

  • Now, on the second line, when we perform

  • Click, instead of starting a new activity,

  • Espresso will intercept this intent

  • and provide a Stub Activity result, which we can then

  • use on the last line to verify that our UI was updated

  • accordingly.

  • Now we have an isolated test.

  • OK.

  • So let's go back to our example and see

  • how we can isolate the second part, right?

  • So when you usually write tests, you

  • end up in a position where you may

  • have some external dependencies in play that

  • are outside of your control.

  • In our example, as I showed before,

  • we have a note that we save and it hits the real server.

  • Now even though we have another resource now

  • that makes it more reliable, your test

  • can still fail because your server

  • may crash for some reason.

  • So your task will fail.

  • So wouldn't it be better if we completely isolate ourselves

  • from these conditions and run our tests

  • in a hermetic environment?

  • This will not only make your test run much faster,

  • but it will also eliminate any flakiness.

  • And beyond this specific example,

  • you further want to isolate yourself

  • from any external dependencies.

  • So for example, you don't want to test any Android system

  • UI or any other UI components that you

  • don't own because they probably already tested

  • and they can also change without your knowing

  • so your test will actually fail.

  • Let's see how our second isolated

  • test will look in code.

  • So the main point here is that we no longer use

  • the real server.

  • Instead, we set up a hermetic repository.

  • Now, there's many different ways of you to do this

  • and this is just one way.

  • So then you can use this hermetic repository

  • in order to verify that your note is actually

  • saved without ever leaving the context of your app

  • or hitting the network.

  • So at this point, if you think about it,

  • you have two smaller tests that are way more reliable

  • and run much faster.

  • But at the same time, you maintain the same amount

  • of test coverage as your large end-to-end test.

  • And this is why we want to have more of these smaller

  • isolated tests compared to the large end-to-end tests we

  • showed before.

  • OK.

  • So at this point, we iterated through our developer cycle

  • a few times and we should see all of our tests

  • start turning green and we should be confident to release

  • our feature.

  • However, before we conclude, let's

  • jump into the future for a second.

  • As your app grows and your team grows,

  • you continue adding more and more features to your app.

  • And you may find yourself in a position

  • where you may have UI running in multiple processes, which

  • is exactly what happened at Google.

  • So if you go to our Add Notes example,

  • this may look something like this.

  • You have a first activity that runs in your main process

  • on the left-hand side.

  • And now the second activity will run in a private process.

  • And in this case, we're going to call it Add Notes.

  • So how do we test that?

  • Well, before Android O, it wasn't possible to test.

  • But with Android O, there is a new instrumentation

  • attribute that you can use in order to define which

  • process you want to instrument.

  • While instrumenting and running tests, I guess,

  • each process, in isolation, is a great idea

  • and you should do it, you may find yourself in a position

  • where you want to cross-process boundaries within one test.

  • So you would probably want to write an Espresso

  • test that looks like this.

  • While this was not only impossible on a framework level

  • before Android O, this was also impossible on Espresso level.

  • Because in this specific example,

  • Espresso is not even aware of your secondary process,

  • nor can it maintain any of the synchronization guarantees

  • we all know and love.

  • Today, I'm happy to announce Multiprocess Espresso support.

  • Without changing any of your test code or your app code

  • this will allow you to seamlessly interact with UI

  • across processes, while maintaining all of us

  • Espresso synchronization guarantees.

  • And it will be available in the next version of Android Test

  • Support Library release.

  • So let's have a quick overview of how it actually works.

  • Traditionally, as you know, in our example,

  • we start in one process, where we

  • have an instance of Android JUnit Runner and Espresso,

  • in this case.

  • Now, if you remember from our example,

  • when we click the Add Note button,

  • there will be a new activity and now we have a new process.

  • So the problem now is that we have

  • two processes with two different instances of Android JUnit

  • Runner and Espresso, and they're not aware of each other.

  • So the first thing that we want to do

  • is we want to establish communication between the two

  • Android JUnit Runners.

  • And now that we have this communication,

  • we can use it to establish the communication

  • to Espresso instances.

  • And the way we do that is by having

  • an ability in the Android JUnit Runner to register any testing

  • frameworks, like Espresso, with Android JUnit Runner.

  • And then the runner will then facilitate all the handshaking

  • required in order to establish communication between the two

  • Espresso instances.

  • Now that the two Espresso instances

  • can talk to each other, it can then

  • use it in order to enable cross-process testing

  • and maintain all the synchronization guarantees

  • that we had before.

  • OK.

  • With that, we're reaching the end of our developer workflow

  • and we showed you all the tools that you

  • can use across each step of the way

  • in order to make TDD happen on Android.

  • And with that said, even if you don't follow this flow exactly,

  • hopefully, you know how to use every single tool

  • and how to write good tests in order to bring your app

  • quality to the next level.

  • So if you like to write tests and you want to write and run

  • tests like we do at Google, here are some resources

  • to get you started.

  • I want to thank you and I think we

  • have some time for questions.

  • And if not, we have office hours at 3:30 today.

  • So hopefully, we'll see you there.

  • Thank you.

  • [APPLAUSE]

  • [MUSIC PLAYING]

[MUSIC PLAYING]

Subtitles and vocabulary

Click the word to look it up Click the word to find further inforamtion about it