10 ways to shoot yourself in the foot with tests - Shai Geva, PyCon IL, 2024

ShaiGeva1 62 views 121 slides Sep 16, 2024
Slide 1
Slide 1 of 121
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
Slide 77
77
Slide 78
78
Slide 79
79
Slide 80
80
Slide 81
81
Slide 82
82
Slide 83
83
Slide 84
84
Slide 85
85
Slide 86
86
Slide 87
87
Slide 88
88
Slide 89
89
Slide 90
90
Slide 91
91
Slide 92
92
Slide 93
93
Slide 94
94
Slide 95
95
Slide 96
96
Slide 97
97
Slide 98
98
Slide 99
99
Slide 100
100
Slide 101
101
Slide 102
102
Slide 103
103
Slide 104
104
Slide 105
105
Slide 106
106
Slide 107
107
Slide 108
108
Slide 109
109
Slide 110
110
Slide 111
111
Slide 112
112
Slide 113
113
Slide 114
114
Slide 115
115
Slide 116
116
Slide 117
117
Slide 118
118
Slide 119
119
Slide 120
120
Slide 121
121

About This Presentation

This document discusses 10 ways that tests can "shoot yourself in the foot" by being poorly designed or implemented. It covers issues like having no tests, unclear tests, tests that are not isolated, improper test scope, overuse of test doubles, and tests that are too slow. The key lessons...


Slide Content

TESTING
FOOTGUNS;
10 WAYS TO SHOOT YOURSELF IN THE FOOT
WITH TESTS
Shai Geva | Tech Lead @Sayata | @shai_ge

Shai Geva
~20 years making software
Love testing
Tech Lead, Sayata

Return
On
Investment

Tests
PROPERTIES

STRENGTH
Tests
PROPERTIES

STRENGTH
MAINTAINABILITY
Tests
PROPERTIES

PERFORMANCE
STRENGTH
MAINTAINABILITY
Tests
PROPERTIES

THERE ARE
NO TESTS
1

UNTESTED TESTS
2

3
THE TESTS ARE
NOT ISOLATED

4NO
LOCALITY
BEHAVIOR
OF

def test_something():
data = Path(PATH_TO_DATA_FILE).read_text()
assert calc_something(data) == 4.5

def test_something():
data = “””
{
<JSON data>
}
“””
assert calc_something(data) == 4.5

5 UNCLEAR LANGUAGE
test_ (): ...
test_ (): ...

GUIDELINES

GUIDELINES
DECISIVE
LANGUAGE

GUIDELINES
DECISIVE
LANGUAGE
SPECIFIC
EXPLICIT

BookStore

test_edit_book():

test_edit_book_works_correctly():

test_edit_book_works_correctly():

test_user_should_be_able_to_edit_their_own_book():

test_user_should_be_able_to_edit_their_own_book():

test_user_can_edit_their_own_book():

6TESTING
TOO
MANY
THINGS

SINGLE
FACT
BEHAVIOR

BookStore

test_user_can_edit_their_own_book

test_user_can_edit_their_own_book
test_edit_book

7
IMPROPER
TEST SCOPE

COMPLETE STORY

COMPLETE STORY
COHESIVE WHOLE

COMPLETE STORY
COHESIVE WHOLE
BEHAVIOR / IMPLEMENTATION

Book Store MySQL

BEHAVIOR TEST IMPLEMENTATION TEST

BEHAVIOR TEST IMPLEMENTATION TEST
WITH BUG
WITHOUT BUG

BEHAVIOR TEST IMPLEMENTATION TEST
WITH BUG
WITHOUT BUG

def test_editing_description_sets_correct_value():
BEHAVIOR TEST IMPLEMENTATION TEST

BEHAVIOR TEST IMPLEMENTATION TEST
# Create book
# Edit book
# Get updated description
# Assert correctness
# Create book
# Edit book
# Get updated description
# Assert correctness
def test_editing_description_sets_correct_value():

