index.html (35532B)
1 <!DOCTYPE html> 2 3 <html lang="en"> 4 5 <head> 6 <meta charset="utf-8"> 7 <meta http-equiv="content-type" content="text/html; charset=utf-8" /> 8 9 <link rel="start" href="https://vincent.demeester.fr" /> 10 11 <title>Vincent Demeester</title> 12 <link rel="canonical" href="https://vincent.demeester.fr/posts/2017-01-01-go-testing-functionnal-builders/"> 13 <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> 14 15 <link rel="openid.server" href="https://indieauth.com/openid" /> 16 <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> 17 <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> 18 19 <link rel="stylesheet" href="/css/screen.css" type="text/css" /> 20 <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> 21 <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> 22 23 </head> 24 25 <body lang="en"/> 26 27 28 29 30 31 32 <div id="main-container"> 33 <div id="page"> 34 <article class="post"> 35 <header> 36 <h1 class="emphnext">Golang testing — functional arguments for wonderful builders</h1><a href='https://vincent.demeester.fr/posts/2017-01-01-go-testing-functionnal-builders/'></a> 37 <address class="signature"> 38 <span class="date">Sun, 1 January, 2017</span> 39 <span class="words">(1600 Words)</span> 40 </address> 41 <ul class="tag_box inline"> 42 43 <li class="category"><a href="/categories/#developement">developement</a></li> 44 45 46 47 48 49 <li class="tag tag-golang"><a href="/tags/#golang">golang<span>12</span></a></li> 50 51 52 <li class="tag tag-testing"><a href="/tags/#testing">testing<span>11</span></a></li> 53 54 55 <li class="tag tag-builder"><a href="/tags/#builder">builder<span>1</span></a></li> 56 57 58 <li class="tag tag-functionnal"><a href="/tags/#functionnal">functionnal<span>2</span></a></li> 59 60 61 <li class="tag tag-java"><a href="/tags/#java">java<span>4</span></a></li> 62 63 64 <li class="tag tag-featured"><a href="/tags/#featured">featured<span>2</span></a></li> 65 66 <br/> 67 68 </ul> 69 </header> 70 71 72 73 <blockquote> 74 <p> 75 Programming is not easy; even the best programmers are incapable of 76 writing programs that work exactly as intended every time. Therefore 77 an important part of the software development process is 78 testing. Writing tests for our code is a good way to ensure quality 79 and improve reliability. 80 </p> 81 </blockquote> 82 83 <p> 84 Go programs, when properly implemented, are fairly simple to test 85 programatically. The <code>testing</code> built-in library and the features of 86 the language itself offer plenty of ways to write good tests. As this 87 is a subject I particularly like, I'm gonna write a bunch of articles 88 about it, that, <i>hopefully</i> do not get old or boring. 89 </p> 90 91 <p> 92 I'm not going to start by introducing how <code>testing</code> works, it's 93 already widely described in <a href="https://golang.org/pkg/testing/">the <code>testing</code> godoc</a>, <a href="https://blog.golang.org/examples">some</a> <a href="https://www.golang-book.com/books/intro/12">articles</a> and 94 <a href="https://jonathanmh.com/golang-unit-testing-for-absolute-beginners/">blogs</a>. I'm going to jump ahead on a more advanced techinque to write 95 tests, the <code>builders</code> for tests. 96 </p> 97 98 <p> 99 One of the most important characteristic of a <b>unit test</b> (and any 100 type of test really) is <b>readability</b>. This means it should be <i>easy 101 to read</i> but most importantly it should <b>clearly show the intent of 102 the test</b>. The setup (and cleanup) of the tests should be as small as 103 possible to avoid the noise. And as we are going to see below, <code>go</code> 104 makes it pretty easy to do so. 105 </p> 106 107 <div id="outline-container-org1afcfe7" class="outline-2"> 108 <h2 id="org1afcfe7">Builders in tests</h2> 109 <div class="outline-text-2" id="text-org1afcfe7"> 110 <p> 111 Sometimes, your need to create data structure for your test that 112 might take a lot of line and introduce noise. In <code>golang</code> we don't 113 have method overload or even <i>constructors</i> as some other language 114 have. This means most of the time, we end up building our data using 115 directly the struct expression, as below. 116 </p> 117 118 <div class="org-src-container"> 119 <pre class="src src-go"><span class="org-rainbow-identifiers-identifier-5">node</span> := &<span class="org-type">Node</span>{ 120 <span class="org-constant">Name</span>: <span class="org-string">"carthage"</span>, 121 <span class="org-constant">Hostname</span>: <span class="org-string">"carthage.sbr.pm"</span>, 122 <span class="org-constant">Platform</span>: <span class="org-type">Platform</span>{ 123 <span class="org-constant">Architecture</span>: <span class="org-string">"x86_64"</span>, 124 <span class="org-constant">OS</span>: <span class="org-string">"linux"</span>, 125 }, 126 } 127 </pre> 128 </div> 129 130 <p> 131 Let's imagine we have a <code>Validate</code> function that make sure the 132 specified <code>Node</code> is supported on our structure. We would write some 133 tests that ensure that. 134 </p> 135 136 <div class="org-src-container"> 137 <pre class="src src-go"><span class="org-keyword">func</span> <span class="org-function-name">TestValidateLinuxIsSupported</span>(<span class="org-rainbow-identifiers-identifier-8">t</span> *<span class="org-type">testing.T</span>) { 138 <span class="org-rainbow-identifiers-identifier-6">valid</span> := <span class="org-function-name">Validate</span>(&<span class="org-type">Node</span>{ 139 <span class="org-constant">Name</span>: <span class="org-string">"carthage"</span>, 140 <span class="org-constant">Hostname</span>: <span class="org-string">"carthage.sbr.pm"</span>, 141 <span class="org-constant">Platform</span>: &<span class="org-type">Platform</span>{ 142 <span class="org-constant">Architecture</span>: <span class="org-string">"x86_64"</span>, 143 <span class="org-constant">OS</span>: <span class="org-string">"linux"</span>, 144 }, 145 }) 146 <span class="org-keyword">if</span> !<span class="org-rainbow-identifiers-identifier-6">valid</span> { 147 <span class="org-rainbow-identifiers-identifier-8">t</span>.<span class="org-function-name">Fatal</span>(<span class="org-string">"linux should be supported, it was not"</span>) 148 } 149 } 150 151 <span class="org-keyword">func</span> <span class="org-function-name">TestValidateDarwinIsNotSupported</span>(<span class="org-rainbow-identifiers-identifier-8">t</span> *<span class="org-type">testing.T</span>) { 152 <span class="org-rainbow-identifiers-identifier-6">valid</span> := <span class="org-function-name">Validate</span>(&<span class="org-type">Node</span>{ 153 <span class="org-constant">Name</span>: <span class="org-string">"babylon"</span>, 154 <span class="org-constant">Hostname</span>: <span class="org-string">"babylon.sbr.pm"</span>, 155 <span class="org-constant">Platform</span>: &<span class="org-type">Platform</span>{ 156 <span class="org-constant">Architecture</span>: <span class="org-string">"x86_64"</span>, 157 <span class="org-constant">OS</span>: <span class="org-string">"darwin"</span>, 158 }, 159 }) 160 <span class="org-keyword">if</span> <span class="org-rainbow-identifiers-identifier-6">valid</span> { 161 <span class="org-rainbow-identifiers-identifier-8">t</span>.<span class="org-function-name">Fatal</span>(<span class="org-string">"darwin should not be supported, it was"</span>) 162 } 163 } 164 </pre> 165 </div> 166 167 <p> 168 This is quickly hard to read, there is too much noise on that 169 test. We setup a whole <code>Node</code> struct, but the only thing we really 170 intend to test is the <code>Platform.OS</code> part. The rest is just required 171 fields for the function to correctly compile and run. 172 </p> 173 174 <p> 175 This is where test builders (and builders in general) comes into 176 play. In <a href="http://www.growing-object-oriented-software.com/">Growing Object-Oriented Software Guided by Tests</a>, the 177 Chapter 22 "Constructing Complex Test Data" is exactly about that 178 and guide us through the why and the how of these builders. The 179 examples in the book are in <code>java</code> and uses wisely the 180 object-oriented nature of the language. Here is an example from the 181 book. 182 </p> 183 184 <div class="org-src-container"> 185 <pre class="src src-java"><span class="org-comment-delimiter">// </span><span class="org-comment">I just want an order from a customer that has no post code</span> 186 <span class="org-type">Order</span> <span class="org-variable-name">order</span> = <span class="org-rainbow-identifiers-identifier-12">anOrder</span>() 187 .<span class="org-rainbow-identifiers-identifier-7">from</span>(<span class="org-rainbow-identifiers-identifier-13">aCustomer</span>().<span class="org-rainbow-identifiers-identifier-11">with</span>(<span class="org-rainbow-identifiers-identifier-13">anAddress</span>().<span class="org-rainbow-identifiers-identifier-3">withNotPostCode</span>())) 188 .<span class="org-rainbow-identifiers-identifier-10">build</span>() 189 </pre> 190 </div> 191 192 <p> 193 These builders helps <b>keep tests expressive</b>, as it's pretty obvious 194 when reading it, what we want to test. They remove the <b>visual 195 noise</b> you have when building an object (or a <code>struct{}</code> in Go) and 196 allows you to put sane default. They also make <b>tests resilient to 197 change</b>. If the structure changes, only the builder has to be 198 updated, not the tests depending on it. They also make default case 199 really simple to write, and special cases not much more complicated. 200 </p> 201 </div> 202 </div> 203 204 <div id="outline-container-orgff90129" class="outline-2"> 205 <h2 id="orgff90129">Builder in Go</h2> 206 <div class="outline-text-2" id="text-orgff90129"> 207 <p> 208 The naive way to create builders in <code>go</code> could be to create a 209 <code>builder</code> struct that have methods to construct the final struct and 210 a <code>build</code> method. Let's see how it looks. 211 </p> 212 213 <div class="org-src-container"> 214 <pre class="src src-go"><span class="org-keyword">func</span> <span class="org-function-name">ANode</span>() *<span class="org-type">NodeBuilder</span> { 215 <span class="org-keyword">return</span> &<span class="org-type">NodeBuilder</span>{ 216 <span class="org-constant">node</span>: &<span class="org-type">Node</span>{ 217 <span class="org-constant">Name</span>: <span class="org-string">"node"</span>, 218 <span class="org-comment-delimiter">// </span><span class="org-comment">Other defaults</span> 219 }, 220 } 221 } 222 223 <span class="org-keyword">type</span> <span class="org-type">NodeBuilder</span> <span class="org-keyword">struct</span> { 224 <span class="org-rainbow-identifiers-identifier-5">node</span> *<span class="org-rainbow-identifiers-identifier-12">Node</span> 225 } 226 227 <span class="org-keyword">func</span> (<span class="org-rainbow-identifiers-identifier-1">b</span> *<span class="org-type">NodeBuilder</span>) <span class="org-function-name">Build</span>() *<span class="org-type">Node</span> { 228 <span class="org-keyword">return</span> <span class="org-rainbow-identifiers-identifier-1">b</span>.<span class="org-rainbow-identifiers-identifier-5">node</span> 229 } 230 231 <span class="org-keyword">func</span> (<span class="org-rainbow-identifiers-identifier-1">b</span> *<span class="org-type">NodeBuilder</span>) <span class="org-function-name">Hostname</span>(<span class="org-rainbow-identifiers-identifier-3">hostname</span> <span class="org-type">string</span>) *<span class="org-type">NodeBuilder</span> { 232 <span class="org-rainbow-identifiers-identifier-1">b</span>.<span class="org-rainbow-identifiers-identifier-5">node</span>.<span class="org-rainbow-identifiers-identifier-10">Hostname</span> = <span class="org-rainbow-identifiers-identifier-3">hostname</span> 233 <span class="org-keyword">return</span> <span class="org-rainbow-identifiers-identifier-1">b</span> 234 } 235 236 <span class="org-keyword">func</span> (<span class="org-rainbow-identifiers-identifier-1">b</span> *<span class="org-type">NodeBuilder</span>) <span class="org-function-name">Name</span>(<span class="org-rainbow-identifiers-identifier-3">name</span> <span class="org-type">string</span>) *<span class="org-type">NodeBuilder</span> { 237 <span class="org-rainbow-identifiers-identifier-1">b</span>.<span class="org-rainbow-identifiers-identifier-5">node</span>.<span class="org-rainbow-identifiers-identifier-8">Name</span> = <span class="org-rainbow-identifiers-identifier-3">name</span> 238 <span class="org-keyword">return</span> <span class="org-rainbow-identifiers-identifier-1">b</span> 239 } 240 241 <span class="org-keyword">func</span> (<span class="org-rainbow-identifiers-identifier-1">b</span> *<span class="org-type">NodeBuilder</span>) <span class="org-function-name">Platform</span>(<span class="org-rainbow-identifiers-identifier-14">platform</span> *<span class="org-type">Platform</span>) *<span class="org-type">NodeBuilder</span> { 242 <span class="org-rainbow-identifiers-identifier-1">b</span>.<span class="org-rainbow-identifiers-identifier-5">node</span>.<span class="org-rainbow-identifiers-identifier-4">Platform</span> = <span class="org-rainbow-identifiers-identifier-14">platform</span> 243 <span class="org-keyword">return</span> <span class="org-rainbow-identifiers-identifier-1">b</span> 244 } 245 </pre> 246 </div> 247 248 <p> 249 This looks decent, and using it is pretty straightforward. At least 250 it make building the <code>struct</code> more expressive, less noisy and 251 resilient to change. We can update the previous test as follow. 252 </p> 253 254 <div class="org-src-container"> 255 <pre class="src src-go"><span class="org-keyword">func</span> <span class="org-function-name">TestValidateLinuxIsSupported</span>(<span class="org-rainbow-identifiers-identifier-8">t</span> *<span class="org-type">testing.T</span>) { 256 <span class="org-rainbow-identifiers-identifier-6">valid</span> := <span class="org-function-name">Validate</span>(<span class="org-function-name">ANode</span>().<span class="org-function-name">Platform</span>(&<span class="org-type">Platform</span>{ 257 <span class="org-constant">Architecture</span>: <span class="org-string">"x86_64"</span>, 258 <span class="org-constant">OS</span>: <span class="org-string">"linux"</span>, 259 }).<span class="org-function-name">Build</span>()) 260 <span class="org-keyword">if</span> !<span class="org-rainbow-identifiers-identifier-6">valid</span> { 261 <span class="org-rainbow-identifiers-identifier-8">t</span>.<span class="org-function-name">Fatal</span>(<span class="org-string">"linux should be supported, it was not"</span>) 262 } 263 } 264 265 <span class="org-keyword">func</span> <span class="org-function-name">TestValidateDarwinIsNotSupported</span>(<span class="org-rainbow-identifiers-identifier-8">t</span> *<span class="org-type">testing.T</span>) { 266 <span class="org-rainbow-identifiers-identifier-6">valid</span> := <span class="org-function-name">Validate</span>(<span class="org-function-name">ANode</span>().<span class="org-function-name">Platform</span>(&<span class="org-type">Platform</span>{ 267 <span class="org-constant">Architecture</span>: <span class="org-string">"x86_64"</span>, 268 <span class="org-constant">OS</span>: <span class="org-string">"darwin"</span>, 269 }).<span class="org-function-name">Build</span>()) 270 <span class="org-keyword">if</span> <span class="org-rainbow-identifiers-identifier-6">valid</span> { 271 <span class="org-rainbow-identifiers-identifier-8">t</span>.<span class="org-function-name">Fatal</span>(<span class="org-string">"darwin should not be supported, it was"</span>) 272 } 273 } 274 </pre> 275 </div> 276 277 <p> 278 There is room for improvement : 279 </p> 280 281 <ul class="org-ul"> 282 <li>There is still some noise, mainly <code>build()</code> and the platform 283 <code>struct</code>, as it still shows too much.</li> 284 <li>It's not that extensible yet. If you want to update the <code>Node</code> a 285 certain way that the builder is not written for, you have to 286 update the builder.</li> 287 <li>The <code>NodeBuilder</code> struct feels a little empty, it's just there to 288 hold on the <code>Node</code> being constructed until it is <code>build</code>.</li> 289 </ul> 290 291 <p> 292 One improvement we could make is to have a <code>Platform</code> builder, even 293 if it's a small struct here. Let's do that in the same way we did 294 with <code>Node</code>. 295 </p> 296 297 <div class="org-src-container"> 298 <pre class="src src-go"><span class="org-keyword">func</span> <span class="org-function-name">APlatform</span>() *<span class="org-type">PlatformBuilder</span> { 299 <span class="org-keyword">return</span> &<span class="org-type">PlatformBuilder</span>{ 300 <span class="org-constant">platform</span>: &<span class="org-type">Platform</span>{ 301 <span class="org-constant">Architecture</span>: <span class="org-string">"x64_86"</span>, 302 <span class="org-constant">OS</span>: <span class="org-string">"linux"</span>, 303 }, 304 } 305 } 306 307 <span class="org-keyword">type</span> <span class="org-type">PlatformBuilder</span> <span class="org-keyword">struct</span>{ 308 <span class="org-rainbow-identifiers-identifier-14">platform</span> *<span class="org-rainbow-identifiers-identifier-4">Platform</span> 309 } 310 311 <span class="org-keyword">func</span> (<span class="org-rainbow-identifiers-identifier-1">b</span> *<span class="org-type">PlatformBuilder</span>) <span class="org-function-name">Build</span>() *<span class="org-type">Platform</span> { 312 <span class="org-keyword">return</span> <span class="org-rainbow-identifiers-identifier-1">b</span>.<span class="org-rainbow-identifiers-identifier-14">platform</span> 313 } 314 315 <span class="org-keyword">func</span> (<span class="org-rainbow-identifiers-identifier-1">b</span> *<span class="org-type">PlatformBuilder</span>) <span class="org-function-name">OS</span>(<span class="org-rainbow-identifiers-identifier-2">os</span> <span class="org-type">string</span>) *<span class="org-type">PlatformBuilder</span> { 316 <span class="org-rainbow-identifiers-identifier-1">b</span>.<span class="org-rainbow-identifiers-identifier-14">platform</span>.<span class="org-rainbow-identifiers-identifier-12">OS</span> = <span class="org-rainbow-identifiers-identifier-2">os</span> 317 <span class="org-keyword">return</span> <span class="org-rainbow-identifiers-identifier-1">b</span> 318 } 319 </pre> 320 </div> 321 322 <p> 323 And our tests becomes 🐻. 324 </p> 325 326 <div class="org-src-container"> 327 <pre class="src src-go"><span class="org-keyword">func</span> <span class="org-function-name">TestValidateLinuxIsSupported</span>(<span class="org-rainbow-identifiers-identifier-8">t</span> *<span class="org-type">testing.T</span>) { 328 <span class="org-rainbow-identifiers-identifier-6">valid</span> := <span class="org-function-name">Validate</span>(<span class="org-function-name">ANode</span>().<span class="org-function-name">Platform</span>( 329 <span class="org-function-name">APlatform</span>().<span class="org-function-name">OS</span>(<span class="org-string">"linux"</span>).<span class="org-function-name">Build</span>() 330 ).<span class="org-function-name">Build</span>()) 331 <span class="org-keyword">if</span> !<span class="org-rainbow-identifiers-identifier-6">valid</span> { 332 <span class="org-rainbow-identifiers-identifier-8">t</span>.<span class="org-function-name">Fatal</span>(<span class="org-string">"linux should be supported, it was not"</span>) 333 } 334 } 335 336 <span class="org-keyword">func</span> <span class="org-function-name">TestValidateDarwinIsNotSupported</span>(<span class="org-rainbow-identifiers-identifier-8">t</span> *<span class="org-type">testing.T</span>) { 337 <span class="org-rainbow-identifiers-identifier-6">valid</span> := <span class="org-function-name">Validate</span>(<span class="org-function-name">ANode</span>().<span class="org-function-name">Platform</span>( 338 <span class="org-function-name">APlatform</span>().<span class="org-function-name">OS</span>(<span class="org-string">"darwin"</span>).<span class="org-function-name">Build</span>() 339 ).<span class="org-function-name">Build</span>()) 340 <span class="org-keyword">if</span> <span class="org-rainbow-identifiers-identifier-6">valid</span> { 341 <span class="org-rainbow-identifiers-identifier-8">t</span>.<span class="org-function-name">Fatal</span>(<span class="org-string">"darwin should not be supported, it was"</span>) 342 } 343 } 344 </pre> 345 </div> 346 347 <p> 348 It does not really improve the visual noise as there is now quite a 349 few duplication : several <code>build</code>, <code>APlatform</code> inside <code>Platform</code>, … 350 It is a small improvement on readability but not that much compared 351 to the previous one. This is were the Go language features comes 352 into play. 353 </p> 354 </div> 355 </div> 356 357 <div id="outline-container-org28e3042" class="outline-2"> 358 <h2 id="org28e3042">Functional arguments to the rescue</h2> 359 <div class="outline-text-2" id="text-org28e3042"> 360 <p> 361 Go has two interesting feature that are going to be useful here. 362 </p> 363 364 <p> 365 First, a function in Go is a type on its own and thus is considered 366 a <i>first class citizen</i>. It means it's possible to pass a function 367 as argument, or define a variable that holds it. 368 </p> 369 370 <div class="org-src-container"> 371 <pre class="src src-go"><span class="org-keyword">func</span> <span class="org-function-name">ApplyTo</span>(<span class="org-rainbow-identifiers-identifier-8">s</span> <span class="org-type">string</span>, <span class="org-rainbow-identifiers-identifier-12">fn</span> <span class="org-keyword">func</span>(<span class="org-type">string</span>) <span class="org-type">string</span>) <span class="org-type">string</span> { 372 <span class="org-keyword">return</span> <span class="org-function-name">fn</span>(<span class="org-rainbow-identifiers-identifier-8">s</span>) 373 } 374 375 <span class="org-keyword">func</span> <span class="org-function-name">world</span>(<span class="org-rainbow-identifiers-identifier-8">s</span> <span class="org-type">string</span>) <span class="org-type">string</span> { 376 <span class="org-keyword">return</span> <span class="org-rainbow-identifiers-identifier-9">fmt</span>.<span class="org-function-name">Sprintf</span>(<span class="org-string">"%s, world!"</span>, <span class="org-rainbow-identifiers-identifier-8">s</span>) 377 } 378 379 <span class="org-comment-delimiter">// </span><span class="org-comment">Usage</span> 380 <span class="org-rainbow-identifiers-identifier-1">a</span> := <span class="org-function-name">ApplyTo</span>(<span class="org-string">"hello"</span>, <span class="org-rainbow-identifiers-identifier-9">world</span>) 381 <span class="org-comment-delimiter">// </span><span class="org-comment">a == "hello, world!"</span> 382 </pre> 383 </div> 384 385 <p> 386 The second feature that comes into play here, is the possiblity to 387 have <i>variadic</i> functions. A variadic function is a function that 388 takes a variable number of arguments (from <code>0</code> to any number of 389 argument). 390 </p> 391 392 <div class="org-src-container"> 393 <pre class="src src-go"><span class="org-keyword">func</span> <span class="org-function-name">Print</span>(<span class="org-rainbow-identifiers-identifier-9">strs</span> ...<span class="org-type">string</span>) <span class="org-type">string</span> { 394 <span class="org-keyword">for</span> <span class="org-rainbow-identifiers-identifier-8">_</span>, <span class="org-rainbow-identifiers-identifier-8">s</span> := <span class="org-keyword">range</span> <span class="org-rainbow-identifiers-identifier-9">strs</span> { 395 <span class="org-rainbow-identifiers-identifier-9">fmt</span>.<span class="org-function-name">Println</span>(<span class="org-rainbow-identifiers-identifier-8">s</span>) 396 } 397 } 398 </pre> 399 </div> 400 401 <p> 402 As we are going to see below, combining these two feature makes our 403 builders pretty easy to write and to use with simple case, while 404 staying very customizable, even outside of the builder. This is 405 really well described in a talk from Dave Cheney : <a href="https://www.youtube.com/watch?v=24lFtGHWxAQ&index=15&list=PLMW8Xq7bXrG58Qk-9QSy2HRh2WVeIrs7e">Functional 406 options for friendly APIs</a> (<a href="https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis">transcription</a>). 407 </p> 408 409 <p> 410 Let's apply that to our new builders. 411 </p> 412 413 <div class="org-src-container"> 414 <pre class="src src-go"><span class="org-keyword">func</span> <span class="org-function-name">ANode</span>(<span class="org-rainbow-identifiers-identifier-2">nodeBuilders</span> ...<span class="org-keyword">func</span>(*<span class="org-type">Node</span>)) *<span class="org-type">Node</span> { 415 <span class="org-rainbow-identifiers-identifier-5">node</span> := &<span class="org-type">Node</span>{ 416 <span class="org-constant">Name</span>: <span class="org-string">"node"</span>, 417 <span class="org-comment-delimiter">// </span><span class="org-comment">Other defaults</span> 418 } 419 420 <span class="org-keyword">for</span> <span class="org-rainbow-identifiers-identifier-8">_</span>, <span class="org-rainbow-identifiers-identifier-10">build</span> := <span class="org-keyword">range</span> <span class="org-rainbow-identifiers-identifier-2">nodeBuilders</span> { 421 <span class="org-function-name">build</span>(<span class="org-rainbow-identifiers-identifier-5">node</span>) 422 } 423 424 <span class="org-keyword">return</span> <span class="org-rainbow-identifiers-identifier-5">node</span> 425 } 426 427 <span class="org-keyword">func</span> <span class="org-function-name">APlatform</span>(<span class="org-rainbow-identifiers-identifier-8">platformBuilders</span> ...<span class="org-keyword">func</span>(*<span class="org-type">Platform</span>)) *<span class="org-type">Platform</span> { 428 <span class="org-rainbow-identifiers-identifier-14">platform</span> := &<span class="org-type">Platform</span>{ 429 <span class="org-constant">Architecture</span>: <span class="org-string">"x64_86"</span>, 430 <span class="org-constant">OS</span>: <span class="org-string">"linux"</span>, 431 } 432 433 <span class="org-keyword">for</span> <span class="org-rainbow-identifiers-identifier-8">_</span>, <span class="org-rainbow-identifiers-identifier-10">build</span> := <span class="org-keyword">range</span> <span class="org-rainbow-identifiers-identifier-8">platformBuilders</span> { 434 <span class="org-function-name">build</span>(<span class="org-rainbow-identifiers-identifier-14">platform</span>) 435 } 436 437 <span class="org-keyword">return</span> <span class="org-rainbow-identifiers-identifier-14">platform</span> 438 } 439 </pre> 440 </div> 441 442 <p> 443 And that is it for the actual builder code. It is <b>small</b> and 444 simple, there is <b>no more <code>NodeBuilder</code></b> struct, and this is highly 445 extensible. Let's see how to use it. 446 </p> 447 448 <div class="org-src-container"> 449 <pre class="src src-go"><span class="org-comment-delimiter">// </span><span class="org-comment">a default node</span> 450 <span class="org-rainbow-identifiers-identifier-2">node1</span> := <span class="org-function-name">ANode</span>() 451 <span class="org-comment-delimiter">// </span><span class="org-comment">a node with a specific Hostname</span> 452 <span class="org-rainbow-identifiers-identifier-5">node2</span> := <span class="org-function-name">ANode</span>(<span class="org-keyword">func</span>(<span class="org-rainbow-identifiers-identifier-15">n</span> *<span class="org-type">Node</span>) { 453 <span class="org-rainbow-identifiers-identifier-15">n</span>.<span class="org-rainbow-identifiers-identifier-10">Hostname</span> = <span class="org-string">"custom-hostname"</span> 454 }) 455 <span class="org-comment-delimiter">// </span><span class="org-comment">a node with a specific name and platform</span> 456 <span class="org-rainbow-identifiers-identifier-8">node3</span> := <span class="org-function-name">ANode</span>(<span class="org-keyword">func</span>(<span class="org-rainbow-identifiers-identifier-15">n</span> *<span class="org-type">Node</span>) { 457 <span class="org-rainbow-identifiers-identifier-15">n</span>.<span class="org-rainbow-identifiers-identifier-8">Name</span> = <span class="org-string">"custom-name"</span> 458 }, <span class="org-keyword">func</span> (<span class="org-rainbow-identifiers-identifier-15">n</span> *<span class="org-type">Node</span>) { 459 <span class="org-rainbow-identifiers-identifier-15">n</span>.<span class="org-rainbow-identifiers-identifier-4">Platform</span> = <span class="org-function-name">APlatform</span>(<span class="org-keyword">func</span> (<span class="org-rainbow-identifiers-identifier-10">p</span> *<span class="org-type">Platform</span>) { 460 <span class="org-rainbow-identifiers-identifier-10">p</span>.<span class="org-rainbow-identifiers-identifier-12">OS</span> = <span class="org-string">"darwin"</span> 461 }) 462 }) 463 </pre> 464 </div> 465 466 <p> 467 The last step is to define some <i>function builder</i> for common or 468 widely used customization, to make this <b>expressive</b>. And let 469 complex, <i>one-time</i> function builder in the end of the user. Now our 470 tests looks like. 471 </p> 472 473 <div class="org-src-container"> 474 <pre class="src src-go"><span class="org-keyword">func</span> <span class="org-function-name">TestValidateLinuxIsSupported</span>(<span class="org-rainbow-identifiers-identifier-8">t</span> *<span class="org-type">testing.T</span>) { 475 <span class="org-rainbow-identifiers-identifier-6">valid</span> := <span class="org-function-name">Validate</span>(<span class="org-function-name">ANode</span>(<span class="org-function-name">WithAPlatform</span>(<span class="org-rainbow-identifiers-identifier-2">Linux</span>))) 476 <span class="org-keyword">if</span> !<span class="org-rainbow-identifiers-identifier-6">valid</span> { 477 <span class="org-rainbow-identifiers-identifier-8">t</span>.<span class="org-function-name">Fatal</span>(<span class="org-string">"linux should be supported, it was not"</span>) 478 } 479 } 480 481 <span class="org-keyword">func</span> <span class="org-function-name">TestValidateDarwinIsNotSupported</span>(<span class="org-rainbow-identifiers-identifier-8">t</span> *<span class="org-type">testing.T</span>) { 482 <span class="org-rainbow-identifiers-identifier-6">valid</span> := <span class="org-function-name">Validate</span>(<span class="org-function-name">ANode</span>(<span class="org-function-name">WithAPlatform</span>(<span class="org-rainbow-identifiers-identifier-15">Darwin</span>))) 483 <span class="org-keyword">if</span> <span class="org-rainbow-identifiers-identifier-6">valid</span> { 484 <span class="org-rainbow-identifiers-identifier-8">t</span>.<span class="org-function-name">Fatal</span>(<span class="org-string">"darwin should not be supported, it was"</span>) 485 } 486 } 487 488 <span class="org-comment-delimiter">// </span><span class="org-comment">Function builders</span> 489 <span class="org-keyword">func</span> <span class="org-function-name">WithAPlatform</span>(<span class="org-rainbow-identifiers-identifier-4">builders</span> ...<span class="org-keyword">func</span>(*<span class="org-type">Platform</span>)) <span class="org-keyword">func</span> (<span class="org-rainbow-identifiers-identifier-15">n</span> *<span class="org-type">Node</span>) { 490 <span class="org-keyword">return</span> <span class="org-keyword">func</span>(<span class="org-rainbow-identifiers-identifier-15">n</span> *<span class="org-type">Node</span>) { 491 <span class="org-rainbow-identifiers-identifier-15">n</span>.<span class="org-rainbow-identifiers-identifier-4">Platform</span> = <span class="org-function-name">Platform</span>(<span class="org-rainbow-identifiers-identifier-4">builders</span>...) 492 } 493 } 494 495 <span class="org-keyword">func</span> <span class="org-function-name">Linux</span>(<span class="org-rainbow-identifiers-identifier-10">p</span> *<span class="org-type">Platform</span>) { 496 <span class="org-rainbow-identifiers-identifier-10">p</span>.<span class="org-rainbow-identifiers-identifier-12">OS</span> = <span class="org-string">"linux"</span> 497 } 498 499 <span class="org-keyword">func</span> <span class="org-function-name">Darwin</span>(<span class="org-rainbow-identifiers-identifier-10">p</span> *<span class="org-type">Platform</span>) { 500 <span class="org-rainbow-identifiers-identifier-10">p</span>.<span class="org-rainbow-identifiers-identifier-12">OS</span> = <span class="org-string">"darwin"</span> 501 } 502 </pre> 503 </div> 504 505 <p> 506 The intent is now clear. It's readable and still resilient to 507 change. The code <code>Node(WithPlatform(Linux))</code> is easy to understand 508 for a human. It makes what are the <i>tested</i> characteristics of 509 <code>struct</code> pretty clear. It's easy to combine multiple builders as the 510 <code>WithPlatform</code> function shows 👼. It's also easy to create a 511 <i>function builder</i>, even in a different package (as long as the ways 512 to modify the struct are exported) and complex or <i>on-off</i> builder 513 can be embedded in the function call (<code>Node(func(n *Node) { // … 514 })</code>). 515 </p> 516 517 <p> 518 In summary, using these types of builder have several advantages : 519 </p> 520 521 <ul class="org-ul"> 522 <li>tests are <b>easy to read</b>, and reduce the visual noise</li> 523 <li>tests are <b>resilient to change</b></li> 524 <li>builders are <b>easy to compose</b> and very extensible</li> 525 <li>builders could even be <b>shared</b> with production code as there is 526 nothing tied to <code>testing</code>.</li> 527 </ul> 528 </div> 529 </div> 530 531 532 </article> 533 <hr /> 534 <div class="prev-next"> 535 536 <a class="paging-link prev" href="/posts/2017-04-22-golang-testing-golden-file/" title="Golang testing — golden file">← Previous post</a> 537 538 539 540 <a class="paging-link next" href="/posts/2016-09-18-firefox-places-and-bookmarks/" title="Firefox hidden feature — places in bookmarks">Next post →</a> 541 542 </div> 543 544 </div> 545 </div> 546 547 <footer> 548 <nav> 549 550 <a href="/">home</a> 551 <span class="text-muted"> | </span> 552 553 <a href="/about">about</a> 554 <span class="text-muted"> | </span> 555 556 <a href="/archive">archive</a> 557 <span class="text-muted"> | </span> 558 559 <a href="/categories">categories</a> 560 <span class="text-muted"> | </span> 561 562 <a href="/tags">tags</a> 563 <span class="text-muted"> | </span> 564 565 <a href="https://twitter.com/vdemeest">twitter</a> 566 <span class="text-muted"> | </span> 567 568 <a href="https://github.com/vdemeester">github</a> 569 <span class="text-muted"> | </span> 570 571 <a href="https://vincent.demeester.fr/index.xml">rss</a> 572 </nav> 573 <br/> 574 <address> 575 <span class="copyright"> 576 Content and design by Vincent Demeester 577 (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) 578 </span><br /> 579 <span class="engine"> 580 Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> 581 </span> 582 </address> 583 </footer> 584 </body> 585