[MUSIC PLAYING] SEAN MCQUILLAN: I
am Sean McQuillan, Developer Advocate for Android. MANUEL VICENTE VIVO:
And Manuel Vivo, I work in the Android
Developer Relations Team. SEAN MCQUILLAN: And we're
going to be talking to you about testing coroutines. But before we talk about
testing coroutines, let's talk a little
bit about coroutines. So at I/O, we talked
about how we're going to make Android
UI coroutines-first. And what does that
actually mean? What, practically,
does that change about what we're doing while
we're building the Android UI toolkit? So fundamentally
what that means is, as we're building
new APIs for Android, we're going to take a look at
whether we can fit coroutines into them and whether
that makes sense and provide a good coroutines
support for the APIs we're building. As we build Jetpack
libraries, we're going to use coroutines
to build those libraries. We're already doing that with
some of the Jetpack libraries we're working on now. So you're going to
start seeing coroutines shipped in Kotlin-first
jet Jetpack libraries. Additionally, we're going
to write documentation. I have put documentation
up on developer.android.com to explain how to use
coroutines and how to use coroutines with
architecture components and other parts of Android. So at I/O, we talked about a
bunch of different libraries that we're working on. And since then, four of
them have made it to stable. So that's awesome. So you can use WorkManager,
Retrofit, Room, viewModelScope. These all support coroutines out
of the box in a stable version. The liveData builder,
lifecycleScope, and whenStarted are all still in a release
candidate state on the train to stable. And Kotlinx coroutines
test-- the library we are going to be talking
about today-- it's still experimental. And it's on the trend
to stable as well. So to zoom in on
testing coroutines, we need to define an application
that we can actually test. So I'm going to
go ahead and walk through how to add coroutines
to an application that uses only room just to
keep the app simple, so we can focus
in on the testing. It's going to be just
a simple to-do app that store strings and
you can mark them as done. And I am going store
this in a Room database. And to do that, I'm going to
need to define an entity like I would with any Room database. And there's no coroutines
in your entity. It's still just an object that
holds a row for your database. But then we're going
to add coroutines over in the Dao of our Room. So here we can see
where we're going to start integrating
coroutines into this Room flow. We're going to go ahead
and add a suspend modifier on one of our Room queries. This time we're going to
insert with the addItem. And that makes this
function Main-safe. It's now a suspend function. And Room is going to run that
query on a background thread. And it's going to run that
on a Custom dispatcher. It's actually going to be
the same executor that Room uses if you use live data. And it gets this other
really cool superpower of, its cancel-able. So if the coroutine
that calls it cancels, it's now cancel-able
all the way down. We're also going to
do the same thing. We're going to make a
suspend function for fetching all the items here as well. You could use Flow for
something like this as well. But I want to keep this example
simple so we can focus in on the testing part. Oh, I have a little
bit of code up here that's kind of questionable. But I wanted to
show real quick-- it is, in fact, Main-safe. You can make questionable
architecture choices. And this is now
technically correct, which, as I like
to say, is maybe the worst kind of correct. So we're going to move on. We're going to make a
repository that uses our Dao. So we basically have to now
make our first API decision. We have to actually figure out
how to use coroutines here. And one option
that we have is we can make the suspend
function like this. And that's very similar
to what we did before. Or we could return a
Deferred right here. This is kind of like
a promise or a future, if you're familiar with that. But basically, it's an
object that lets you say, I would like to get the result
of this computation later. And these are two different
ways I could write this API. And I have to make
a choice and decide which one I'm going to do here. And if I compare
these two, they're similar in a lot of ways. And there's actually
some big differences. So both of them basically
require a suspend function to call them. When you call the
deferred version, you don't technically need
to be in a suspend context to call it, but to get a
result from it, you later will. The suspend version does support
that auto cancellation feature that's super cool, and
awesome, and magical coroutines Kotlin stuff. The deferred version does not. Both of are Main-safe. There's absolutely no difference
in the threading behavior between these two
implementations. But the threading behavior
does get quite different when we look at how we
get the results out. So deferred's have
this callback called invoke on completion, which just
gets called on any old thread. And you have no
control over that. And it becomes really
difficult to actually use that to get a result
out if you're not in a suspend function. So generally, in
Kotlin, you should refer to exposed
suspend functions just as many times as you can. That's just the place
where you should default. And then we're going to
go over to our viewModel and finish out this
flow for adding an item to our to-do list. So the viewModel is the
natural owner of this work because it's the thing that
owns the work that's happening. So that's why I'm going to
actually launch the coroutine. This launch call right
here actually creates a new coroutine,
which then allows me to call the suspend
functions that I just created. And then I can use the
result right away right here without having to
define a callback, which is kind of like the superpower
of coroutines right there. So there's three basic
rules that we walk through as we were going
through that code. So as you're designing
code with coroutines, prefer to expose suspend
functions as your primitive API choice. Try not to return a bunch
of deferred's or build complicated interfaces
that are harder than that, unless you have a
really good reason to go for other interfaces. Have whatever the
natural owner of the work is that kind of contains
the lifecycle of that work-- be the thing that launches it. And just learn to trust that
main-safety is going to work. I've seen a lot of code as
people come into coroutines. And they'll start with
contexting five times on the way to actually
calling a database call. Just trust that it's going
to work, and it does. So that's all I really
have to talk about for this basic coroutines talk. And I'm going to pass
it over to Manuel, who's going to talk about testing. MANUEL VICENTE VIVO:
Thank you, Sean. Testing is an integral part of
the app development process, but I don't want to spend
that much time about it. So TLDR-- test your code. We are going to focus on
unit testing in this talk. So how can we define
a good unit test? A good unit test should be fast. You shouldn't have to wait
for it to fail or pass. And it should be
reliable-- always give you the same results. And it should be
isolated as well. So execution of
unit tests should be independent from each other. And obviously, after
the test finishes, no other work should be running. So as we said,
we're going to see how to test coroutines and the
code that we showed before. So when testing coroutines,
I would like to ask yourself, is the test that
I am creating now through getting the
execution of a new coroutine? If that's the case, it's
because you are likely calling launch or async
in code under test. If that's not the case, it's
because you are probably testing a suspend function that
doesn't create a new coroutine. And if nothing of this happens,
you are not testing coroutines. So we are living without. So a test is broadly going
to fall into one of these two categories. So FYI-- as Sean
said before, we're going to be using the Kotlinx
coroutine test library. And it's an experimental. Keeping up to date
should be relatively straightforward on
the road to stable. So I wouldn't worry
that much about it. So we're going to see how to
test suspend functions now, and specifically,
the repo layer. The repo layer as we
said, it's supposed to expose suspend functions. Now we're going to see this
insertTodo, which is basically adding an item to the Dao. How can we test this? This is a suspend function. And it's run inside a coroutine. And for that, we can use
the method runBlockingTest, which is the method
from the test library that we just mentioned. runBlockingTest is going
to create a new coroutine, and it's going to allow you
to execute suspend functions immediately. And you may have heard of
runBlocking in the past. What is the difference
with runBlockingTest? Well, runBlockingTest
is going escape delays, so you don't have to waste
extra time in your tests. How can we use
runBlockingTest in our tests? Basically, just have
to grab your test body inside this method--
runBlockingTest. And that would be it. So if we go through the
test, we are creating our subject-- our repo. Then we're calling the
suspend function, insertTodo. We can do that because we
are inside a coroutine. And now that insertTodo
function is going to be executed immediately. So therefore now we can assert
that the item was have it. That was easy, right? Things get complicated
now with tests that are going to
trigger new coroutines. So we're going to focus on
how to test viewModel later. So now here we
have addItem, which is a regular function-- it's
not a suspend function-- that is going to create
a new coroutine. This is executed
with viewModelScope, which actually uses the
dispatchers.main as a default dispatcher. Here, just to
simplify everything, calling the repo with a text--
something we want to add. Can we use the technique
that we just saw before? So just grabbing our test
body is a runBlockingTest. So if you do this,
it is going to fail. Why? We are going to see
this in a second. So if we had to visualize
what's happening here in the different
threads, you will see that the test
body is going to be executed in the test thread. But as soon as you call
viewModelScope does launch, that code is going to execute
in a different thread. And so the test thread is
going to keep on running. And the test assertions
are likely going to fail. Because still, all
their code might be running in other threads. So this is not an option. We cannot use
runBlockingTest as it is. What if we take runBlockingTest
out of the equation? We want to make it simple. And we just wait for
something to be there? For example, you
can use Mockito wait if you're using Mockito or
any kind of other testing framework. But here, what we
really are doing is that the coroutine
code is still running on a different thread. But we're blocking
the test thread, just to wait for
something to be there. So this might be OK
in some [INAUDIBLE],, don't get me wrong. But this test doesn't fail fast. And so even if it
passes, it is going to add an extra overhead
time for every single test that you run. So your test rate is going
to be overall, slower. So this is OK, but
we can do better. So actually, what we want-- it's a test that are going
to pass or fail fast. That's clear. And we want them
to be deterministic when running coroutines. So we want to remove that
randomness from the test to make it reliable. What can we use? We have a class in
the library, which is called TestCoroutineDispatcher. This is just a
rule of dispatcher, but it's a fake one. And it's going to
allow you to control the execution of the coroutines
when you are doing tests. And so how can we use
TestCoroutineDispatcher? We said before
that viewModelScope is using Dispatchers.Main. So somehow we have
to replace it. But to be honest,
Dispatchers.Main is not even available
in unit test. So you couldn't use it
anyway if you wanted to. And this is because
Dispatchers.Main uses the Android main
looper to access some code. And so that's available
in instrumentation tests, but not in unit tests. So we need to replace it by
a TestCoroutineDispatcher. How can we do that? So for every test
class, you would have to add some code
like this, whereas you are going to declare a variable-- that's dispatcher. And then before running
every single test, you are going write
Dispatchers.Main with this method-- Dispatchers.setMain. And then after the
test, you are going to reset everything that
you did and then clean up the TestCoroutineDispatcher. This just makes sure that
no other work is running after the test finishes. So this is another
boilerplate code to add to every
single test class. So you can strike this out
and put it in a JUnit rule. And then we can have
something like this. So now we are in our
TodoViewModelTest. We defined our main
coroutine rule with the code that we just saw before. And how can we use it? So we said before we
have the runBlockingTest. TestCoroutineDispatcher
also allows us to call runBlockingTest. But with the difference that
we had before is that now, every single coroutine
that gets started with this test dispatcher is
going to execute immediately. So that's pretty handy for us. So now, you can make
it shorter if you want to-- if you don't
want that boilerplate code. So you can say
coroutineRule.runBlockingTest create an action and function. Be imaginative-- Kotlin power. SEAN MCQUILLAN: You can
go crazy with Kotlin and make that an apply
function right there. MANUEL VICENTE VIVO: Definitely. So if we see what's
happening now-- if we utilize that
with the threading that we saw before, you will
see that now runBlockingTest-- in reality, it's going to
create a new coroutine. And everything is going
to be executed there. So this is going to create a
new coroutine that this body is going to get executed. viewModelScope.launch is going
to be executed immediately there. And then by the time the
test assertion is gone, all the work that the coroutine
has started, is finished. So you are good to go. But what if you don't
use Dispatchers.Main? Now imagine that
in our viewModel, we want to do some formatting
in Dispatchers.default just before adding
that to the repo. So can we use just the
rule runBlockingTest? You're going to have
the same problem. That coroutine is going to be
executed in a different thread. And so the test assertions
are likely going to fail. And this is because we
hard-coded Dispatchers.default in the code. And that's not a pretty
good practice for testing. So what we recommend is
that you should always inject Dispatchers. So how can we do that? For example, go and
watch the DI talk. I'm pretty good now. And so in our viewModel,
what we can do is pass the default
dispatcher as a parameter. And the default
dispatcher later on will be used to execute
the viewModelLaunch. And in this case,
obviously in production, you will still be using
Dispatchers. default. But now in tests, what we can
do is pass in the testDispatcher from the coroutine
rule in our viewModel. And if we do this, we'll
get the expected results. And the coroutine
that we started is going to be
executed immediately. This is not the only
thing you can do with TestCoroutineDispatcher. Sean is going to tell
you more about it. SEAN MCQUILLAN: So I
know we're all awaiting the end of this conference. So let's kind of get
through this section. But let's see these three bullet
points that Manuel had earlier. And let's dive
into these and talk about some of the features
of TestCoroutineDispatcher and the other parts
of the library. So we started with, we want
to make tests that run fast. Who loves waiting for long test
suites that take half an hour to run? nobody? Whoa. Oh, there's a person up there. Yes, one person--
we should chat. So we all want our test
suite to run fast-- milliseconds is awesome,
seconds is good, minutes, OK. That's what we're
aiming for here. And so the big thing that
TestCoroutineDispatcher helps you with here-- and
runBlockingTest kind of work together on this-- is, it gives you this delay
and time out behavior. So this lets you basically
auto-progress time from your test in the
coroutines context. So if there's a delay or
a timeout in your test, you can trigger that
immediately or instantly in your test execution
instead of having to actually wait a second or
five seconds for that time out to happen. In practical test
code, this is typically used for testing time outs. That's the reason that
you would actually end up calling these
functions explicitly. But it's nice to have
that feature available. It's also kind of
fast from things that it helps you
do as a programmer. Because the other
cost to writing tests is that you have
to write the tests. So one thing that it does
is, it returns units. So you can just apply it-- say my test equals
runBlockingTest. Which is nice because
if you use runBlocking, it returns the last
value of the lambda. And then JUnit4 won't run it. So that's a pretty nice thing. Every single part of the
library is injectable. So there is
TestCoroutineDispatcher, there is testCoroutineScope. And you can inject
either of those, depending on your architecture
and how you need it to work with your application. And it's also extensible. The whole library is
designed from the beginning to be test framework agnostic. So it doesn't have a
dependency on JUnit4.12. So whatever dependency of
JUnit you have in your builds is going to be fine. It's going to work
with JUnit5 and it's going to work with your
custom test runner. The other thing we want
from our test suites is, we want them to be reliable. We either want them
to either always fail because things are broken. Or we want them to always pass
because things are not broken. We typically don't want them
to fail one out of 10 times. And you know you've
hit this when you're like, oh, the build failed. Let me run that again. And that's the
first thing you do. And so when you get
in that situation, you want to spend some time on
code quality and test quality. And this library helps
you when you're doing coroutines in a couple ways. So the big one-- really these two kind
of work together-- is, it gives you explicit control
over your coroutine execution. And it does that by transforming
what was fundamentally a very asynchronous activity-- running
a bunch of things on different threads, and joining
them all over the place, and doing a bunch
of concurrency-- and turning it into a
deterministic process that should execute the same
way every single time. And so we can visualize
that a little bit. So let's imagine this is the
order of coroutine execution in one of my tests. So I've written a test
using runBlockingTest. And the coroutines execute A,
then B, then C, then D, then E. Then I check this in, I put it
into my continuous integration. And the test runs
again and again. And each time, it's running this
coroutine in the same order. And because it's
deterministic, I know that until I either change
the code or I change that test, it's going to keep passing. Which is lovely, because I'm
going to only get signals when it fails. If I didn't have
deterministic behavior here, I could end up in a
situation where I ended up getting a different order. And this different
order may not work with the assertions I
made, or the fakes I have, or some part of my test code. Because you know it's test code. I just put it together. It's not production code. And so it's going to fail, and
I'm going to get a flaky build, and I'm just going to
hit that rebuild button. And so that's why I
prefer determinism when I'm testing
concurrency, especially down in that specific level. And then the other big
thing for this dispatcher is, it's pause-able. So this is like a huge thing. So it does this
immediate execution, which is very much the exact
same thing that dispatchers on confine does. However, since
it's pause-able, it allows you to basically
undo that and actually execute coroutines in a
much more realistic fashion than either one. So immediate execution is
awesome for 90%-95% of tests that you write. But it's actually an order that
can never happen in production. So sometimes you need
it to not happen, and that's when you need to
pause the dispatcher to write that last 5% or 10% of tests. And then it helps you
write isolated tests. And the big thing there is,
it looks for coroutine leaks at the end of the
runBlockingTest lambda. And if you call that
cleanup test coroutine, it's going to go
ahead and make sure that you didn't leak a coroutine
into your next test suite, which writes to the database. And then your test fails
one out of 10 times. So it helps you with this
situation right there. And it also tries really hard--
and not always successfully, but it tries-- to put the uncaught
exceptions into the test that caused the uncaught
exception and caused that test to fail instead of the
test suite 1,000 tests later. So go check out
kotlinx-coroutines-test. Coroutines are awesome. We love them at Android. And that's because-- this graph. As you can clearly
see on this graph, as you go into more complex
code, the axis goes up. [LAUGHING] Thanks for coming to
Android Dev Summit. [MUSIC PLAYING]