Slide 1

Slide 1 text

PRODUCTIVE
TESTS MARC
PHILIPP chuttersnap

Slide 2

Slide 2 text

ABOUT
ME 5

Slide 3

Slide 3 text

THE
CASE
AGAINST INTEGRATION
TESTS Lanju Fotografie

Slide 4

Slide 4 text

INTEGRATED
TESTS
ARE
A
SCAM Famous
ar cle/talk
by
J.B.
Rainsberger 
 Integrated
tests
are
a
scam—a
self‑ replica ng
virus
that
threatens
to
infect your
code
base,
your
project,
and
your team
with
endless
pain
and
suffering. h ps:/ /blog.thecodewhisperer.com/permalink/integrated‑tests‑are‑a‑scam

Slide 5

Slide 5 text

INTEGRATED
VS.
INTEGRATION I
use
the
term
integrated
test
to
mean any
test
whose
result
(pass
or
fail) depends
on
the
correctness
of
the implementa on
of
more
than
one
“piece of
non‑trivial
behavior”.

Slide 6

Slide 6 text

WHY
INTEGRATED
TESTS
ARE
BAD Slow,
Bri le,
Flaky You
can
never
cover
all
branches Waste
of
 me
and
money

Slide 7

Slide 7 text

TEST
PYRAMID (2012) h ps:/ /mar nfowler.com/bliki/TestPyramid.html

Slide 8

Slide 8 text

UNIT
TEST
=
GOOD? From
 
in
JUnit
5 @Test
void
launcherCanExecuteTestPlan()
{
 



TestEngine
engine
=
mock(TestEngine.class);
 



when(engine.getId()).thenReturn("some­engine");
 



when(engine.discover(any(),
any())).thenAnswer(invocation
­>
 







UniqueId
uniqueId
=
invocation.getArgument(1);
 







return
new
EngineDescriptor(uniqueId,
uniqueId.toString() 



});
 
 



var
launcher
=
createLauncher(engine);
 



TestPlan
testPlan
=
launcher.discover(request().build());
 



verify(engine,
times(1)).discover(any(),
any());
 
 



launcher.execute(testPlan);
 



verify(engine,
times(1)).execute(any());
 }
 DefaultLauncherTests.java

Slide 9

Slide 9 text

INTEGRATION
TEST
=
BAD? static
class
TestCase
{
 



@ParameterizedTest
@CsvSource({
"foo",
"bar"
})
 



void
testWithCsvSource(String
argument)
{
 







fail(argument);
 



}
 }
 @Test
 void
