{"id":2297,"date":"2019-10-09T20:36:23","date_gmt":"2019-10-09T19:36:23","guid":{"rendered":"https:\/\/denbeke.be\/blog\/?p=2297"},"modified":"2019-10-09T20:38:00","modified_gmt":"2019-10-09T19:38:00","slug":"data-driven-testing-in-go-aka-table-testing-or-parameterized-testing","status":"publish","type":"post","link":"https:\/\/denbeke.be\/blog\/programming\/data-driven-testing-in-go-aka-table-testing-or-parameterized-testing\/","title":{"rendered":"Data-Driven Testing in Go aka Table Testing or Parameterized Testing"},"content":{"rendered":"\n<p>When writing tests, we want to focus as much as possible on the actual test cases and test data, and not on implementing the individual cases.\nThe less time you spend in writing code to implement a test-case, the more time you can spend on actual test data.<\/p>\n\n\n\n<p>This is where data-driven testing comes in handy. Data-driven testing splits the test data from the test logic.  <\/p>\n\n\n\n<h3 id=\"whatisdatadrivetesting\">What is Data-Drive Testing?<\/h3>\n\n\n\n<p>So what is data-driven testing exactly?\nIn data-driven testing you reuse the same test script\/invoker with multiple inputs.<\/p>\n\n\n\n<p>To do so you need:<\/p>\n\n\n\n<ul><li>Have test-data in files. For each test you should have:\n\n\n<ul>\n<li>Description of the test<\/li>\n\n<li>Input for the test<\/li>\n\n<li>Expected output<\/li><\/ul>\n<\/li><li>Run the same test script on each of the input data.<\/li><li>Check whether the actual output of the test script matches the expected output you defined in the input file.<\/li><\/ul>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter is-resized\"><img loading=\"lazy\" src=\"https:\/\/denbeke.be\/blog\/wp-content\/uploads\/2019\/10\/Data-Driven-Testing-1024x509.png\" alt=\"\" class=\"wp-image-2296\" width=\"512\" height=\"255\" srcset=\"https:\/\/denbeke.be\/blog\/wp-content\/uploads\/2019\/10\/Data-Driven-Testing-1024x509.png 1024w, https:\/\/denbeke.be\/blog\/wp-content\/uploads\/2019\/10\/Data-Driven-Testing-300x149.png 300w, https:\/\/denbeke.be\/blog\/wp-content\/uploads\/2019\/10\/Data-Driven-Testing-768x382.png 768w, https:\/\/denbeke.be\/blog\/wp-content\/uploads\/2019\/10\/Data-Driven-Testing-1568x779.png 1568w, https:\/\/denbeke.be\/blog\/wp-content\/uploads\/2019\/10\/Data-Driven-Testing.png 2004w\" sizes=\"(max-width: 512px) 100vw, 512px\" \/><figcaption>Overview of Data-Driven Testing<\/figcaption><\/figure><\/div>\n\n\n\n<p><em>You probably know data-driven testing already as &#8220;Table Testing &#8220;or &#8220;Parameterized&#8221; testing<\/em><\/p>\n\n\n\n<h3 id=\"howtododatadriventestingingo\">How to Do Data-Driven Testing in Go<\/h3>\n\n\n\n<p>But how do you implement data-driven testing in Go?<\/p>\n\n\n\n<p><em>The examples I use originate from tests I wrote to test Sanity&#8217;s patching logic on documents. This means we need an input document, a patching function to apply on this document, and an expected output after the patching is applied.<\/em><\/p>\n\n\n\n<h4 id=\"testinputfile\">Test Input File<\/h4>\n\n\n\n<p>I opted to put the test input in Yaml files. Each file contains a list of (related) test cases.<\/p>\n\n\n\n<ul><li><code>description<\/code> of the test is string.<\/li><li><code>input<\/code>, <code>patch<\/code>, <code>expected_output<\/code>, are multi-line strings, which contain JSON. This can be of course anything, but in my tests I needed JSON. <\/li><\/ul>\n\n\n\n<p>An example of such an input data file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- description: inc\n  input: |\n    {\n      \"x\": 0\n    }\n\n  patch: |\n    {\n      \"patch\": {\n        \"id\": \"123\",\n        \"ifRevisionID\": \"666\",\n        \"inc\": {\n          \"x\": 1\n        }\n      }\n    }\n\n  expected_output: |\n    {\n      \"x\": 1\n    }\n<\/code><\/pre>\n\n\n\n<h4 id=\"parsefile\">Parse File<\/h4>\n\n\n\n<p>Creating a datafile isn&#8217;t enough, it must also be parsed. To do so I created a custom <code>UnmarshalYAML<\/code> function to implement the Yaml Unmarshaller interface. So that it gets automatically picked up by the <a href=\"https:\/\/github.com\/go-yaml\/yaml\">go-yaml\/yaml<\/a> package when trying to unmarshall it. I left this implementation out because it is very specific to what we do in our tests at Sanity.<\/p>\n\n\n\n<p>The datafile is represented in Go with a type alias and a struct as follows:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ A TestFile contains a list of test cases\ntype TestFile []TestCase\n\n\/\/ TestCase represents a single patch test case.\ntype TestCase struct {\n    Description    string                `yaml:\"description\"`\n    Input          attributes.Attributes `yaml:\"input\"`\n    Patch          mutation.Patch        `yaml:\"patch\"`\n    ExpectedOutput attributes.Attributes `yaml:\"expected_output\"`\n}\n<\/code><\/pre>\n\n\n\n<h4 id=\"executefile\">Execute File<\/h4>\n\n\n\n<p>To test the patching mechanism we have a testing function which takes the <code>input<\/code>, <code>patch<\/code> and <code>expected_output<\/code> as parameters:<\/p>\n\n\n\n<pre class=\"wp-block-code go language-go golang\"><code>func testPatchPerform(\n    t *testing.T,\n    patch mutation.Patch,\n    input attributes.Attributes,\n    expectedOutput attributes.Attributes\n) {\n\n    \/\/ ...\n\n}\n<\/code><\/pre>\n\n\n\n<p>So know we need to call it for each test case from each test data file. <br>\nTo do so I created a test helper which parses a test file and runs all the test cases in it (with the above helper). For each test-case I added a <code>t.Run()<\/code> which discribes the test being executed. This simplifies debugging a lot.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>func testPatchPerformFromFile(t *testing.T, file string) {\n\n    yamlInput, err := ioutil.ReadFile(file)\n    require.NoError(t, err)\n\n    testFile := TestFile{}\n\n    err = yaml.Unmarshal(yamlInput, &amp;testFile)\n    require.NoError(t, err)\n\n    for _, testCase := range testFile {\n        t.Run(file+\"\/\"+testCase.Description, func(t *testing.T) {\n            testPatchPerform(t, testCase.Patch, testCase.Input, testCase.ExpectedOutput)\n        })\n    }\n\n}\n<\/code><\/pre>\n\n\n\n<p>Now we just need to go over all the test files in our data directory and execute the <code>testPatchPerformFromFile<\/code> for each file.\nSo the actual top-level test function that will be executed by <code>go test<\/code> looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>func TestPatchPerformFromTestDataDirectory(t *testing.T) {\n\n    err := filepath.Walk(\".\/testdata\/\", func(path string, info os.FileInfo, err error) error {\n\n        if err != nil {\n            return err\n        }\n        if info.IsDir() {\n            return nil\n        }\n\n        if strings.Contains(info.Name(), \"patch_\") {\n            testPatchPerformFromFile(t, path)\n        }\n\n        return nil\n    })\n    require.NoError(t, err)\n}\n<\/code><\/pre>\n\n\n\n<h4 id=\"testoutput\">Test Output<\/h4>\n\n\n\n<p>Test about in verbose mode looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>--- PASS: TestPatchPerformFromTestDataDirectory (0.00s)\n    patch_increment.yml\/inc (0.00s)\n    --- PASS: TestPatchPerformFromTestDataDirectory\/testdata\/patch_increment.yml\/inc_variable_number (0.00s)\n    --- PASS: TestPatchPerformFromTestDataDirectory\/testdata\/patch_increment.yml\/dec (0.00s)\n    --- PASS: TestPatchPerformFromTestDataDirectory\/testdata\/patch_increment.yml\/dec_variable_number (0.00s)\n<\/code><\/pre>\n\n\n\n<h3 id=\"conclusion\">Conclusion<\/h3>\n\n\n\n<p>With this data-driven testing approach we can easily write tests. We implement the test script only once, and after that we can add as many data files as possible.\nNeed a new test case? Just create a new case in a Yaml file and run the tests again with <code>go test<\/code>.<\/p>\n\n\n\n<p>Data-driven testing also makes it possible to reuse test-cases in other places\/languages in your stack since the Yaml test input is language-independent.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>When writing tests, we want to focus as much as possible on the actual test cases and test data, and not on implementing the individual cases. The less time you spend in writing code to implement a test-case, the more time you can spend on actual test data. This is where data-driven testing comes in [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[235],"tags":[231,232,277,282],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v15.6.2 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Data-Driven Testing in Go aka Table Testing or Parameterized Testing &ndash; DenBeke<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/denbeke.be\/blog\/programming\/data-driven-testing-in-go-aka-table-testing-or-parameterized-testing\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Data-Driven Testing in Go aka Table Testing or Parameterized Testing &ndash; DenBeke\" \/>\n<meta property=\"og:description\" content=\"When writing tests, we want to focus as much as possible on the actual test cases and test data, and not on implementing the individual cases. The less time you spend in writing code to implement a test-case, the more time you can spend on actual test data. This is where data-driven testing comes in [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/denbeke.be\/blog\/programming\/data-driven-testing-in-go-aka-table-testing-or-parameterized-testing\/\" \/>\n<meta property=\"og:site_name\" content=\"DenBeke\" \/>\n<meta property=\"article:published_time\" content=\"2019-10-09T19:36:23+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2019-10-09T19:38:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/denbeke.be\/blog\/wp-content\/uploads\/2019\/10\/Data-Driven-Testing-1024x509.png\" \/>\n<meta name=\"twitter:card\" content=\"summary\" \/>\n<meta name=\"twitter:creator\" content=\"@MthsBk\" \/>\n<meta name=\"twitter:site\" content=\"@MthsBk\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\">\n\t<meta name=\"twitter:data1\" content=\"4 minutes\">\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebSite\",\"@id\":\"https:\/\/denbeke.be\/blog\/#website\",\"url\":\"https:\/\/denbeke.be\/blog\/\",\"name\":\"DenBeke\",\"description\":\"Mathias Beke\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":\"https:\/\/denbeke.be\/blog\/?s={search_term_string}\",\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/denbeke.be\/blog\/programming\/data-driven-testing-in-go-aka-table-testing-or-parameterized-testing\/#primaryimage\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/denbeke.be\/blog\/wp-content\/uploads\/2019\/10\/Data-Driven-Testing.png\",\"width\":2004,\"height\":996,\"caption\":\"Overview of Data-Driven Testing\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/denbeke.be\/blog\/programming\/data-driven-testing-in-go-aka-table-testing-or-parameterized-testing\/#webpage\",\"url\":\"https:\/\/denbeke.be\/blog\/programming\/data-driven-testing-in-go-aka-table-testing-or-parameterized-testing\/\",\"name\":\"Data-Driven Testing in Go aka Table Testing or Parameterized Testing &ndash; DenBeke\",\"isPartOf\":{\"@id\":\"https:\/\/denbeke.be\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/denbeke.be\/blog\/programming\/data-driven-testing-in-go-aka-table-testing-or-parameterized-testing\/#primaryimage\"},\"datePublished\":\"2019-10-09T19:36:23+00:00\",\"dateModified\":\"2019-10-09T19:38:00+00:00\",\"author\":{\"@id\":\"https:\/\/denbeke.be\/blog\/#\/schema\/person\/386878f712fe3fe22227216f087772dc\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/denbeke.be\/blog\/programming\/data-driven-testing-in-go-aka-table-testing-or-parameterized-testing\/\"]}]},{\"@type\":\"Person\",\"@id\":\"https:\/\/denbeke.be\/blog\/#\/schema\/person\/386878f712fe3fe22227216f087772dc\",\"name\":\"Mathias Beke\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/denbeke.be\/blog\/#personlogo\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/015ba35e6ce4f5859e3888ca99807575?s=96&d=mm&r=g\",\"caption\":\"Mathias Beke\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","_links":{"self":[{"href":"https:\/\/denbeke.be\/blog\/wp-json\/wp\/v2\/posts\/2297"}],"collection":[{"href":"https:\/\/denbeke.be\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/denbeke.be\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/denbeke.be\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/denbeke.be\/blog\/wp-json\/wp\/v2\/comments?post=2297"}],"version-history":[{"count":3,"href":"https:\/\/denbeke.be\/blog\/wp-json\/wp\/v2\/posts\/2297\/revisions"}],"predecessor-version":[{"id":2300,"href":"https:\/\/denbeke.be\/blog\/wp-json\/wp\/v2\/posts\/2297\/revisions\/2300"}],"wp:attachment":[{"href":"https:\/\/denbeke.be\/blog\/wp-json\/wp\/v2\/media?parent=2297"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/denbeke.be\/blog\/wp-json\/wp\/v2\/categories?post=2297"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/denbeke.be\/blog\/wp-json\/wp\/v2\/tags?post=2297"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}