Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Produktive Tests (German)

Produktive Tests (German)

Die Testpyramide von Martin Fowler rät uns, möglichst wenige integrative Tests zu schreiben, da sie langsam und teuer sind. J.B. Rainsberger spricht gar von (Selbst-)betrug. Sind integrative Tests also „böse” und Unit Tests „gut”? Neuere Stimmen behaupten das Gegenteil: Unit Tests sind reine Zeit- und Geldverschwendung und behindern uns mehr, als sie uns helfen. Sollten wir also lieber keine Unit Tests schreiben und dafür umso mehr integrative Tests? Die Antwort ist, wie so oft: Es kommt darauf an. Anhand von Kriterien (Test Desiderata), die Kent Beck vor kurzem in seinem Blog aufgelistet hat, werden wir Tests aus den Open-Source-Projekten JUnit und Gradle betrachten und uns fragen: Welche Kriterien erfüllen sie und welche nicht? Wann helfen uns Unit Tests? Sind integrative Tests wirklich immer langsam und unzuverlässig? Wie schreibt man gute integrative Tests? Und wie schafft man es, sie dauerhaft stabil zu halten?

Marc Philipp

January 15, 2020
Tweet

More Decks by Marc Philipp

Other Decks in Programming

Transcript

  1. 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
  2. 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"))));
 }

  3. “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
  4. 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"))));
 }

  5. 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 }

  6. 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()
  7. 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") }