2018-09-14-gotest-tools-fs.html (11684B)
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <!-- Sep 03, 2024 --> 5 <meta charset="utf-8" /> 6 <meta name="viewport" content="width=device-width, initial-scale=1" /> 7 <title>Golang testing — gotest.tools fs</title> 8 <meta name="author" content="Vincent Demeester" /> 9 <meta name="generator" content="Org Mode" /> 10 <link rel='icon' type='image/x-icon' href='/images/favicon.ico'/> 11 <meta name='viewport' content='width=device-width, initial-scale=1'> 12 <link rel='stylesheet' href='/css/new.css' type='text/css'/> 13 <link rel='stylesheet' href='/css/syntax.css' type='text/css'/> 14 <link href='/index.xml' rel='alternate' type='application/rss+xml' title='Vincent Demeester' /> 15 </head> 16 <body> 17 <main id="content" class="content"> 18 <header> 19 <h1 class="title">Golang testing — gotest.tools fs</h1> 20 </header><nav id="table-of-contents" role="doc-toc"> 21 <h2>Table of Contents</h2> 22 <div id="text-table-of-contents" role="doc-toc"> 23 <ul> 24 <li><a href="#Introduction">Introduction</a></li> 25 <li><a href="#Create%20folder%20structures">Create folder structures</a></li> 26 <li><a href="#Compare%20folder%20structures">Compare folder structures</a></li> 27 <li><a href="#Conclusion%E2%80%A6">Conclusion…</a></li> 28 </ul> 29 </div> 30 </nav> 31 <section id="outline-container-Introduction" class="outline-2"> 32 <h2 id="Introduction">Introduction</h2> 33 <div class="outline-text-2" id="text-Introduction"> 34 <p> 35 Let’s continue the <a href="https://gotest.tools"><code>gotest.tools</code></a> serie, this time with the <code>fs</code> package. 36 </p> 37 38 <blockquote> 39 <p> 40 Package fs provides tools for creating temporary files, and testing the contents and structure of a directory. 41 </p> 42 </blockquote> 43 44 <p> 45 This package is heavily using functional arguments (as we saw in <a href="file:///posts/2017-01-01-go-testing-functionnal-builders/">functional arguments for 46 wonderful builders</a>). Functional arguments is, in a nutshell, a combinaison of two Go 47 features : <i>variadic</i> functions (<code>...</code> operation in a function signature) and the fact 48 that <code>func</code> are <i>first class citizen</i>. This looks more or less like that. 49 </p> 50 51 <div class="org-src-container"> 52 <pre class="src src-go">type Config struct {} 53 54 func MyFn(ops ...func(*Config)) *Config { 55 c := &Config{} // with default values 56 for _, op := range ops { 57 op(c) 58 } 59 return c 60 } 61 62 // Calling it 63 conf := MyFn(withFoo, withBar("baz")) 64 </pre> 65 </div> 66 67 <p> 68 The <code>fs</code> package has too <b>main</b> purpose : 69 </p> 70 71 <ol class="org-ol"> 72 <li>create folders and files required for testing in a simple manner</li> 73 <li>compare two folders structure (and content)</li> 74 </ol> 75 </div> 76 </section> 77 <section id="outline-container-Create%20folder%20structures" class="outline-2"> 78 <h2 id="Create%20folder%20structures">Create folder structures</h2> 79 <div class="outline-text-2" id="text-Create%20folder%20structures"> 80 <p> 81 Sometimes, you need to create folder structures (and files) in tests. Doing <code>i/o</code> work 82 takes time so try to limit the number of tests that needs to do that, especially in unit 83 tests. Doing it in tests adds a bit of boilerplate that could be avoid. As stated <a href="file:///posts/2017-01-01-go-testing-functionnal-builders/">before</a> : 84 </p> 85 86 <blockquote> 87 <p> 88 One of the most important characteristic of a unit test (and any type of test really) is 89 <b>readability</b>. This means it should be easy to read but most importantly it should <b>clearly 90 show the intent</b> of the test. The setup (and cleanup) of the tests should be as small as 91 possible to avoid the noise. 92 </p> 93 </blockquote> 94 95 <p> 96 In a test you usually end up using <code>ioutil</code> function to create what you need. This looks 97 somewhat like the following. 98 </p> 99 100 <div class="org-src-container"> 101 <pre class="src src-go">path, err := ioutil.TempDir("", "bar") 102 if err != nil { // or using `assert.Assert` 103 t.Fatal(err) 104 } 105 if err := os.Mkdir(filepath.Join(path, "foo"), os.FileMode(0755)); err != nil { 106 t.Fatal(err) 107 } 108 if err := ioutil.WriteFile(filepath.Join(path, "foo", "bar"), []byte("content"), os.FileMode(0777)); err != nil { 109 t.Fatal(err) 110 } 111 defer os.RemoveAll(path) // to clean up at the end of the test 112 </pre> 113 </div> 114 115 <p> 116 The <code>fs</code> package intends to help reduce the noise and comes with a bunch function to create 117 folder structure : 118 </p> 119 120 <ul class="org-ul"> 121 <li>two main function <code>NewFile</code> and <code>NewDir</code></li> 122 <li>a bunch of <i>operators</i> : <code>WithFile</code>, <code>WithDir</code>, …</li> 123 </ul> 124 125 <div class="org-src-container"> 126 <pre class="src src-go">func NewDir(t assert.TestingT, prefix string, ops ...PathOp) *Dir { 127 // … 128 } 129 130 func NewFile(t assert.TestingT, prefix string, ops ...PathOp) *File { 131 // … 132 } 133 </pre> 134 </div> 135 136 <p> 137 The <code>With*</code> function are all satisfying the <code>PathOp</code> interface, making <code>NewFile</code> and 138 <code>NewDir</code> extremely composable. Let’s first see how our above example would look like using 139 the <code>fs</code> package, and then, we’ll look a bit more at the main <code>PathOp</code> function… 140 </p> 141 142 <div class="org-src-container"> 143 <pre class="src src-go">dir := fs.NewDir(t, "bar", fs.WithDir("foo", 144 fs.WithFile("bar", fs.WithContent("content"), fs.WithMode(os.FileMode(0777))), 145 )) 146 defer dir.Remove() 147 </pre> 148 </div> 149 150 <p> 151 It’s clean and simple to read. The intent is well described and there is not that much of 152 noise. <code>fs</code> functions tends to have <i>sane</i> and <i>safe</i> defaults value (for <code>os.FileMode</code> 153 for example). Let’s list the main, useful, <code>PathOp</code> provided by <code>gotest.tools/fs</code>. 154 </p> 155 156 <ul class="org-ul"> 157 <li><code>WithDir</code> creates a sub-directory in the directory at path.</li> 158 <li><code>WithFile</code> creates a file in the directory at path with content.</li> 159 <li><code>WithSymlink</code> creates a symlink in the directory which links to target. Target must be a 160 path relative to the directory.</li> 161 <li><code>WithHardlink</code> creates a link in the directory which links to target. Target must be a 162 path relative to the directory.</li> 163 <li><code>WithContent</code> and <code>WWithBytes</code> write content to a file at Path (from a <code>string</code> or a 164 <code>[]byte</code> slice).</li> 165 <li><code>WithMode</code> sets the file mode on the directory or file at path.</li> 166 <li><code>WithTimestamps</code> sets the access and modification times of the file system object at 167 path.</li> 168 <li><code>FromDir</code> copies the directory tree from the source path into the new Dir. This is 169 pretty useful when you have a huge folder structure already present in you <code>testdata</code> 170 folder or elsewhere.</li> 171 <li><code>AsUser</code> changes ownership of the file system object at Path.</li> 172 </ul> 173 174 <p> 175 Also, note that <code>PathOp</code> being an function type, you can provide your own implementation 176 for specific use-cases. Your function just has to satisfy <code>PathOp</code> signature. 177 </p> 178 179 <div class="org-src-container"> 180 <pre class="src src-go">type PathOp func(path Path) error 181 </pre> 182 </div> 183 </div> 184 </section> 185 <section id="outline-container-Compare%20folder%20structures" class="outline-2"> 186 <h2 id="Compare%20folder%20structures">Compare folder structures</h2> 187 <div class="outline-text-2" id="text-Compare%20folder%20structures"> 188 <p> 189 Sometimes, the code you’re testing is creating a folder structure, and you would like to 190 be able to tests that, with the given arguments, it creates the specified structure. <code>fs</code> 191 allows you to do that too. 192 </p> 193 194 <p> 195 The package provides a <code>Equal</code> function, which returns a <code>Comparison</code>, that the <a href="file:///posts/2018-08-16-gotest-tools-assertions/"><code>assert</code></a> 196 package understand. It works by comparing a <code>Manifest</code> type provided by the test and a 197 <code>Manifest</code> representation of the specified folder. 198 </p> 199 200 <blockquote> 201 <p> 202 Equal compares a directory to the expected structured described by a manifest and returns 203 success if they match. If they do not match the failure message will contain all the 204 differences between the directory structure and the expected structure defined by the 205 Manifest. 206 </p> 207 </blockquote> 208 209 <p> 210 A <code>Manifest</code> stores the expected structure and properties of files and directories in a 211 file-system. You can create a <code>Manifest</code> using either the functions <code>Expected</code> or 212 <code>ManifestFromDir</code>. 213 </p> 214 215 <p> 216 We’re going to focus on the <code>Expected</code> function, as <code>ManifestFromDir</code> does pretty much 217 what you would expected : it takes the specified path, and returns a <code>Manifest</code> that 218 represent this folder. 219 </p> 220 221 <div class="org-src-container"> 222 <pre class="src src-go">func Expected(t assert.TestingT, ops ...PathOp) Manifest 223 </pre> 224 </div> 225 226 <p> 227 <code>Expected</code> is close to <code>NewDir</code> function : it takes the same <code>PathOp</code> functional 228 arguments. This makes creating a <code>Manifest</code> straightforward, as it’s working the same. Any 229 function that satisfy <code>PathOp</code> can be used for <code>Manifest</code> the exact same way you’re using 230 them on <code>fs.NewDir</code>. 231 </p> 232 233 <p> 234 There is a few additional functions that are only useful with <code>Manifest</code> : 235 </p> 236 237 <ul class="org-ul"> 238 <li><code>MatchAnyFileContent</code> updates a Manifest so that the file at path may contain any content.</li> 239 <li><code>MatchAnyFileMode</code> updates a Manifest so that the resource at path will match any file mode.</li> 240 <li><code>MatchContentIgnoreCarriageReturn</code> ignores cariage return discrepancies.</li> 241 <li><code>MatchExtraFiles</code> updates a Manifest to allow a directory to contain unspecified files.</li> 242 </ul> 243 244 <div class="org-src-container"> 245 <pre class="src src-go">path := operationWhichCreatesFiles() 246 expected := fs.Expected(t, 247 fs.WithFile("one", "", 248 fs.WithBytes(golden.Get(t, "one.golden")), 249 fs.WithMode(0600)), 250 fs.WithDir("data", 251 fs.WithFile("config", "", fs.MatchAnyFileContent)), 252 ) 253 254 assert.Assert(t, fs.Equal(path, expected)) 255 </pre> 256 </div> 257 258 <p> 259 The following example compares the result of <code>operationWhichCreatesFiles</code> to the expected 260 <code>Manifest</code>. As you can see it also integrates well with other part of the <code>gotest.tools</code> 261 library, with the <a href="file:///posts/2018-09-06-gotest-tools-golden/"><code>golden</code> package</a> in this example. 262 </p> 263 </div> 264 </section> 265 <section id="outline-container-Conclusion%E2%80%A6" class="outline-2"> 266 <h2 id="Conclusion%E2%80%A6">Conclusion…</h2> 267 <div class="outline-text-2" id="text-Conclusion%E2%80%A6"> 268 <p> 269 … that’s a wrap. In my opinion, this is one the most useful package provided by 270 <code>gotest.tools</code> after <code>assert</code>. It allows to create simple or complex folder structure 271 without the noise that usually comes with it. 272 </p> 273 </div> 274 </section> 275 </main> 276 <footer id="postamble" class="status"> 277 <footer> 278 <small><a href="/" rel="history">Index</a> • <a href="/sitemap.html">Sitemap</a> • <a href="https://dl.sbr.pm/">Files</a></small><br/> 279 <small class='questions'>Questions, comments ? Please use my <a href="https://lists.sr.ht/~vdemeester/public-inbox">public inbox</a> by sending a plain-text email to <a href="mailto:~vdemeester/public-inbox@lists.sr.ht">~vdemeester/public-inbox@lists.sr.ht</a>.</small><br/> 280 <small class='copyright'> 281 Content and design by Vincent Demeester 282 (<a rel='licence' href='http://creativecommons.org/licenses/by-nc-sa/3.0/'>Some rights reserved</a>) 283 </small><br /> 284 </footer> 285 </footer> 286 </body> 287 </html>