The Joy of Testing - Deep Dive @ Devoxx Belgium 2024

VictorRentea 687 views 76 slides Oct 08, 2024
Slide 1
Slide 1 of 76
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25
Slide 26
26
Slide 27
27
Slide 28
28
Slide 29
29
Slide 30
30
Slide 31
31
Slide 32
32
Slide 33
33
Slide 34
34
Slide 35
35
Slide 36
36
Slide 37
37
Slide 38
38
Slide 39
39
Slide 40
40
Slide 41
41
Slide 42
42
Slide 43
43
Slide 44
44
Slide 45
45
Slide 46
46
Slide 47
47
Slide 48
48
Slide 49
49
Slide 50
50
Slide 51
51
Slide 52
52
Slide 53
53
Slide 54
54
Slide 55
55
Slide 56
56
Slide 57
57
Slide 58
58
Slide 59
59
Slide 60
60
Slide 61
61
Slide 62
62
Slide 63
63
Slide 64
64
Slide 65
65
Slide 66
66
Slide 67
67
Slide 68
68
Slide 69
69
Slide 70
70
Slide 71
71
Slide 72
72
Slide 73
73
Slide 74
74
Slide 75
75
Slide 76
76

About This Presentation

We spend half of our coding time reading, writing or fixing tests. But who likes that? To quickly fix this chore, we often copy-cat older tests or write new ones without paying much attention to their quality. This deep-dive walks you through a variety of practical testing techniques and tricks you ...


Slide Content

The Joy of Tes*ng
3-hours Deep Dive at
by Victor Rentea
Code is here: h+ps://github.com/victorrentea/unit-tes9ng.git branch: devoxx24

UT 3
- Evan Spiegel
Ever had a great
refactoring idea
but you were afraid
to do it?

UT 4
Prove it works today, including edge cases
Fast Feedback to preserve focus: Start Clicking => Stop Thinking!
Refactor without fear tomorrow, be crea7ve, if func'onal-tests(elseJ)
Up-to-date Spec running on CI, if func'onal tests
Clarify requirements, if test-early
Design feedback: Hard to test ≈> poor design, if test-early
For coverage%, to get my PR approved "; Trap: Coverage Rush⚠
Why Write Tests?

"
#
$
%

%

! I'm Victor Rentea " Java Champion, PhD(CS)
# 18 years in Java + most languages
$ 10 years of Training / 150+ companies / 20+ countries:
- Architecture, Refactoring, Unit Tes2ng (today)
- Spring, JPA, Performance, ReacIve
European SoKware CraKers Community (on meetup.com)
YouTube.com/vrentea - events% + conference&
Life += ' + ( + ) + 2**victorrentea.ro
I'm drawing on screen with:
> ScreenBrush (Mac)
> ZoomIt (Win)

UT 9!
Quali&es of a Unit Test

