<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: motemen</title>
    <description>The latest articles on DEV Community by motemen (@motemen).</description>
    <link>https://dev.to/motemen</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F974121%2Fae356f60-a2e3-43b2-9349-12786c137dd0.png</url>
      <title>DEV Community: motemen</title>
      <link>https://dev.to/motemen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/motemen"/>
    <language>en</language>
    <item>
      <title>Locating failing test cases in table-driven tests in Go</title>
      <dc:creator>motemen</dc:creator>
      <pubDate>Fri, 20 Oct 2023 04:59:05 +0000</pubDate>
      <link>https://dev.to/motemen/locating-failing-test-cases-in-table-driven-tests-in-go-77m</link>
      <guid>https://dev.to/motemen/locating-failing-test-cases-in-table-driven-tests-in-go-77m</guid>
      <description>&lt;p&gt;In Go, &lt;a href="https://dave.cheney.net/2019/05/07/prefer-table-driven-tests"&gt;table-driven testing&lt;/a&gt; is widely known as a good practice to follow.&lt;/p&gt;

&lt;p&gt;Dividing behaviors and data is great, but with current testing in Go, locating a failing test case is not straightforward. Instead, we always get the line number of the failing test assertion, which isn't helpful.&lt;/p&gt;

&lt;p&gt;Check out the example below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;eg&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"testing"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestExample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;testcases&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
        &lt;span class="n"&gt;sum&lt;/span&gt;  &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="p"&gt;}{&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"1+1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;99&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"2+2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"4+4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="c"&gt;// [long lines of code...]&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"1024+1024"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testcase&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;testcases&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testcase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;testcase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;testcase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testcase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expected %d, got %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we run the test, we will get the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--- FAIL: TestExample (0.00s)
    --- FAIL: TestExample/1+1 (0.00s)
        eg_test.go:100: expected 99, got 2
    --- FAIL: TestExample/1024+1024 (0.00s)
        eg_test.go:100: expected -1, got 2048 # &amp;lt;-- this is the line number of the assertion, not the test case
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same line number regardless of which test case failed. What we actually want is to see the line number where the test case itself failed. The test name barely tells us which test case failed, but it's not enough.&lt;br&gt;
So, to solve this problem, I have crafted a small helper library: go-testutil/dataloc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pkg.go.dev/github.com/motemen/go-testutil/dataloc"&gt;https://pkg.go.dev/github.com/motemen/go-testutil/dataloc&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Using the above example, let's modify the code a bit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;@@ -1,6 +1,7 @@&lt;/span&gt;
 package eg

 import "testing"
&lt;span class="gi"&gt;+import "github.com/motemen/go-testutil/dataloc"
&lt;/span&gt;
 func TestExample(t *testing.T) {
        testcases := []struct {
&lt;span class="p"&gt;@@ -96,7 +97,7 @@&lt;/span&gt;
        for _, testcase := range testcases {
                t.Run(testcase.name, func(t *testing.T) {
                        if got, expected := testcase.a+testcase.b, testcase.sum; got != expected {
&lt;span class="gd"&gt;-                               t.Errorf("expected %d, got %d", expected, got)
&lt;/span&gt;&lt;span class="gi"&gt;+                               t.Errorf("expected %d, got %d, test case at %s", expected, got, dataloc.L(testcase.name))
&lt;/span&gt;                        }
                })
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...and we will get the following output, which shows the &lt;em&gt;location of failing test case, not assertion.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--- FAIL: TestExample (0.00s)
    --- FAIL: TestExample/1+1 (0.00s)
        eg_test.go:100: expected 99, got 2, test case at eg_test.go:12
    --- FAIL: TestExample/1024+1024 (0.00s)
        eg_test.go:100: expected -1, got 2048, test case at eg_test.go:95
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Under the hood
&lt;/h2&gt;

&lt;p&gt;To achieve this magic, the library uses &lt;a href="https://pkg.go.dev/runtime#Caller"&gt;runtime.Caller&lt;/a&gt; to get the caller's file and line number, and then uses &lt;a href="https://pkg.go.dev/go/ast"&gt;go/ast&lt;/a&gt; to parse the file and find the location of the corresponding test case.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find the call of the form &lt;code&gt;dataloc.L(testcase.name)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Find the &lt;code&gt;for ... range testcases&lt;/code&gt; that is the origin of &lt;code&gt;testcase&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Find the definition of &lt;code&gt;testcases&lt;/code&gt;`&lt;/li&gt;
&lt;li&gt;Find the definition of the test case whose name is the runtime value of testcase.name ("1+1" etc.)

&lt;ul&gt;
&lt;li&gt;So if the test case name is not statically determined, it cannot be identified.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Happy table-driven testing!&lt;/p&gt;

</description>
      <category>go</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
