www

My personal website(s)
Log | Files | Refs

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&rsquo;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 := &amp;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&rsquo;s first see how our above example would look like using
    139 the <code>fs</code> package, and then, we&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;s working the same. Any
    229 function that satisfy <code>PathOp</code> can be used for <code>Manifest</code> the exact same way you&rsquo;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&rsquo;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>