11# testdetect
22
3- Despite [ many] ( https://github.com/golang/go/issues/12120 ) ,
4- [ many] ( https://github.com/golang/go/issues/14668 ) ,
5- [ many] ( https://github.com/golang/go/issues/21360 ) ,
6- [ many] ( https://github.com/golang/go/issues/60737 ) ,
7- [ many] ( https://github.com/golang/go/issues/60772 ) ,
8- [ many] ( https://github.com/golang/go/issues/64356 )
9- requests and proposals for a compile-time constraint to strip out test-time
10- specific code, Go still does not have a ` test ` build tag.
11- [ ` testing.Testing() ` ] ( https://pkg.go.dev/testing#Testing ) was added in Go 1.21
12- but is sadly not constant, meaning any code gated behind it will still be
13- present in a release binary.
3+ Sometimes it is useful in Go code to provide test hooks so that methods can
4+ behave differently at test time. Here is a trivial example.
5+
6+ ``` go
7+ package main
8+
9+ import " fmt"
10+
11+ var testHookGreet func (string ) string
12+
13+ func Greet (s string ) string {
14+ if h := testHookGreet; h != nil {
15+ return h (s)
16+ }
17+ return fmt.Sprintf (" Hello, %s !" , s)
18+ }
19+
20+ func main () { println (Greet (" world" )) }
21+ ```
22+
23+ Now ` testHookGreet ` can be set during testing to override the behavior of the
24+ real ` Greet() ` . This might be useful for tests that are not testing the
25+ implementation of ` Greet() ` itself, but in which ` Greet() ` is still being
26+ called somewhere.
27+
28+ Test hooks are found in several places in the [ Go standard library] [ stdlib ]
29+ as well as elsewhere [ in the wild] [ search ] .
30+
31+ The Go compiler is not currently smart enough to recognize that, in the above
32+ example, ` testHookGreet ` is always ` nil ` when the real program is running. As a
33+ result, in the finished program, ` Greet() ` performs a check to see if
34+ ` testHookGreet ` is ` nil ` every time it is called ([ godbolt] [ godbolt ] ).
35+
36+ This has very little impact in the grand scheme of things, especially if the
37+ program is running with [ profile-guided optimization] [ pgo ] , but it would be
38+ nice for test instrumentation like this to be brought down to zero impact.
1439
1540` testdetect ` generates a package-local ` testingDetector ` type with a single
1641method, ` Testing() ` . While not a true constant, this method is "constant
1742enough" that most Go compilers will optimize test related branches out of the
18- finished binary. In this way, it's the closest thing to an ` #ifdef TEST ` as we
19- can get.
43+ finished binary. It's the closest thing to an ` #ifdef TEST ` as can be achieved
44+ today.
45+
46+ Here it is wired into the previous example.
47+
48+ ``` go
49+ package main
50+
51+ import " fmt"
52+
53+ var t testingDetector
54+ var testHookGreet func (string ) string
55+
56+ func Greet (s string ) string {
57+ if h := testHookGreet; t.Testing () && h != nil {
58+ return h (s)
59+ }
60+ return fmt.Sprintf (" Hello, %s !" , s)
61+ }
62+
63+ func main () { println (Greet (" world" )) }
64+ ```
65+
66+ Now the entire test hook branch is optimized away, leaving the compiled code
67+ effectively the same as if the program were written like this.
68+
69+ ``` go
70+ package main
71+
72+ import " fmt"
73+
74+ func Greet (s string ) string {
75+ return fmt.Sprintf (" Hello, %s !" , s)
76+ }
77+
78+ func main () { println (Greet (" world" )) }
79+ ```
2080
2181## Usage
2282
@@ -90,8 +150,21 @@ this code is perfectly valid by Go spec rules and will never fail to run.
90150
91151I've built this in the hopes that it will someday be retired.
92152
93- All of this package's complexity could be reduced by treating ` _test.go ` files
94- the same as any other build constraint and exposing a ` test ` build tag.
153+ Despite [ many] ( https://github.com/golang/go/issues/12120 ) ,
154+ [ many] ( https://github.com/golang/go/issues/14668 ) ,
155+ [ many] ( https://github.com/golang/go/issues/21360 ) ,
156+ [ many] ( https://github.com/golang/go/issues/60737 ) ,
157+ [ many] ( https://github.com/golang/go/issues/60772 ) ,
158+ [ many] ( https://github.com/golang/go/issues/64356 )
159+ requests and proposals for a compile-time constraint to strip out test-time
160+ specific code, Go still does not have a ` test ` build tag.
161+ [ ` testing.Testing() ` ] ( https://pkg.go.dev/testing#Testing ) was added in Go 1.21
162+ but is sadly not constant, meaning any code gated behind it will still be
163+ present in a release binary.
164+
165+ Almost all of this utility's work could be could be made redundant if Go
166+ treated ` _test.go ` files the same as any other build constraint by exposing a
167+ ` test ` build tag.
95168
96169``` go filename=undertest_false.go
97170// go:build !test
@@ -116,3 +189,9 @@ it a second time elsewhere in the code would be a compile error.
116189I am personally of the opinion that this is simpler, more understandable, less
117190surprising, and a simple ` if false == true ` check is highly likely to be
118191optimized out by any current or future Go compiler.
192+
193+ [ stdlib ] : https://github.com/search?q=repo%3Agolang%2Fgo%20testhook&type=code
194+ [ search] :
195+ https://github.com/search?q=NOT+repo%3Agolang%2Fgo+%22var+testhook%22+language%3AGo&type=code
196+ [ pgo ] : https://go.dev/doc/pgo
197+ [ godbolt ] : https://godbolt.org/z/fYj1rrEEx
0 commit comments