TDD
on Android
Danny Preussler, CodingSerbia 2019
@PreusslerBerlin
about:me
• Started talking about testing in 2008
@PreusslerBerlin
about:me
• Switched to Java in 2003
• Switched to Kotlin in 2016
• Worked for eBay, Groupon, Viacom
@PreusslerBerlin
sporttotal.tv
• We are streaming under-
medialised sports
• Amateur and semi professional
football
• Professional Volleyball…
@PreusslerBerlin
Let’s
talk about
tests
@PreusslerBerlin
@PreusslerBerlin
“Code without tests is bad code.”
“any code without test is a legacy code.”
(Michael C. Feathers)
@PreusslerBerlin
“Code without tests is bad code.”
“any code without test is legacy code.”
(Michael C. Feathers)
@PreusslerBerlin
Tests give confidence
“how do you know something works when you
don’t have test for it?”
(Robert ‘Uncle Bob’ Martin)
@PreusslerBerlin
Tests allow refactoring
“Refactoring without good test coverage
is changing shit”
(Martin Fowler)
@PreusslerBerlin
TDD
@PreusslerBerlin
History
• Started with test first from XP in 1999 by Kent
Beck
• Just a rediscovery:
test first was normal in early days of
programming
• Famous as TDD since Kent Becks book in 2003
@PreusslerBerlin
History
• Started with test first from XP in 1999 by Kent
Beck
• Just a rediscovery:
test first was normal in early days of
programming
• Famous as TDD since Kent Becks book in 2003
@PreusslerBerlin
History
• Started with test first from XP in 1999 by Kent
Beck
• Just a rediscovery:
test first was normal in early days of
programming
• Famous as TDD since Kent Becks
book in 2003
@PreusslerBerlin
How does
it work?
@PreusslerBerlin
The 3 rules of TDD
• You must write a failing test before you
write any production code.
• You must not write more of a test than is
sufficient to fail, or fail to compile.
• You must not write more production
code than is sufficient to make the
currently failing test pass.
nano-cycle (seconds)
@PreusslerBerlin
The 3 rules of TDD
• You must write a failing test before you
write any production code.
• You must not write more of a test than is
sufficient to fail, or fail to compile.
• You must not write more production
code than is sufficient to make the
currently failing test pass.
nano-cycle (seconds)
@PreusslerBerlin
The 3 rules of TDD
• You must write a failing test before you
write any production code.
• You must not write more of a test than is
sufficient to fail, or fail to compile.
• You must not write more production
code than is sufficient to make the
currently failing test pass.
nano-cycle (seconds)
@PreusslerBerlin
The 3 rules of TDD
• You must write a failing test before you
write any production code.
• You must not write more of a test than is
sufficient to fail, or fail to compile.
• You must not write more production
code than is sufficient to make the
currently failing test pass.
nano-cycle (seconds)
@PreusslerBerlin
Red Green Refactor
• Create a unit tests that fails
• Write just enough production code
to makes that test pass.
• Clean up the mess you just made.
micro-cycle (minutes)
@PreusslerBerlin
Red Green Refactor
• Create a unit tests that fails
• Write just enough production code
to makes that test pass.
• Clean up the mess you just made.
micro-cycle (minutes)
@PreusslerBerlin
Red Green Refactor
• Create a unit tests that fails
• Write just enough production code
to makes that test pass.
• Clean up the mess you just made.
micro-cycle (minutes)
@PreusslerBerlin
Red Green Refactor
• Create a unit tests that fails
• Write just enough production code
to makes that test pass.
• Clean up the mess you just made.
micro-cycle (minutes)
@PreusslerBerlin
Red Green Refactor
• Make it fail
• Make it work
• Make it right
micro-cycle (minutes)
@PreusslerBerlin
WTF
https://www.flickr.com/photos/jantruter/12794187835
@PreusslerBerlin
Baby steps
Based: our limited minds are not capable of two
simultaneous goals:
1. Correct behavior.
2. Correct structure.
https://www.flickr.com/photos/21561428@N03/4616816371
@PreusslerBerlin
Red Green Refactor
• You need red:
• as no one tests the tests
• In green:
• Speed trumps design!
• Make it dirty!
@PreusslerBerlin
Red Green Refactor
• Always be one step away from green bar
@PreusslerBerlin
Red Green Refactor
• Always be one step away from green bar
• Think of new test-> write it down
@PreusslerBerlin
Red Green Refactor
• Always be one step away from green bar
• Think of new test-> write it down
• It’s getting ugly? -> write it down
@PreusslerBerlin
Start somewhere
@PreusslerBerlin
Write the tests that
forces you
to write the code
you want to write
@PreusslerBerlin
Start somewhere
@PreusslerBerlin
Start somewhere
@PreusslerBerlin
Start somewhere
@PreusslerBerlin
Start somewhere
@PreusslerBerlin
Start somewhere
@PreusslerBerlin
Start somewhere
@PreusslerBerlin
Start somewhere
@PreusslerBerlin
Start somewhere
@PreusslerBerlin
Start somewhere
@PreusslerBerlin
Start somewhere
@PreusslerBerlin
Write the tests that
forces you
to write the code
you want to write
Tip: Create a list of tests on paper
@PreusslerBerlin
Isn’t that
slow?
https://pixabay.com/en/snail-illustration-drawing-yellow-1757756/
@PreusslerBerlin
Isn’t it slow?
Writing tests is slower than not writing tests.
You’ll write at least as much test code as
production code
@PreusslerBerlin
Isn’t it slow?
Writing tests is slower than not writing tests.
You’ll write at least as much test code as
production code
@PreusslerBerlin
Isn’t it slow?
Research shows that TDD:
adds 10%—30% on initial costs
= longer to complete their projects
@PreusslerBerlin
Isn’t it slow?
Research shows that TDD:
• Reduces defect density by 60-90 %
• Reduces production bug density by 40–80%
@PreusslerBerlin
Isn’t it slow?
Without TDD, you spend a few weeks writing
code which mostly works and spend the next
year "testing" and fixing many (but not all) of the
bugs
@PreusslerBerlin
Isn’t it slow?
With TDD, you spend a year writing code which
actually works. Then you do final integration
testing for a few weeks.
@PreusslerBerlin
Isn’t it slow?
• Single feature will take longer
• Bugfixing phase is shorter
• Debugging disappears
• Ci finds bugs before tester does
• Long term it’s much faster no more big rewrite
@PreusslerBerlin
Isn’t it slow?
• Single feature will take longer
• Bugfixing phase is shorter
• Debugging disappears
• Ci finds bugs before tester does
• Long term it’s much faster no more big rewrite
@PreusslerBerlin
Isn’t it slow?
• Single feature will take longer
• Bugfixing phase is shorter
• Debugging disappears
• Ci finds bugs before tester does
• Long term it’s much faster no more big rewrite
@PreusslerBerlin
Isn’t it slow?
• Single feature will take longer
• Bugfixing phase is shorter
• Debugging disappears
• Ci finds bugs before tester does
• Long term it’s much faster no more big rewrite
@PreusslerBerlin
Isn’t it slow?
• Single feature will take longer
• Bugfixing phase is shorter
• Debugging disappears
• Ci finds bugs before tester does
• Long term it’s much faster no more big rewrite
@PreusslerBerlin
Benefits
•YAGNI and KISS out of the box
•No more over-engineering
•Test all business needs
•Minimize debugging
•Minimize use of Android devices
@PreusslerBerlin
Benefits
•YAGNI and KISS out of the box
•No more over-engineering
•Test all business needs
•Minimize debugging
•Minimize use of Android devices
@PreusslerBerlin
Benefits
•YAGNI and KISS out of the box
•No more over-engineering
•Test all business needs
•Minimize debugging
•Minimize use of Android devices
@PreusslerBerlin
Benefits
•YAGNI and KISS out of the box
•No more over-engineering
•Test all business needs
•Minimize debugging
•Minimize use of Android devices
@PreusslerBerlin
Benefits
•YAGNI and KISS out of the box
•No more over-engineering
•Test all business needs
•Minimize debugging
•Minimize use of Android devices
@PreusslerBerlin
Benefits
•It’s Gamification -> fun
•Small Changes -> Small PRs
•Always know what’s next
@PreusslerBerlin
Benefits
•It’s Gamification -> fun
•Small Changes -> Small PRs
•Always know what’s next
@PreusslerBerlin
Benefits
•It’s Gamification -> fun
•Small Changes -> Small PRs
•Always know what’s next
@PreusslerBerlin
Benefits
•Less stressful
•Go home any time
•Interrupt any time
•No need for “flow”
@PreusslerBerlin
Benefits
•Less stressful
•Go home any time
•Interrupt any time
•No need for “flow”
@PreusslerBerlin
Benefits
•Less stressful
•Go home any time
•Interrupt any time
•No need for “flow”
@PreusslerBerlin
Benefits
•Less stressful
•Go home any time
•Interrupt any time
•No need for “flow”
@PreusslerBerlin
Android
specifics
@PreusslerBerlin
val tested = spy(PrivacyPolicyActivity())
@Nested
inner class `When created` {
@Test
fun `sets viewmodel`() {
tested.onCreate(null)
verify(tested)
.setContentView(R.layout.main)
}
@PreusslerBerlin
val tested = spy(PrivacyPolicyActivity())
@Nested
inner class `When created` {
@Test
fun `sets viewmodel`() {
tested.onCreate(null)
verify(tested)
.setContentView(R.layout.main)
}
}
@PreusslerBerlin
val tested = spy(PrivacyPolicyActivity())
@Nested
inner class `When created` {
@Test
fun `sets viewmodel`() {
tested.onCreate(null)
verify(tested)
.setContentView(R.layout.main)
}
}
@PreusslerBerlin
val tested = spy(PrivacyPolicyActivity())
@Nested
inner class `When created` {
@Test
fun `sets viewmodel`() {
tested.onCreate(null)
verify(tested)
.setContentView(R.layout.main)
}
}
@PreusslerBerlin
val tested = spy(PrivacyPolicyActivity())
@Nested
inner class `When created` {
@Test
fun `sets viewmodel`() {
tested.onCreate(null)
verify(tested)
.setContentView(R.layout.main)
}
}
protected
@PreusslerBerlin
package android.app
fun Activity.onCreate(bundle: Bundle?)
= this.onCreate(bundle)
github.com/
dpreussler/android-tdd-utils
@PreusslerBerlin
val tested = spy(PrivacyPolicyActivity())
@Nested
inner class `When created` {
@Test
fun `sets viewmodel`() {
tested.onCreate(null)
verify(tested)
.setContentView(R.layout.main)
}
}
Does this work?
@PreusslerBerlin
Android SDK under test
• Android classes can be loaded on JVM:
build/generated/mockable-android-XX.jar
• Removes finals and ALL the code!
@PreusslerBerlin
Android SDK under test
• Empty methods with default return values
android {
…
testOptions {
unitTests.returnDefaultValues = true
}
-> no code will run
@PreusslerBerlin
The problem of v4… Activity
• mockable.jar not existing for libraries,
including support library L
-> real code will run
@PreusslerBerlin
The problem of v4… Activity
• Helper method
fun <T : FragmentActivity> T.prepareForTest(): T {
…
whenever(supportFragmentManager).thenReturn(mockSupportFragmentManager)
whenever(fragmentManager).thenReturn(mockFragmentManager)
whenever(layoutInflater).thenReturn(mockLayoutInflater)
}
@PreusslerBerlin
val tested =
PrivacyPolicyActivity().prepareForTest()
@Nested
inner class `When created` {
@Test
fun `sets viewmodel`() {
tested.onCreate(null)
verify(tested)
.setContentView(R.layout.main)
}
}
@PreusslerBerlin
val tested =
PrivacyPolicyActivity().prepareForTest()
@Nested
inner class `When created` {
@Test
fun `sets viewmodel`() {
tested.onCreate(null)
verify(tested)
.setContentView(R.layout.main)
}
}
@PreusslerBerlin
@Nested
inner class `When started` {
@Test
fun `forwards lifecylce to viewmodel`() {
tested.onStart()
verify(viewModel).onStart() }
}
java.lang.NullPointerException
at
android.support.v17.leanback.app.PlaybackSupportFragment.setupChild
FragmentLayout(PlaybackSupportFragment.java:730)
at
android.support.v17.leanback.app.PlaybackSupportFragment.onStart(Pl
aybackSupportFragment.java:899)
@PreusslerBerlin
fun Fragment.safeStart() {
try {
onStart()
}catch (e: NullPointerException) {}
catch (e: IllegalStateException) {}
}
github.com/
dpreussler/android-tdd-utils
@PreusslerBerlin
Avoid
Activity/Fragment
code
@PreusslerBerlin
Avoid activity and fragment code
• Use life cycle aware components!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
observeLifecycleIn(viewModel)
}
@OnLifecycleEvent(ON_START)
fun onStart() {
….
}
@PreusslerBerlin
Avoid activity and fragment code
• Testing android view classes are hard
• Avoid needing to test them!
• ViewModel encapsulates view state testable
• Use the power of data binding
@PreusslerBerlin
Avoid activity and fragment code
• Testing android view classes are hard
• Avoid needing to test them!
• ViewModel encapsulates view state testable
• Use the power of data binding
@PreusslerBerlin
Avoid activity and fragment code
• Testing android view classes are hard
• Avoid needing to test them!
• ViewModel encapsulates view state testable
• Use the power of data binding
@PreusslerBerlin
Avoid activity and fragment code
• Testing android view classes are hard
• Avoid needing to test them!
• ViewModel encapsulates view state testable
• Use the power of data binding
@PreusslerBerlin
Data binding…
XML ViewModelbind
@PreusslerBerlin
Data binding…
who is the view?
• Solves the one big android question once
and for all
XML
@PreusslerBerlin
What’s left?
• Activities needs to be declared in manifest
• Permissions for older devices
@PreusslerBerlin
What’s left?
@PreusslerBerlin
What’s left?
• Manifest via lint
lintOptions {
check 'Registered'
warningsAsErrors true
}
@PreusslerBerlin
What’s left?
• Zero tolerance policy very valuable
@PreusslerBerlin
What’s left?
• Get access to merged manifest and resources:
testOptions {
unitTests.includeAndroidResources = true
}
com/android/tools/
test_config.properties
@PreusslerBerlin
Whats left?
• UI: text size, button text…
@PreusslerBerlin
What about
Espresso?
@PreusslerBerlin
What about Espresso
• need to be fast! fast feedback!
• need to run continuously!
Espresso can’t give any of those
@PreusslerBerlin
What about Espresso
• Test first? Yes!
• TDD? No!
@PreusslerBerlin
What about
Robolectric?
@PreusslerBerlin
Tired of issues like
java.lang.NullPointerException
at
org.robolectric.manifest.MetaData
.init(MetaData.java:55)
at
org.robolectric.manifest.AndroidM
anifest.initMetaData(AndroidManif
est.java:377)....
?
Sleepy by Tomas; flickr.com/photos/tma/2438467223; CC 2.0
Don’t spent more time
fixing your test setup
than fixing your app
Sleepy by Tomas; flickr.com/photos/tma/2438467223; CC 2.0
Still valid?
@PreusslerBerlin
Robolectric 4
@PreusslerBerlin
Robolectric 4
@PreusslerBerlin
Robolectric 4
@PreusslerBerlin
Robolectric 4
• Robolectric is just another “android device”
when using Espresso like API
• Not compatible with Junit5
• More stable now as of new API?!
• Still slower
@PreusslerBerlin
Robolectric 4
• Increases first test run by 6-8 sec
• With ActivityTestRule: 12-14 sec
@PreusslerBerlin
SPEED!
• Build speed is important
• Test new features in separate module!
• Tip: Keep Robolectric tests in separate test
folder:
test, androidTest, sharedTest
@PreusslerBerlin
Limitations
• Custom view and animation code might get too
hard
@PreusslerBerlin
What
about
mocking
?
@PreusslerBerlin
What about mocking
• Mocks make our tests flaky
• Only mock your outside dependency
(think of modules not classes)
@PreusslerBerlin
What about mocking
• Mocks make our tests flaky
• Only mock your outside dependency
(think of modules not classes)
@PreusslerBerlin
What about mocking
• TDD changes the way your tests will be:
Test1 Test2
Class1 Class2 Class3
Test3
@PreusslerBerlin
What about mocking
• TDD changes the way your tests will be:
Test1
Class1 Class2 Class3
@PreusslerBerlin
What about mocking
• TDD changes the way your tests will be:
Test1
Class1
@PreusslerBerlin
What about mocking
• TDD changes the way your tests will be:
Test1
Class1 Class2 Class3
@PreusslerBerlin
What about mocking
• Tests will become more solid
Test1
ViewModel Repository API
@PreusslerBerlin
What about mocking
• Tests will become more solid
Test1
ViewModel
Repository API
Store Reducer
SideEffects
@PreusslerBerlin
What about mocking
• Tests will become more solid
Test1
ViewModel
Repository API
Store Reducer
SideEffects
@PreusslerBerlin
What about mocking
• Mock your outside dependency (modules)
Test1
ViewModel
Repository API
Store Reducer
SideEffects
@PreusslerBerlin
That’s not
a UNIT test!?
Test1
Class1 Class2 Class3
@PreusslerBerlin
TDD and tests
• Kent Beck spoke about behavior of the system
in his TDD book
• A “unit” does not mean every class/method!
Unit came from Blackbox!
• TDD tests behaviors not implementation details!
• What your software does, is stable!
How it does this, is unstable!
TDD what went wrong: https://www.youtube.com/watch?v=EZ05e7EMOLM
@PreusslerBerlin
TDD and tests
• Kent Beck spoke about behavior of the system
in his TDD book
• A “unit” does not mean every class/method!
Unit came from Blackbox!
• TDD tests behaviors not implementation details!
• What your software does, is stable!
How it does this, is unstable!
TDD what went wrong: https://www.youtube.com/watch?v=EZ05e7EMOLM
@PreusslerBerlin
TDD and tests
• Kent Beck spoke about behavior of the system
in his TDD book
• A “unit” does not mean every class/method!
Unit came from Blackbox!
• TDD tests behaviors not implementation details!
• What your software does, is stable!
How it does this, is unstable!
TDD what went wrong: https://www.youtube.com/watch?v=EZ05e7EMOLM
@PreusslerBerlin
TDD and tests
• Kent Beck spoke about behavior of the system
in his TDD book
• A “unit” does not mean every class/method!
Unit came from Blackbox!
• TDD tests behaviors not implementation details!
• What your software does, is stable!
How it does this, is unstable!
TDD what went wrong: https://www.youtube.com/watch?v=EZ05e7EMOLM
@PreusslerBerlin
code
coverage
?
@PreusslerBerlin
What about code coverage
In TDD
100% coverage
is a
side effect
not a goal!
@PreusslerBerlin
Wrap up
@PreusslerBerlin
"Test only if you would
want it to work.”
Kent Beck
@PreusslerBerlin
If it's worth building,
it's worth testing
If it's not worth testing,
why are you wasting your time
working on it?
@PreusslerBerlin
If it's worth building,
it's worth testing
If it's not worth testing,
why are you wasting your time
working on it?
@PreusslerBerlin
TDD on Android?
• Its possible!
• Might feel extreme
• But it’s fun!
https://www.flickr.com/photos/chefranden/14838138493
More resources
• TDD what went wrong:
https://www.youtube.com/watch?v=EZ05e7EMOLM
• The three laws of TDD by Uncle Bob
https://www.youtube.com/watch?v=AoIfc5NwRks
• TDD for those who don't need it
https://www.youtube.com/watch?v=a6oP24CSdUg
• Test Driven Development by Example (Kent Beck)
More resources
• https://online-training.jbrains.ca/p/wbitdd-01
• https://medium.com/androiddevelopers/write-
once-run-everywhere-tests-on-android-
88adb2ba20c5
• github.com/sporttotal-tv/android-tdd-
workshop
• github.com/dpreussler/android-tdd-utils
TDD
on Android
@PreusslerBerlin
@PreusslerBerlin
What
about
architecture
?
@PreusslerBerlin
What about architecture?
• TDD does not replace Architecture and Design
• Have a vision in your head
• NO big up front design
• Defer architecture decisions as long as possible
@PreusslerBerlin
What about architecture?
• TDD does not replace Architecture and Design
• Have a vision in your head
• NO big up front design
• Defer architecture decisions as long as possible
@PreusslerBerlin
What about architecture?
• TDD does not replace Architecture and Design
• Have a vision in your head
• No big up front design
• Defer architecture decisions as long as possible
@PreusslerBerlin
What about architecture?
• TDD does not replace Architecture and Design
• Have a vision in your head
• No big up front design
• Defer architecture decisions as long as possible
@PreusslerBerlin
What
about
protoypes
?
@PreusslerBerlin
What about Prototypes?
hacking is fine
but afterwards
start from scratch!
@PreusslerBerlin
What
about
MVP ?
@PreusslerBerlin
What about MVP?
MVP is a minimum feature
set but
no excuse for bad quality

TDD on android. Why and How? (Coding Serbia 2019)