# Create book - API
requests.post(...
# Edit book - API
requests.post(...
# Get updated description - API
new_desc = requests.get(…
# Assert correctness
assert new_desc == ...
# Create book - DB
# Edit book - API
# Get updated description - DB
# Assert correctness
BEHAVIOR TEST IMPLEMENTATION TEST
def test_editing_description_sets_correct_value():

# Create book - DB
DbBook.creat_new(...
# Edit book - API
requests.post(...
# Get updated description - DB
new_desc = DbBook.query_one(…
# Assert correctness
assert new_desc == ...
BEHAVIOR TEST IMPLEMENTATION TEST
# Create book - API
requests.post(...
# Edit book - API
requests.post(...
# Get updated description - API
new_desc = requests.get(…
# Assert correctness
assert new_desc == ...
def test_editing_description_sets_correct_value():

BEHAVIOR TEST IMPLEMENTATION TEST
# Create book - DB
DbBook.creat_new(...
# Edit book - API
requests.post(...
# Get updated description - DB
new_desc = DbBook.query_one(…
# Assert correctness
assert new_desc == ...
# Create book - API
requests.post(...
# Edit book - API
requests.post(...
# Get updated description - API
new_desc = requests.get(…
# Assert correctness
assert new_desc == ...
def test_editing_description_sets_correct_value():WHAT
COHESIVE HOW
INCOHESIVE

OUR CODE CHANGE

BOOK TABLE
DESCRIPTION DESC_ID ID
DESCRIPTION TABLE
VALUE ID
BOOK TABLE
DESCRIPTION ID …

BOOK TABLE
DESCRIPTION ID …
BOOK TABLE
DESCRIPTION DESC_ID ID
DESCRIPTION TABLE
VALUE ID

BOOK TABLE
DESCRIPTION DESC_ID ID
DESCRIPTION TABLE
VALUE ID
Book Store
/edit-book
/new-book
/get-book

BOOK TABLE
DESCRIPTION DESC_ID ID
DESCRIPTION TABLE
VALUE ID
Book Store
/edit-book
/new-book
/get-book
BEHAVIOR
TEST

BOOK TABLE
DESCRIPTION DESC_ID ID
DESCRIPTION TABLE
VALUE ID
Book Store
/edit-book
/new-book
/get-book
BEHAVIOR
TEST

BOOK TABLE
DESCRIPTION DESC_ID ID
DESCRIPTION TABLE
VALUE ID
Book Store
/edit-book
/new-book
/get-book
BEHAVIOR
TEST

BOOK TABLE
DESCRIPTION DESC_ID ID
DESCRIPTION TABLE
VALUE ID
Book Store
/edit-book
/new-book
/get-book
IMPLEMENTATION
TEST

BOOK TABLE
DESCRIPTION DESC_ID ID
DESCRIPTION TABLE
VALUE ID
Book Store
/edit-book
/new-book
/get-book
IMPLEMENTATION
TEST

BOOK TABLE
DESCRIPTION DESC_ID ID
DESCRIPTION TABLE
VALUE ID
Book Store
/edit-book
/new-book
/get-book
IMPLEMENTATION
TEST

BOOK TABLE
DESCRIPTION DESC_ID ID
DESCRIPTION TABLE
VALUE ID
Book Store
/edit-book
/new-book
/get-book

BOOK TABLE
DESCRIPTION DESC_ID ID
DESCRIPTION TABLE
VALUE ID
Book Store
/edit-book
/new-book
/get-book
BEHAVIOR
TEST

BOOK TABLE
DESCRIPTION DESC_ID ID
DESCRIPTION TABLE
VALUE ID
Book Store
/edit-book
/new-book
/get-book
IMPLEMENTATION
TEST

BOOK TABLE
DESCRIPTION DESC_ID ID
DESCRIPTION TABLE
VALUE ID
Book Store
/edit-book
/new-book
/get-book
IMPLEMENTATION
TEST

COHESIVE
BEHAVIOR
INCOHESIVE
IMPLEMENTATION

COHESIVE
BEHAVIOR
INCOHESIVE
IMPLEMENTATION
PASSING == WORKS?

COHESIVE
BEHAVIOR
INCOHESIVE
IMPLEMENTATION
PASSING == WORKS?
CATCHES BUGS
AS EXPECTED?

COHESIVE
BEHAVIOR
INCOHESIVE
IMPLEMENTATION
PASSING == WORKS?
CATCHES BUGS
AS EXPECTED?
ONLY NECESSARY WORK?

COHESIVE
BEHAVIOR
INCOHESIVE
IMPLEMENTATION
PASSING == WORKS?
CATCHES BUGS
AS EXPECTED?
ONLY NECESSARY WORK?
HIGH CONFIDENCE?

SCARY
CHANGES
DRAMATIC
DIFFERENCE

BEHAVIOR
COHESIVE

8TEST
DOUBLES
EVERW
E
ERYH

TEST DOUBLES == IMPLEMENTATION

REAL
TEST
DOUBLE

REAL
TEST
DOUBLE

REAL
TEST
DOUBLE

REAL
TEST
DOUBLE

REAL
TEST
DOUBLE

USE WITHCAUTION

USE WITH
BUT
CAUTION
HOW?

DESIGN

MySQL db_fake = []

TEST
THE FAKE

WHY NOT BOTH?

MySQL
service db_fake
service

service
MySQL
db_fake
service

service
MySQL
db_fake
service

USE, AND VERIFY

Footgun #: the tests are slow
SLOW
TESTS
9

SLOW TESTS #1:
THE BOTTLENECK
AND THE TIME BOMB

SLOW TESTS: THE BOTTLENECK AND THE TIME BOMB

>>> workday_hours = 10

>>> workday_hours = 10
>>> test_run_hours = 2

>>> workday_hours = 10
>>> test_run_hours = 2
>>> test_run_per_day = 10 / 2
5

>>> workday_hours = 10
>>> test_run_hours = 2
>>> test_run_per_day = 10 / 2
5
>>> if num_finished_tasks_today > 5:

>>> workday_hours = 10
>>> test_run_hours = 2
>>> test_run_per_day = 10 / 2
5
>>> if num_finished_tasks_today > 5:

>>> workday_hours = 10
>>> test_run_hours = 2
>>> test_run_per_day = 10 / 2
5

>>> max_reasonable_test_run_min
10
>>> max_worst_case_test_run_min
15

DEFUSE THE BOMBHOW TO

DEFUSE THE BOMB
PREPARED TO OPTIMIZE
HOW TO
BE

DEFUSE THE BOMB
PREPARED TO OPTIMIZE
CAN RUN IN PARALLEL
HOW TO
BE
TESTS

DEFUSE THE BOMB
PREPARED TO OPTIMIZE
CAN RUN IN PARALLEL
ISOLATED TESTS
HOW TO
BE
TESTS
WRITE

SLOW TESTS 2#:
THE FEEDBACK LOOP AND
THE BUG FUNNEL

FAST = 3 seconds, watch

FAST = 3 seconds, watch
SLOW = 10 minutes, CI

BE FAST
IN PRACTICE

HOW LONG FOR THE TESTS TO RUN?
HOW LONG TO CATCH A BUG?

ALL BUGS
FASTEST: e.g. < 10 sec
EVEN SLOWER: CI, 2 min
REALLY SLOW: CI, 15 min
LESS FAST: < 1 min

ALL BUGS
CI END-TO-END

ALL BUGS (10)
10
CI END-TO-END

ALL BUGS (10)
2
2
4UTs FOR THIS MODULE
CI UTs
CI END-TO-END
ALL LOCAL UTs 2

10
WRONG
PRIORITIES

Tests
PROPERTIES
PERFORMANCE
STRENGTH
MAINTAINABILITY

STRENGTH

PERFORMANCE
STRENGTH
MAINTAINABILITY

WHY?

SLOW

SLOW
TOO
LONG

SLOW
TOO
LONG
FEWER
TESTS

SLOW
TOO
LONG
FEWER
TESTS
WEAK

HARD TO
MAINTAIN
EXPENSIVE
FEWER
TESTS
WEAK

PERFORMANCE
STRENGTH
MAINTAINABILITY

SUMMING UP

TOOLS:
>>> pytest-watch
Re-run tests on file changes
>>> pytest-testmon
Only run tests that can be impacted
by the code that changed
>>> pytest-xdist
Run tests in parallel
>>> pytest-env, pytest-dotenv
Deal with env vars
>>> unittest subTest Organizing test output
when you have large tests.
For pytest: pytest-subtests
>>> factory-boy, faker
Generate random test data
>>> hypothesis !
Property-based testing - for getting strong
tests.
>>> coverage.py
Python test-coverage
>>> vcrpy
Record HTTP requests
Slides:
bit.ly/testing_footguns_pycon_il_2024

1.There are no tests
2.Untested tests
3.No Locality of Behavior
4.Unclear language
5.Testing too many things
6.The tests are not isolated
7.Improper scope
8.Test doubles
9.Slow tests
10.Wrong priorities

Shai Geva
Slides:
bit.ly/testing_footguns_pycon_il_2024
QUESTIONS?
@shai_ge