UT 10
§Sensible: detects changes in behavior
§Func7onal, not coupled to implementaEon (resilient to refactoring)
§Specific: fails for one clear reason
§Expressive: uses domain terms with a high signal/raEo raEo
§Automated: easy to run by anyone with no/minimal manual setup
§Repeatable: produces the same outcome if rerun
§Isolated vs other tests / external system state
§Minimal: includes only relevant stuff (#responsible copy-paste)
§Fast, total build Eme < 15-20 min
Quali0es of a Test
from the book "The Art of Unit Tes.ng"by Roy Osherove

UT 12Coverage

UT 13
Coverage Rush
Management Goal: Coverage > xx%'vs (Confused Developers or ☠Legacy
The more code in here,
the more untested code
can be in the project.!
Example of Cobra Effect
5000 lines

UT 14
Coverage %
Cost
Coverage%100%80%90%60%
= covrigi [RO]
cover critical code first
! Higher goals for 'domain' package
Minimum Coverage%

UT 15
Line vs Branch Coverage
§Line Coverage = did my tests executed a line?
§Branch Coverage = did my tests exercised a path? ! ! !
-if (condition) è 50% = means condi4on was only true (or false) during tests
-condition ? expr1 : expr2
-a && b || c è 100% branch coverage = a, b, c were all false + true in tests
-Measured via a -javaagent: that
monitors code executed by tests
IntelliJ setup

UT 16
tl;dr
Use Branch Coverage

UT 17
Professionals use coverage analysis
to hunt for UNCOVERED code,
NOT to celebrate COVERED lines

UT 18
Talk is cheap
Show the me the code_
Code is here: h+ps://github.com/victorrentea/unit-tes9ng.git branch: devoxx24

UT 19
A new instance of the test class is created for each @Test.
So it's ok to leave dirty data in test class' instance fields.
#simplicity
#1 Basic People don't know about JUnit!

UT 20
Muta%on Tes%ng

UT 21
All tests are green. "#
You break some behavior,
but all tests stay Green
!
Evergreen Tests
Imagine 95% coverage...

UT 22
Evergreen Tests
Coverage% = ?
Oups!!
100%
If all tested code is deleted,
the test remains GREEN *
Da-i asa! vs Basculanta
!
lies to you

UT 23
§Prod coverage of class X.java = 87% !
§XTest.java = 2000-lines
§67 x @Mock
§200 x Mockito.when "
including when(aMock.getB()).thenReturn(bMock);
§0 x "assert" #
§0 x "verify" #
Cobra ! Effect

UT 24
The only way to prove
a test is correct?
tl;dr
it FAILS if (and only if)
you BREAK the tested behavior

UT 25
Muta3on Tes3ng
You inten1onally
introduce a bug in
Produc@on CodeYou get a
MUTANT
Tests should FAIL +
(proving they would catch that bug)
Run Tests
Don't forget⚠ to
Undo the bug
Correct tests fail<=>Produc@on Code is altered
The only way to prove
a test is correct?

UT 26
§Alters tested bytecode and checks tests fail
-Using a set of mutators, like "Negate Condi@onals"
§But⚠:
-Can report false posi@ves (non-issues)
-Takes long @me to run if applied on lots of code
§Use it:
-To learn
-On super-cri@cal code (target a single package)
Muta3on Tes3ngwith pitest

UT 27!"
Ever generated a test with AI (LLM)?
, Dream: Gen-AI tests (chaos) but then check them via muta@on-tes@ng (exact)
Lower entry-cost when tes@ng legacy -

UT 28
Check that every new test you write
fails for a bug in tested logic.
(hands-on if not obvious)

UT 30Bug-Tests

UT 31

UT 32
Breakpoint
using step-into, inspect,
evaluate, watch variable...
✅ Use for a 1st explora4on

UT 33
Breakpoint-debugging an issue is an<social behavior
Others entering that code
tomorrow will suffer too.
requires deep focus . and
drains lots of brain energy /
Breakpoint

UT 35
Fix a bug like a Pro
1.Reproduce the bug [twice]
2.Write a test for it è FAILS❌
(⚠can be very-very hard in complex, terrible legacy code)
3.log.debug("..{}", var) instead of breakpoint
4.Find the cause > Kill bug > Test è GREEN✅
Avoid vola1le
breakpoint sessions
You can later lower the log level in producIon without restart#:
using: JMX, Spring Actuator, or Logback config auto-reload to inves9gate unreproducible bugs

UT 36
Why Bother?
Bugs S7ck Together

UT 37
refactoring + tes4ng merge
in "islands of safety"
Test Around Bugs
bug
unit-tests around it
Tes4ng another nearby bug
becomes easier tomorrow
Bugs S1ck Together
Unit-tes4ng legacy code
requires some prep-cleaning

UT 38 h+p://googletes9ng.blogspot.com
https://drive.google.com/open?id=1YQNHvc0NIzspxKHG4BU9mjdOqDQdks9z

UT 40
Bugs in Tests

UT 41
A Bug in a Test
assertEquals("Greetings" + name, logic.getGreetings(name));
production codeit's missing a " "!
The developer copied the logic from produc@on to tests0
You want Explicit Tests
assertEquals("Greetings John", logic.getGreetings("John"));
Find the BUG !

UT 42
Tests should be Simple and Expressive,
NOT cryp4c, smarty, and hard to read

UT 43
Unmaintainable Tests
Can make developers :
Anger Zone
delete test
@Disable
// comment test
// @Test
delete asserts
flip asserts to pass

UT 45Test Name

UT 47
Test Name
void givenAnInactiveCustomer_whenHePlacesAnOrder_itIsActivated()
void givenAProgramLinkedTo2Records_whenUpdated_2AuditsAreInserted()
GivenThenWhen
!

UT 48
AnInactiveCustomerShould
.beActivatedWhenHePlacesAnOrder()
Brain-Friendly Test Names
Fluent naming
hFps://codurance.com/2014/12/13/naming-test-classes-and-methods/
"Given"
the class name explains
the context shared by testsTest
"Then"
start with the expected effect
to capture the reader a+en9on
"When"
... then describe in what condi9on it occurs

UT 49AssertJ

UT 50
assertEquals
Anyone?

UT 51
assertEquals
Never use again
Use assertThat(from AssertJ)

UT 54
§A few fields
assertThat(x.getA()).isEqualTo(y.getA());
assertThat(x.getB()).isEqualTo(y.getB());
§Almost all fields
assertThat(x)
.usingReflective(ignoringFields("a")).isEqualTo(y);
§All fields
assertThat(toPrettyJson(x))
.isEqualTo(toPrettyJson(y));
How to compare data structures?

UT 58
Parameterized
Tests

UT 59
Parameterized Tests – Best Prac&ces
§Orthogonal parameters - best (low mul4collinearity)
Example (for 3 input params):
-Good: a={1,2,3}, b={7,8}, c={T,F} = 3x2x2 = 12 validcombina4ons with 3 params
-Bad if only 3 valid combina4ons: (1,7,true); (1,8,false); (2,8,true)
§Few parameters < 3-5
-More => group them in a class/record with named fields
-Complex input structure * many tests => consider file-based tests
§Avoid boolean-riddled test
-if (param1) excep4on was thrown
if (param2) when(mock)..
if (param3) verify(mock)..

UT 60
Tes0ng StylesMaintainability
§Output-Based
-Assert the returned value
-(enough when tested code has no side effects, pure func4on")
§State-Based
-Assert fields of tested stateful object
-Assert fields of collaborator objects (eg. passed as params#)
-Assert external state in DB/Files
§CommunicaEon-Based (mocks)
-Verify interac4on with collaborators

UT 61
Approval Tests

UT 63
When you can't an4cipate the impact of a change.
§Capture all output of a component in a human readable file
-csv, json, xml, txt
§Change the component behavior.
§Diff 1the new output with the old output saved in files
§If correct => save the new output in the old file = "approve"
Approval Tes3ng

UT 64
Tes2ng Legacy

UT 65
1000
lines of code
in a single file
00

UT 67
Characterization Tests
§Dark Techniques to write temporary, ugly tests
-Break encapsula4on of tested code (private -> public)
-Par4al mock (@Spy) internal methods
-Mock sta4cs & control global state
§Pre-tesEng refactoring (unguarded by tests)
-Exploratory refactoring (eg 30-minute > undo!): look for seams ("fracture planes")
-Tiny, safe baby-steps refactoring with IDE, pairing (2) or mobbing (N)
§Extract and test code with a clear role = maintainable tests!
§CharacterizaEon Tests: capture input/output as a black-box
Legacy Code Tes&ng Prac&ces

UT 68
Characterization Tests
Some legacy code with no tests runs in production for many years.
Therefore, we can assume it is correct. = it became its own spec
But we need to refactor that code. # # # #
Our Goal = preserve its behaviour (correct or not), not to find bugs.
Step 1. Identify & capture the inputs/outputs of the prod code (HARD),
example IN= params, results of DB queries/API call, time/random/UUID!"
OUT= returned value, DB insert/update, API calls, sent messages!"
Step 2. Save {inputs + recorded outputs} to files.
Step 3. Identify many different inputs to cover all execution paths
Get line coverage close to 100%; ± manual mutation testing.
PRO$: Record IN/OUT from production = "Golden Master" Technique
(also highlights dead☠ code)
Step 4. Refactor guarded by these tests. When done, remove tests?

UT 69
Shadow TesFng
Prod env
Staging env
Clientstraffic
traffic


copy




compare
results

UT 74BDD
Libraries:
Cucumber
JBehave
BeHat (php)
SpecFlow (C#)
...Behavior-Driven-Development
(aka Acceptance-Test-Driven-Development)

UT 75
Have I understood correctly
what we need to build?

UT 76
Bridge the Gap
Make sure you implement the right thing
Gherkin Language can help

UT 78
Gherkin Language
PO
Business
// "glue" code can: configure mocks, DB, WireMock, set
fields, load files ...
@When("Player{int} scores {int} points")
public void playerScores (int player, int points) {
for (int i = 0; i < points; i++) {
tennisScore.addPoint(player);
}
}
@Then("Score is {string}")
public void score_is(String expected) throws Throwable {
assertThat(tennisScore.score()).isEqualTo(expected);
}
Cucumber

UT 80
Behavior-Driven Development
Before star;ng the development,
developers + testers + domain experts (BA/PO)
write together 2 a series of .feature files that capture
(developer = typist, having sketched a few draV scenarios beforehand)
the acceptance criteria of the stories to develop.
When these .features go green✅ => develop = done-
Later, each change request starts with edi;ng .feature files

UT 81
! Developers' Dream "
Business people will maintain the .features
. It never works.
✅ Works: Pair on them (Dev✍ + Biz0)
✅ Works: Automa;on-tester

UT 82
When should you use a .feature?
✅ Hesita2ng/conflic2ng requirements (CYA principle)
✅ Complex rules, to beQer understand via more examples
✅ Business-cri2cal features to avoid expen$ive confusions
✅ Table-like test data (Excel-style) => scenario outline
❌ Business disengages: trivial or too technical features?

UT 83
Gherkin Best Prac0ces
§Use Ubiquitous Language understood by tech and !tech: PO, BA, QA
§Focus on behaviour (aka "FuncEonal Tests"), hide details behind steps
§Keep steps implementa7on simple; expose relevant parameters
§UI Scenarios (Selenium) can be fragile and slow⚠ => fire at API-level
§DRY: use shared steps, Background:, Scenario Outline, and @tags
§Mock external services for faster and more reliable tests
§Share state between step classes via dep. injecEon: Spring, Guice..
§Prevent state leak: cleanup test environment a]er each scenario
§Version control .features along code
§Runs on CI ⚠ (even if slow)

UT 84
WASTE
Maintaining .feature files
that no businessperson approves

UT 85
When only developers
work with Gherkin files
consider building a test DSL instead.
WriVen in the same language (java, C#...),
or in a denser one: kt, py, groovy

UT 86
Test Helpers
Tes<ng Framework
Test DSL

UT 88
Test Domain-Specific Language
= a fluent mini-tes@ng framework:
Click to see implementa9on

UT 89 http://spockframework.org/spock/docs/1.3/spock_primer.html
Spock tests (.groovy)

UT 90
Test DSL in Kotlin
https://medium.com/porterbuddy/simple-test-dsls-in-kotlin-c6393035a92

UT 91
Fine-Grained Tests
Complex Mocking
Fragile vs Refactoring
Focus on implementa;on
Longer Tests
Require Test Helpers
Robust
Close to spec

UT 92
JUnit4 Rules
JUnit 5 Extensions
Cucumber Tags
.py decorators
...
Test AOP
Invisible code runs before and a\er your tests
!

UT 93
Hierarchical
Test Fixtures

UT 94
Not all long tests are like this

UT 95
Hierarchical Test Fixtures
Shared fixture for
all tests on this flow
Addi4onal fixture
for some branches
= context
@Nested classes
Test Subclasses
Test AOP

UT 96
An Inac4ve Customer
… with an order
… with no orders
Hierarchical Fixtures
(@Nested in JUnit5)
Nested Test Fixtures in JUnit5: https://junit.org/junit5/docs/current/user-guide/#writing-tests-nested

UT 97
An Inac4ve Customer
@Nested With an order
@Nested With no orders
An Inac4ve Customer
With an order
… with no orders
@Before
Hierarchical Fixtures
@Before
@Before
@Before
@Before
JUnit5 @NestedExtending a test class

UT 98
The Joy of Testing – key points
#1 section
- Why do we write Tests?
- Coverage% ▶ CustomerValidatorTest
- Test Names
- ObjectMother: in-mem vs parse.json
- Mutation Testing
- Bug-testing
- assertThat (AssertJ) ▶
- Test AOP with JUnit Extensions ▶
#2 section
- Parameterized Tests ▶ TennisScoreTest
- Approval Tests (by text inspection%) ▶
- Characterization Tests (for legacy code)
- Gherkin for Functional Tests ▶
- Hierarchical Test Fixtures ▶ CustomerFacade
- Test Qualities

UT 99
The Joy of Tes*ng
Please share with others
Code is here: h+ps://github.com/victorrentea/unit-tes9ng.git branch: devoxx24
Thank you!

UT 100
The Joy of Tes*ng
3 Share your thoughts: stalk me or at [email protected]
4 Test-Driven Design Insights, 3h Deep Dive at Devoxx 20234 Tes1ng Microservices: Join the Revolu1on, at Devoxx 20234 YouTube.com/vrentea
5 Private training ± consultancy: victorrentea.ro/catalog
What Next?
% Free monthly webinars: victorrentea.ro/community
Code is here: h+ps://github.com/victorrentea/unit-tes9ng.git branch: devoxx24