index.html (20649B)
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/2018-09-14-gotest-tools-fs/"> 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=""/> 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 — gotest.tools fs</h1><a href='https://vincent.demeester.fr/posts/2018-09-14-gotest-tools-fs/'></a> 37 <address class="signature"> 38 <span class="date">Fri, 14 September, 2018</span> 39 <span class="words">(1100 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-testing"><a href="/tags/#testing">testing<span>11</span></a></li> 50 51 52 <li class="tag tag-golang"><a href="/tags/#golang">golang<span>12</span></a></li> 53 54 55 <li class="tag tag-filesystem"><a href="/tags/#filesystem">filesystem<span>1</span></a></li> 56 57 <br/> 58 59 </ul> 60 </header> 61 62 63 64 65 66 <p>Let’s continue the <a href="https://gotest.tools"><code>gotest.tools</code></a> serie, this time with the <code>fs</code> package.</p> 67 68 <blockquote> 69 <p>Package fs provides tools for creating temporary files, and testing the contents and structure of a directory.</p> 70 </blockquote> 71 72 <p>This package is heavily using functional arguments (as we saw in <a href="/posts/2017-01-01-go-testing-functionnal-builders/">functional arguments for 73 wonderful builders</a>). Functional arguments is, in a nutshell, a combinaison of two Go 74 features : <em>variadic</em> functions (<code>...</code> operation in a function signature) and the fact 75 that <code>func</code> are <em>first class citizen</em>. This looks more or less like that.</p> 76 <div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">type</span> <span class="nx">Config</span> <span class="kd">struct</span> <span class="p">{}</span> 77 78 <span class="kd">func</span> <span class="nf">MyFn</span><span class="p">(</span><span class="nx">ops</span> <span class="o">...</span><span class="kd">func</span><span class="p">(</span><span class="o">*</span><span class="nx">Config</span><span class="p">))</span> <span class="o">*</span><span class="nx">Config</span> <span class="p">{</span> 79 <span class="nx">c</span> <span class="o">:=</span> <span class="o">&</span><span class="nx">Config</span><span class="p">{}</span> <span class="c1">// with default values 80 </span><span class="c1"></span> <span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">op</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">ops</span> <span class="p">{</span> 81 <span class="nf">op</span><span class="p">(</span><span class="nx">c</span><span class="p">)</span> 82 <span class="p">}</span> 83 <span class="k">return</span> <span class="nx">c</span> 84 <span class="p">}</span> 85 86 <span class="c1">// Calling it 87 </span><span class="c1"></span><span class="nx">conf</span> <span class="o">:=</span> <span class="nf">MyFn</span><span class="p">(</span><span class="nx">withFoo</span><span class="p">,</span> <span class="nf">withBar</span><span class="p">(</span><span class="s">"baz"</span><span class="p">))</span></code></pre></div> 88 <p>The <code>fs</code> package has too <strong>main</strong> purpose :</p> 89 90 <ol> 91 <li>create folders and files required for testing in a simple manner</li> 92 <li>compare two folders structure (and content)</li> 93 </ol> 94 95 <h2 id="create-folder-structures">Create folder structures</h2> 96 97 <p>Sometimes, you need to create folder structures (and files) in tests. Doing <code>i/o</code> work 98 takes time so try to limit the number of tests that needs to do that, especially in unit 99 tests. Doing it in tests adds a bit of boilerplate that could be avoid. As stated <a href="/posts/2017-01-01-go-testing-functionnal-builders/">before</a> :</p> 100 101 <blockquote> 102 <p>One of the most important characteristic of a unit test (and any type of test really) is 103 <strong>readability</strong>. This means it should be easy to read but most importantly it should <strong>clearly 104 show the intent</strong> of the test. The setup (and cleanup) of the tests should be as small as 105 possible to avoid the noise.</p> 106 </blockquote> 107 108 <p>In a test you usually end up using <code>ioutil</code> function to create what you need. This looks 109 somewhat like the following.</p> 110 <div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">path</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">ioutil</span><span class="p">.</span><span class="nf">TempDir</span><span class="p">(</span><span class="s">""</span><span class="p">,</span> <span class="s">"bar"</span><span class="p">)</span> 111 <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> <span class="c1">// or using `assert.Assert` 112 </span><span class="c1"></span> <span class="nx">t</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> 113 <span class="p">}</span> 114 <span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">os</span><span class="p">.</span><span class="nf">Mkdir</span><span class="p">(</span><span class="nx">filepath</span><span class="p">.</span><span class="nf">Join</span><span class="p">(</span><span class="nx">path</span><span class="p">,</span> <span class="s">"foo"</span><span class="p">),</span> <span class="nx">os</span><span class="p">.</span><span class="nf">FileMode</span><span class="p">(</span><span class="mo">0755</span><span class="p">));</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> 115 <span class="nx">t</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> 116 <span class="p">}</span> 117 <span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">ioutil</span><span class="p">.</span><span class="nf">WriteFile</span><span class="p">(</span><span class="nx">filepath</span><span class="p">.</span><span class="nf">Join</span><span class="p">(</span><span class="nx">path</span><span class="p">,</span> <span class="s">"foo"</span><span class="p">,</span> <span class="s">"bar"</span><span class="p">),</span> <span class="p">[]</span><span class="nb">byte</span><span class="p">(</span><span class="s">"content"</span><span class="p">),</span> <span class="nx">os</span><span class="p">.</span><span class="nf">FileMode</span><span class="p">(</span><span class="mo">0777</span><span class="p">));</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> 118 <span class="nx">t</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> 119 <span class="p">}</span> 120 <span class="k">defer</span> <span class="nx">os</span><span class="p">.</span><span class="nf">RemoveAll</span><span class="p">(</span><span class="nx">path</span><span class="p">)</span> <span class="o">//</span> <span class="nx">to</span> <span class="nx">clean</span> <span class="nx">up</span> <span class="nx">at</span> <span class="nx">the</span> <span class="nx">end</span> <span class="nx">of</span> <span class="nx">the</span> <span class="nx">test</span></code></pre></div> 121 <p>The <code>fs</code> package intends to help reduce the noise and comes with a bunch function to create 122 folder structure :</p> 123 124 <ul> 125 <li>two main function <code>NewFile</code> and <code>NewDir</code></li> 126 <li>a bunch of <em>operators</em> : <code>WithFile</code>, <code>WithDir</code>, …</li> 127 </ul> 128 <div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">func</span> <span class="nf">NewDir</span><span class="p">(</span><span class="nx">t</span> <span class="nx">assert</span><span class="p">.</span><span class="nx">TestingT</span><span class="p">,</span> <span class="nx">prefix</span> <span class="kt">string</span><span class="p">,</span> <span class="nx">ops</span> <span class="o">...</span><span class="nx">PathOp</span><span class="p">)</span> <span class="o">*</span><span class="nx">Dir</span> <span class="p">{</span> 129 <span class="c1">// … 130 </span><span class="c1"></span><span class="p">}</span> 131 132 <span class="kd">func</span> <span class="nf">NewFile</span><span class="p">(</span><span class="nx">t</span> <span class="nx">assert</span><span class="p">.</span><span class="nx">TestingT</span><span class="p">,</span> <span class="nx">prefix</span> <span class="kt">string</span><span class="p">,</span> <span class="nx">ops</span> <span class="o">...</span><span class="nx">PathOp</span><span class="p">)</span> <span class="o">*</span><span class="nx">File</span> <span class="p">{</span> 133 <span class="c1">// … 134 </span><span class="c1"></span><span class="p">}</span></code></pre></div> 135 <p>The <code>With*</code> function are all satisfying the <code>PathOp</code> interface, making <code>NewFile</code> and 136 <code>NewDir</code> extremely composable. Let’s first see how our above example would look like using 137 the <code>fs</code> package, and then, we’ll look a bit more at the main <code>PathOp</code> function…</p> 138 <div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">dir</span> <span class="o">:=</span> <span class="nx">fs</span><span class="p">.</span><span class="nf">NewDir</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="s">"bar"</span><span class="p">,</span> <span class="nx">fs</span><span class="p">.</span><span class="nf">WithDir</span><span class="p">(</span><span class="s">"foo"</span><span class="p">,</span> 139 <span class="nx">fs</span><span class="p">.</span><span class="nf">WithFile</span><span class="p">(</span><span class="s">"bar"</span><span class="p">,</span> <span class="nx">fs</span><span class="p">.</span><span class="nf">WithContent</span><span class="p">(</span><span class="s">"content"</span><span class="p">),</span> <span class="nx">fs</span><span class="p">.</span><span class="nf">WithMode</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nf">FileMode</span><span class="p">(</span><span class="mo">0777</span><span class="p">))),</span> 140 <span class="p">))</span> 141 <span class="k">defer</span> <span class="nx">dir</span><span class="p">.</span><span class="nf">Remove</span><span class="p">()</span></code></pre></div> 142 <p>It’s clean and simple to read. The intent is well described and there is not that much of 143 noise. <code>fs</code> functions tends to have <em>sane</em> and <em>safe</em> defaults value (for <code>os.FileMode</code> 144 for example). Let’s list the main, useful, <code>PathOp</code> provided by <code>gotest.tools/fs</code>.</p> 145 146 <ul> 147 <li><code>WithDir</code> creates a sub-directory in the directory at path.</li> 148 <li><code>WithFile</code> creates a file in the directory at path with content.</li> 149 <li><code>WithSymlink</code> creates a symlink in the directory which links to target. Target must be a 150 path relative to the directory.</li> 151 <li><code>WithHardlink</code> creates a link in the directory which links to target. Target must be a 152 path relative to the directory.</li> 153 <li><code>WithContent</code> and <code>WWithBytes</code> write content to a file at Path (from a <code>string</code> or a 154 <code>[]byte</code> slice).</li> 155 <li><code>WithMode</code> sets the file mode on the directory or file at path.</li> 156 <li><code>WithTimestamps</code> sets the access and modification times of the file system object at 157 path.</li> 158 <li><code>FromDir</code> copies the directory tree from the source path into the new Dir. This is 159 pretty useful when you have a huge folder structure already present in you <code>testdata</code> 160 folder or elsewhere.</li> 161 <li><code>AsUser</code> changes ownership of the file system object at Path.</li> 162 </ul> 163 164 <p>Also, note that <code>PathOp</code> being an function type, you can provide your own implementation 165 for specific use-cases. Your function just has to satisfy <code>PathOp</code> signature.</p> 166 <div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">type</span> <span class="nx">PathOp</span> <span class="kd">func</span><span class="p">(</span><span class="nx">path</span> <span class="nx">Path</span><span class="p">)</span> <span class="kt">error</span></code></pre></div> 167 <h2 id="compare-folder-structures">Compare folder structures</h2> 168 169 <p>Sometimes, the code you’re testing is creating a folder structure, and you would like to 170 be able to tests that, with the given arguments, it creates the specified structure. <code>fs</code> 171 allows you to do that too.</p> 172 173 <p>The package provides a <code>Equal</code> function, which returns a <code>Comparison</code>, that the <a href="/posts/2018-08-16-gotest-tools-assertions/"><code>assert</code></a> 174 package understand. It works by comparing a <code>Manifest</code> type provided by the test and a 175 <code>Manifest</code> representation of the specified folder.</p> 176 177 <blockquote> 178 <p>Equal compares a directory to the expected structured described by a manifest and returns 179 success if they match. If they do not match the failure message will contain all the 180 differences between the directory structure and the expected structure defined by the 181 Manifest.</p> 182 </blockquote> 183 184 <p>A <code>Manifest</code> stores the expected structure and properties of files and directories in a 185 file-system. You can create a <code>Manifest</code> using either the functions <code>Expected</code> or 186 <code>ManifestFromDir</code>.</p> 187 188 <p>We’re going to focus on the <code>Expected</code> function, as <code>ManifestFromDir</code> does pretty much 189 what you would expected : it takes the specified path, and returns a <code>Manifest</code> that 190 represent this folder.</p> 191 <div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">func</span> <span class="nf">Expected</span><span class="p">(</span><span class="nx">t</span> <span class="nx">assert</span><span class="p">.</span><span class="nx">TestingT</span><span class="p">,</span> <span class="nx">ops</span> <span class="o">...</span><span class="nx">PathOp</span><span class="p">)</span> <span class="nx">Manifest</span></code></pre></div> 192 <p><code>Expected</code> is close to <code>NewDir</code> function : it takes the same <code>PathOp</code> functional 193 arguments. This makes creating a <code>Manifest</code> straightforward, as it’s working the same. Any 194 function that satisfy <code>PathOp</code> can be used for <code>Manifest</code> the exact same way you’re using 195 them on <code>fs.NewDir</code>.</p> 196 197 <p>There is a few additional functions that are only useful with <code>Manifest</code> :</p> 198 199 <ul> 200 <li><code>MatchAnyFileContent</code> updates a Manifest so that the file at path may contain any content.</li> 201 <li><code>MatchAnyFileMode</code> updates a Manifest so that the resource at path will match any file mode.</li> 202 <li><code>MatchContentIgnoreCarriageReturn</code> ignores cariage return discrepancies.</li> 203 <li><code>MatchExtraFiles</code> updates a Manifest to allow a directory to contain unspecified files.</li> 204 </ul> 205 <div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">path</span> <span class="o">:=</span> <span class="nf">operationWhichCreatesFiles</span><span class="p">()</span> 206 <span class="nx">expected</span> <span class="o">:=</span> <span class="nx">fs</span><span class="p">.</span><span class="nf">Expected</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> 207 <span class="nx">fs</span><span class="p">.</span><span class="nf">WithFile</span><span class="p">(</span><span class="s">"one"</span><span class="p">,</span> <span class="s">""</span><span class="p">,</span> 208 <span class="nx">fs</span><span class="p">.</span><span class="nf">WithBytes</span><span class="p">(</span><span class="nx">golden</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="s">"one.golden"</span><span class="p">)),</span> 209 <span class="nx">fs</span><span class="p">.</span><span class="nf">WithMode</span><span class="p">(</span><span class="mo">0600</span><span class="p">)),</span> 210 <span class="nx">fs</span><span class="p">.</span><span class="nf">WithDir</span><span class="p">(</span><span class="s">"data"</span><span class="p">,</span> 211 <span class="nx">fs</span><span class="p">.</span><span class="nf">WithFile</span><span class="p">(</span><span class="s">"config"</span><span class="p">,</span> <span class="s">""</span><span class="p">,</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">MatchAnyFileContent</span><span class="p">)),</span> 212 <span class="p">)</span> 213 214 <span class="nx">assert</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">fs</span><span class="p">.</span><span class="nf">Equal</span><span class="p">(</span><span class="nx">path</span><span class="p">,</span> <span class="nx">expected</span><span class="p">))</span></code></pre></div> 215 <p>The following example compares the result of <code>operationWhichCreatesFiles</code> to the expected 216 <code>Manifest</code>. As you can see it also integrates well with other part of the <code>gotest.tools</code> 217 library, with the <a href="/posts/2018-09-06-gotest-tools-golden/"><code>golden</code> package</a> in this example.</p> 218 219 <h2 id="conclusion">Conclusion…</h2> 220 221 <p>… that’s a wrap. In my opinion, this is one the most useful package provided by 222 <code>gotest.tools</code> after <code>assert</code>. It allows to create simple or complex folder structure 223 without the noise that usually comes with it.</p> 224 225 226 </article> 227 <hr /> 228 <div class="prev-next"> 229 230 <a class="paging-link prev" href="/posts/2018-09-18-gotest-tools-icmd/" title="Golang testing — gotest.tools icmd">← Previous post</a> 231 232 233 234 <a class="paging-link next" href="/posts/2018-09-06-gotest-tools-golden/" title="Golang testing — gotest.tools golden">Next post →</a> 235 236 </div> 237 238 </div> 239 </div> 240 241 <footer> 242 <nav> 243 244 <a href="/">home</a> 245 <span class="text-muted"> | </span> 246 247 <a href="/about">about</a> 248 <span class="text-muted"> | </span> 249 250 <a href="/archive">archive</a> 251 <span class="text-muted"> | </span> 252 253 <a href="/categories">categories</a> 254 <span class="text-muted"> | </span> 255 256 <a href="/tags">tags</a> 257 <span class="text-muted"> | </span> 258 259 <a href="https://twitter.com/vdemeest">twitter</a> 260 <span class="text-muted"> | </span> 261 262 <a href="https://github.com/vdemeester">github</a> 263 <span class="text-muted"> | </span> 264 265 <a href="https://vincent.demeester.fr/index.xml">rss</a> 266 </nav> 267 <br/> 268 <address> 269 <span class="copyright"> 270 Content and design by Vincent Demeester 271 (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) 272 </span><br /> 273 <span class="engine"> 274 Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> 275 </span> 276 </address> 277 </footer> 278 </body> 279