-
Notifications
You must be signed in to change notification settings - Fork 0
/
writing-tests.slide
267 lines (149 loc) · 5.96 KB
/
writing-tests.slide
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
Writing tests in Go
13 August 2016
Kaviraj Kanagaraj
Aircto ( A Product by Launchyard)
kaviraj@launchyard.com
https://kaviraj.me
@kvrajk
* Agenda
- Basics
- Understanding white-box and black-box testing
- Real-time problems in testing
- Writing Testable Code
- Testing tricky parts
- Conclusion
- Resources
* Basics
* Go built-in testing framework
Entire testing framework built on top of _two_ components.
- *go* *test* command
- *testing* package
* What go test tool do?
- Scans the `*_test.go` files for special functions (functions that start with `Test`, `Benchmark` and `Example`)
- Generates a temporary main package
- Builds and runs it
- Report the results
- And finally Cleans up.
* Types of test functions
- Common tests
func TestCopyBuffer(t *testing.T) {...}
- Benchmark tests
func BenchmarkTrimSpace(b *testing.B) {...}
- Example tests
func ExampleTrimPrefix(){...}
* Simple Problem - Reverse a given string
.play -edit writing-tests/reverse/reverse.go
* Common tests (1/3)
.code -edit writing-tests/reverse/reverse_test.go /^func TestReverse/,/^}/
* Common tests (2/3)
.code -edit writing-tests/reverse/reverse_test.go /^func TestReverseUnicode/,/^}/
* Common tests (3/3)
*testing.T*
- t.Error()
- t.Fatal()
- t.Skip()
- t.Parallel()
* Benchmark tests
- Used to measure the performance of a program on a fixed workload.
- By default no benchmark tests are run. include *-bench=.* to run all the benchmark tests in the package
.code writing-tests/reverse/reverse_test.go /^func Bench/,/^}/
To run:
$ go test -bench=.
$ go test -bench=. -benchmem
* Example tests
- Test functions that starts with `Example` will be treated specially by `go` `test`
- They neither have *parameters* nor *results*
.code writing-tests/reverse/reverse_test.go /^func Example/,/^}/
Three purposes
- Provides good documentation for the APIs
- Easy to maintain the documentation as code evolves (Example tests are real executable code that get executed on `go` `test`)
- Used by godoc server
* Coverage
- Testing is never complete
- `Coverage` increase the confidence that package works well in a wide range of important/critical scenarios
- `Statement` `Coverage`
- Use Go's `cover` `tool` to measure coverage report
To run:
$ go test -cover // prints coverage report on stdout
$ go test -coverprofile=fmt.out // creates coverage profile for further analysis
$ go tool cover -html=c.out // To see HTML report of the generated coverage profile
*Demo* *on* *fmt* *package*!*
* Profiling (1/2)
Trying to identify critical code of the system to increase the performance?
Start with *Profiling*.
Helps in identifying and correcting the bottlenecks
Types
- *CPU* *profile* - Identifies the functions whose execution requires most CPU
- *Heap* *profile* - Identifies statements responsible for allocating the most memory
- *Blocking* *profile* - Identifies the operations responsible for blocking goroutines
* Profiling (2/2)
Use *go* *tool* *pprof* to analyse the profile report
To run:
$ go test -cpuprofile=cpu.out
$ go test -memprofile=mem.out
$ go test -blockprofile=block.out
*DEMO* *on* *net/http* *package!*
* White-box and Black-box testing
* Examples from standard library
* Real problems in testing
Mocking, mocking, mocking!!
- Should not send actual emails
- Should not hit actual database
- etc..
* Writing Testable Code
* Why is it important?
You cannot mock everything at the runtime(like Django/python). Though it works for some cases, its not idiomatic way.
Writing code that can be easily tested is the idiomatic way.
*Two* *simple* *Problems*
- Reset User's password
- Send Notification Email
* Reset User's Password
* Reset User's Password (1/5)
.code writing-tests/resetpassword/resetpassword.go /^func main/,/^}/
* Reset User's Password (2/5)
*Testable* *version*
.code writing-tests/resetpassword/resetpassword_better.go /^func main/,/^}/
* Reset User's Password (3/5)
.code writing-tests/resetpassword/resetpassword_better.go /^func ResetPassword/,/^}/
.code writing-tests/resetpassword/resetpassword_better.go /START2/,/END2/
* Reset User's Password (4/5)
.code writing-tests/resetpassword/resetpassword_better.go /START1/,/END1/
* Reset User Password (5/5)
.code writing-tests/resetpassword/resetpassword_test.go /^func TestResetPassword/,/^}/
* Send Notification Email
* Send notification email
.code writing-tests/email/emails.go /^func SendInvitationEmail/,/^}/
.code writing-tests/email/emails.go /START1/,/END1/
* Send notification email
.code writing-tests/email/emails.go /START2/,/END2/
* Send notification email
*Testable* *Version*
.code writing-tests/email/emails_better.go /START/,/END/
* Send Notification Email
.code writing-tests/email/emails_better.go /START2/,/END2/
* Send Notification Email
.code writing-tests/email/emails_test.go /START1/,/END1/
* Send Notification Email
.code writing-tests/email/emails_test.go /START2/,/END2/
* Testing Tricky Parts
* Testing tricky parts (1/2)
Testing the functions that `Fatals` or `Exits` for some invalid inputs or behaviour
*Example*
.code writing-tests/tricky/tricky.go /^func mustEnv/,/^}/
Now, how do we test, whether *mustEnv(key)* exits if particular *key* is not set in environment variables?
* Testing tricky parts (2/2)
.code writing-tests/tricky/tricky_test.go /^func TestMustEnv/,/^}/
* Conclusion
*I'm* *not* *a* *great* *programmer;* *I'm* *just* *a* *good* *programmer* *with* *great* *habit* *-* *Kent* *Beck*
- Writing Tests is a great habit
- Makes the codebase evolve and maintanable
- Makes you a better Programmer!
* Reference
- [[https://www.youtube.com/watch?v=ndmB0bj7eyw][Testing Techniques]] talk by Andrew Gerrand
- Standard Library
- [[https://golang.org/pkg/testing/][testing]] package documentation
- go tool cover
- go tool pprof
*Books*
- *The* *Go* *programming* *launguage* by Alan A.A Donovan & Brian W. Kernighan (11th Chapter)
- *Web* *Development* *with* *Go* by Shiju Varghese (Covers basics of testing Web APIs)