home

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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&rsquo;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">&amp;</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">&#34;baz&#34;</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">&#34;&#34;</span><span class="p">,</span> <span class="s">&#34;bar&#34;</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">&#34;foo&#34;</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">&#34;foo&#34;</span><span class="p">,</span> <span class="s">&#34;bar&#34;</span><span class="p">),</span> <span class="p">[]</span><span class="nb">byte</span><span class="p">(</span><span class="s">&#34;content&#34;</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&rsquo;s first see how our above example would look like using
    137 the <code>fs</code> package, and then, we&rsquo;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">&#34;bar&#34;</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">&#34;foo&#34;</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">&#34;bar&#34;</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">&#34;content&#34;</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&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;s working the same. Any
    194 function that satisfy <code>PathOp</code> can be used for <code>Manifest</code> the exact same way you&rsquo;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">&#34;one&#34;</span><span class="p">,</span> <span class="s">&#34;&#34;</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">&#34;one.golden&#34;</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">&#34;data&#34;</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">&#34;config&#34;</span><span class="p">,</span> <span class="s">&#34;&#34;</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&rsquo;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