executesWithCsvSource()
{
 



var
results
=
execute("testWithCsvSource",
String.class);
 



results.testEvents().assertThatEvents()
 







.haveExactly(1,
event(displayName("[1]
argument=foo"),
 











finishedWithFailure(message("foo"))))
 







.haveExactly(1,
event(displayName("[2]
argument=bar"),
 











finishedWithFailure(message("bar"))));
 }


Slide 10

Slide 10 text

THE
CASE
AGAINST UNIT
TESTS Greg Rakozy

Slide 11

Slide 11 text

“MOST
UNIT
TESTING
IS
WASTE” “low
(even
poten ally
nega ve)
payoff” “increase
maintenance
liabili es
because
they
are less
resilient
against
code
changes” 
 
 h ps:/ /rbcs‑us.com/documents/Why‑Most‑Unit‑ Tes ng‑is‑Waste.pdf h ps:/ /blog.usejournal.com/lean‑tes ng‑or‑why‑unit‑ tests‑are‑worse‑than‑you‑think‑b6500139a009 h p:/ /250bpm.com/blog:40

Slide 12

Slide 12 text

“UNIT
TESTS
PASS
–
NO
INTEGRATION
TESTS”
MEMES

Slide 13

Slide 13 text

CONTROVERSIAL
OPINIONS? Write
tests.
Not
too
many.
Mostly integra on.
 —
Guillermo
Rauch
(@rauchg)
 December
10,
2016

Slide 14

Slide 14 text

TEST
PYRAMID,
THE
FINE
PRINT 
 2:
The
pyramid
is
based
on
the
assump on
that broad‑stack
tests
are
expensive,
slow,
and
bri le compared
to
more
focused
tests,
such
as
unit tests.
While
this
is
usually
true,
there
are excep ons.
If
my
high
level
tests
are
fast,
reliable, and
cheap
to
modify
‑
then
lower‑level
tests
aren’t needed.
 h ps:/ /mar nfowler.com/bliki/TestPyramid.html

Slide 15

Slide 15 text

THE
TESTING
TROPHY 

 h ps:/ /twi er.com/kentcdodds/status/960723172591992832

Slide 16

Slide 16 text

THE
TESTING
TROPHY as
you
move
up
the
pyramid,
the confidence
quo ent
of
each
form
of tes ng
increases our
tools
have
moved
beyond
the assump on
in
Mar n’s
original
Tes ng Pyramid
concept h ps:/ /kentcdodds.com/blog/write‑tests

Slide 17

Slide 17 text

MICROSERVICES
TESTING
HONEYCOMB h ps:/ /labs.spo fy.com/2018/01/11/tes ng‑of‑microservices/

Slide 18

Slide 18 text

MICROSERVICES
TESTING
HONEYCOMB “Integrated
Test”
=
a
test
that
will
pass
or
fail
based on
the
correctness
of
another
system. “Integra on
Test”
=
verify
the
correctness
of
our service
in
a
more
isolated
fashion
while
focusing
on the
interac on
points
and
making
them
very
explicit “Implementa on
Detail
Test”
=
“unit
test” h ps:/ /labs.spo fy.com/2018/01/11/tes ng‑of‑microservices/

Slide 19

Slide 19 text

INTEGRATION
TESTS
ARE
BAD! UNIT
TESTS
ARE
BAD! ⇒
ALL
TESTS
ARE
A
SCAM!?


Slide 20

Slide 20 text

TEST
DESIDERATA Dawid Zawiła

Slide 21

Slide 21 text

TEST
DESIDERATA Recent
blog
post
by
Kent
Beck, Video
series
(with
Kent
Beck
and
Kelly
Su on), 12
desirable
proper es
of
tests h ps:/ /medium.com/@kentbeck_7670/test‑ desiderata‑94150638a4b3 h ps:/ /www.youtube.com/playlist? list=PLlmVY7qtgT_lkbrk9iZNizp978mVzpBKl

Slide 22

Slide 22 text

12(!)
PROPERTIES? Not
all
tests
need
to
exhibit
all proper es.
However,
no
property
should be
given
up
without
receiving
a
property of
greater
value
in
return.

Slide 23

Slide 23 text

ISOLATED tests
should
return
the
same
results regardless
of
the
order
in
which
they
are run.

Slide 24

Slide 24 text

COMPOSABLE if
tests
are
isolated,
then
I
can
run
1
or 10
or
100
or
1,000,000
and
get
the same
results.

Slide 25

Slide 25 text

FAST tests
should
run
quickly.

Slide 26

Slide 26 text

INSPIRING passing
the
tests
should
inspire confidence

Slide 27

Slide 27 text

WRITABLE tests
should
be
cheap
to
write
rela ve
to the
cost
of
the
code
being
tested.

Slide 28

Slide 28 text

READABLE tests
should
be
comprehensible
for reader,
invoking
the
mo va on
for wri ng
this
par cular
test.

Slide 29

Slide 29 text

BEHAVIORAL tests
should
be
sensi ve
to
changes
in the
behavior
of
the
code
under
test.
If the
behavior
changes,
the
test
result should
change.

Slide 30

Slide 30 text

STRUCTURE-INSENSITIVE tests
should
not
change
their
result
if the
structure
of
the
code
changes.

Slide 31

Slide 31 text

AUTOMATED tests
should
run
without
human interven on.

Slide 32

Slide 32 text

SPECIFIC if
a
test
fails,
the
cause
of
the
failure should
be
obvious.

Slide 33

Slide 33 text

DETERMINISTIC if
nothing
changes,
the
test
result shouldn’t
change.

Slide 34

Slide 34 text

PREDICTIVE if
the
tests
all
pass,
then
the
code
under test
should
be
suitable
for
produc on.

Slide 35

Slide 35 text

POSSIBLE
RATING
(CONTEXT
DEPENDENT!) 


Slide 36

Slide 36 text

SO
WHAT? Look
at
the
last
test
you
wrote. Which
proper es
does
it
have? Which
does
it
lack? Is
that
the
tradeoff
you
want
to make?

Slide 37

Slide 37 text

5 CASE
STUDY:
JUNIT Thomas Lambert

Slide 38

Slide 38 text

TEST
MIX
IN
JUNIT Lots
of
unit
tests Lots
of
integra on
tests A
few
end‑to‑end
(“integrated”)
tests

Slide 39

Slide 39 text

A
SIMPLE
UNIT
TEST Structure‑insensi ve
↑,
Inspiring
↑,
Writable
↑, Fast
↑,
… @Test
 void
assertSameWithSameObject()
{
 



Object
foo
=
new
Object();
 



assertSame(foo,
foo);
 



assertSame(foo,
foo,
"message");
 



assertSame(foo,
foo,
()
­>
"message");
 }


Slide 40

Slide 40 text

A
UNIT
TEST
WITH
MOCKS Structure‑insensi ve
↑,
Inspiring
→,
Writable
→, Fast
↑,
… @Test
void
launcherCanExecuteTestPlan()
{
 



TestEngine
engine
=
mock(TestEngine.class);
 



when(engine.getId()).thenReturn("some­engine");
 



when(engine.discover(any(),
any())).thenAnswer(invocation
­>
 







UniqueId
uniqueId
=
invocation.getArgument(1);
 







return
new
EngineDescriptor(uniqueId,
uniqueId.toString() 



});
 
 



var
launcher
=
createLauncher(engine);
 



TestPlan
testPlan
=
launcher.discover(request().build());
 



verify(engine,
times(1)).discover(any(),
any());
 
 



launcher.execute(testPlan);
 



verify(engine,
times(1)).execute(any());
 }


Slide 41

Slide 41 text

ANOTHER
UNIT
TEST Structure‑insensi ve
↓,
Inspiring
→,
Writable
↑, Fast
↑,
… @Test
 void
providesMultipleArguments()
{
 



CsvSource
annotation
=
csvSource("foo",
"bar");
 
 



Stream
arguments
=
provideArguments(annotation);
 
 



assertThat(arguments)
 







.containsExactly(array("foo"),
array("bar"));
 }


Slide 42

Slide 42 text

A
TYPICAL
INTEGRATION
TEST Structure‑insensi ve
↑,
Inspiring
↑,
Writable
→, Fast
↑,
… static
class
TestCase
{
 



@ParameterizedTest
@CsvSource({
"foo",
"bar"
})
 



void
testWithCsvSource(String
argument)
{
 







fail(argument);
 



}
 }
 @Test
 void
executesWithCsvSource()
{
 



var
results
=
execute("testWithCsvSource",
String.class);
 



results.testEvents().assertThatEvents()
 







.haveExactly(1,
event(displayName("[1]
argument=foo"),
 











finishedWithFailure(message("foo"))))
 







.haveExactly(1,
event(displayName("[2]
argument=bar"),
 











finishedWithFailure(message("bar"))));
 }


Slide 43

Slide 43 text

END-TO-END
TESTS Structure‑insensi ve
↑,
Predic ve
↑,
Writable
↓, Fast
↓
… @Test
void
gradle_wrapper()
{
 



var
result
=
Request.builder()
 











.setTool(new
GradleWrapper(Paths.get("..")))
 











.setProject("gradle­starter")
 











.addArguments("build",
"­­no­daemon",
"­­debug",
"­­s 











.setTimeout(Duration.ofMinutes(2))
 











.setJavaHome(Helper.getJavaHome("8").orElseThrow(TestA 











.build()
 











.run();
 



assumeFalse(result.isTimedOut(),
()
­>
"tool
timed
out:
"
+
r 



assertEquals(0,
result.getExitCode());
 



assertTrue(result.getOutputLines("out").stream()
 







.anyMatch(line
­>
line.contains("BUILD
SUCCESSFUL")));
 



assertThat(result.getOutput("out")).contains("Using
Java
vers }


Slide 44

Slide 44 text

CASE
STUDY:
GRADLE Jennifer Latuperisa‑Andresen

Slide 45

Slide 45 text

TEST
MIX
IN
GRADLE Mostly
integra on/integrated
tests
(~50%) Some
unit
tests
(~35%) Cross‑version
tests Performance
tests

Slide 46

Slide 46 text

A
BAD
UNIT
TEST
 Readable
↓,
Structure‑insensi ve
↓,
Inspiring
↓, Fast
↑ def
"init
task
creates
project
with
all
defaults"()
{
 



given:
 



def
projectLayoutRegistry
=
Mock(ProjectLayoutSetupRegistry.class)
 



def
buildConverter
=
Mock(BuildConverter.class)
 



projectLayoutRegistry.buildConverter
>>
buildConverter
 



buildConverter.canApplyToCurrentDirectory()
>>
false
 



projectLayoutRegistry.default
>>
projectSetupDescriptor
 



projectLayoutRegistry.getLanguagesFor(ComponentType.BASIC)
>>
[Language.NONE 



projectLayoutRegistry.get(ComponentType.BASIC,
Language.NONE)
>>
projectSetu 



def
projectSetupDescriptor
=
Mock(BuildInitializer.class)
 



projectSetupDescriptor.componentType
>>
ComponentType.BASIC
 



projectSetupDescriptor.dsls
>>
[GROOVY]
 



projectSetupDescriptor.defaultDsl
>>
GROOVY
 



projectSetupDescriptor.testFrameworks
>>
[NONE]
 



projectSetupDescriptor.defaultTestFramework
>>
NONE
 



projectSetupDescriptor.furtherReading
>>
Optional.empty()
 
 



when:
 



def
init
=
TestUtil.create(testDir).task(InitBuild)
 init setupProjectLayout()

Slide 47

Slide 47 text

A
GOOD
INTEGRATION
TEST
 Readable
↑,
Structure‑insensi ve
↑,
Inspiring
↑, Fast
→ def
targetDir
=
testDirectory.createDir("some­thing")
 def
"creates
valid
sample
sources
if
no
sources
are
present"()
{
 



when:
 



executer.inDirectory(targetDir)
 







.succeeds('init',
'­­type',
'groovy­library')
 



then:
 



targetDir.file("src/main/groovy")
 







.assertHasDescendants("some/thing/Library.groovy")
 



targetDir.file("src/test/groovy")
 







.assertHasDescendants("some/thing/LibraryTest.groovy")
 



when:
 



run("build")
 



then:
 



assertTestPassed("some.thing.LibraryTest",
"someLibraryMethod
returns
true") }


Slide 48

Slide 48 text

TEST
FIXTURES In
order
to
write
such
readable
integra on
tests,
a
lot of
suppor ng
code
has
to
be
in
place.

Slide 49

Slide 49 text

CHALLENGES Speed:
cri cally
important
for
feedback
loop
and
CI build
 mes Flakiness:
the
more
parts
involved,
the
higher
the chance
of
“random”
failures

Slide 50

Slide 50 text

SPEED Two
modes: embedded
–
runs
Gradle
in‑process
for
fast
local development
and
easy
debugging forking
–
calls
Gradle
like
it
would
be
called
by users CI
only
runs
tests
for
projects
affected
by
changes, other
results
are
loaded
from
build
cache

Slide 51

Slide 51 text

FLAKINESS Failing
tests
are
automa cally
rerun
on
CI If
the
second
run
passes build
is
marked
as
successful GitHub
project
is
checked
whether
the
test
is known
to
be
flaky Otherwise,
an
issue
to
fix
the
test
is
created

Slide 52

Slide 52 text

LESSONS
LEARNED

Slide 53

Slide 53 text

“INTEGRATION
TESTS
ARE
TOO
SLOW!” Profile
them! Is
it
just
the
tests? Or
is
it
actually
your
applica on?

Slide 54

Slide 54 text

“WRITING
INTEGRATION
TESTS
IS
TOO HARD!” Use
the
right
tools! WireMock
/
MockServer
for
HTTP
calls TestContainers
for
databases
etc. … See
 
for
details
on
all
of
the above
and
more. talk
of
Sandra
Parsick

Slide 55

Slide 55 text

“TESTING
ASYNCHRONOUS
CODE
IS
TOO HARD!” Don’t
ever
use
sleep! Use
 Spock’s
PollingConditions CompletableFuture Awai lity

Slide 56

Slide 56 text

“DON’T
MOCK
THINGS
YOU
DON’T
OWN!” Unless
there’s
no
other
way.

Slide 57

Slide 57 text

“DON’T
MOCK
THINGS!” Unless
that’s
the
best
way
to
test
it.

Slide 58

Slide 58 text

“THE
TEST
PYRAMID
IS
ALWAYS
RIGHT!” The
test
pyramid
is
not
the
right
strategy
for
everyone.

Slide 59

Slide 59 text

WHICH
TESTS
SHOULD
I
WRITE? Use
criteria
like
Kent
Beck’s
test
desiderata
to
make
a conscious
decision
about
which
tests
help
you
in
being produc ve. Be
aware
of
the
trade‑offs!

Slide 60

Slide 60 text

THANKS! Marc
Philipp
 
 @marcphilipp
on
 / mail@marcphilipp.de GitHub Twi er