草庐IT

unit-testing - 学习编写单元测试

coder 2023-06-29 原文

我正在尝试学习如何为我的代码编写测试以便编写更好的代码,但我似乎最难弄清楚如何实际测试我编写的一些代码。我读了很多教程,其中大部分似乎只涵盖了将两个数字相加或模拟某些数据库或服务器的函数。

我在下面编写了一个简单的函数,它将文本模板和 CSV 文件作为输入并使用 CSV 的值执行模板。我已经通过反复试验、传递文件和打印值“测试”了代码,但我想学习如何为它编写适当的测试。我觉得学习测试自己的代码会帮助我更快更好地理解和学习。感谢您的帮助。

// generateCmds generates configuration commands from a text template using
// the values from a CSV file. Multiple commands in the text template must
// be delimited by a semicolon. The first row of the CSV file is assumed to
// be the header row and the header values are used for key access in the
// text template.
func generateCmds(cmdTmpl string, filename string) ([]string, error) {
    t, err := template.New("cmds").Parse(cmdTmpl)
    if err != nil {
        return nil, fmt.Errorf("parsing template: %v", err)
    }

    f, err := os.Open(filename)
    if err != nil {
        return nil, fmt.Errorf("reading file: %v", err)
    }
    defer f.Close()

    records, err := csv.NewReader(f).ReadAll()
    if err != nil {
        return nil, fmt.Errorf("reading records: %v", err)
    }
    if len(records) == 0 {
        return nil, errors.New("no records to process")
    }

    var (
        b    bytes.Buffer
        cmds []string
        keys = records[0]
        vals = make(map[string]string, len(keys))
    )

    for _, rec := range records[1:] {
        for k, v := range rec {
            vals[keys[k]] = v
        }
        if err := t.Execute(&b, vals); err != nil {
            return nil, fmt.Errorf("executing template: %v", err)
        }
        for _, s := range strings.Split(b.String(), ";") {
            if cmd := strings.TrimSpace(s); cmd != "" {
                cmds = append(cmds, cmd)
            }
        }
        b.Reset()
    }
    return cmds, nil
}

编辑:感谢您迄今为止提出的所有建议!我的问题被标记为过于宽泛,因此我对我的示例有一些具体问题。

  1. 测试表在这样的函数中有用吗?而且,如果是这样,测试结构是否需要包含返回的 cmds 字符串 slice err 的值?例如:
type tmplTest struct {
    name     string   // test name
    tmpl     string   // the text template
    filename string   // CSV file with template values
    expected []string // expected configuration commands
    err      error    // expected error
}
  1. 您如何处理应该针对特定测试用例返回的错误?例如,如果遇到错误,os.Open() 将返回类型为 *PathError 的错误。如何初始化与 os.Open() 返回的等效的 *PathErrortemplate.Parse()template.Execute() 等的思路相同

编辑 2: 下面是我想出的一个测试函数。我在第一次编辑时提出的两个问题仍然有效。

package cmd

import (
    "testing"
    "strings"
    "path/filepath"
)

type tmplTest struct {
    name     string   // test name
    tmpl     string   // text template to execute
    filename string   // CSV containing template text values
    cmds     []string // expected configuration commands
}

var tests = []tmplTest{
    {"empty_error", ``, "", nil},
    {"file_error", ``, "fake_file.csv", nil},
    {"file_empty_error", ``, "empty.csv", nil},
    {"file_fmt_error", ``, "fmt_err.csv", nil},
    {"template_fmt_error", `{{ }{{`, "test_values.csv", nil},
    {"template_key_error", `{{.InvalidKey}}`, "test_values.csv", nil},
}

func TestGenerateCmds(t *testing.T) {
    for _, tc := range tests {
        t.Run(tc.name, func(t *testing.T) {
            cmds, err := generateCmds(tc.tmpl, filepath.Join("testdata", tc.filename))
            if err != nil {
                // Unexpected error. Fail the test.
                if !strings.Contains(tc.name, "error") {
                    t.Fatal(err)
                }
                // TODO: Otherwise, check that the function failed at the expected point.
            }
            if tc.cmds == nil && cmds != nil {
                t.Errorf("expected no commands; got %d", len(cmds))
            }
            if len(cmds) != len(tc.cmds) {
                t.Errorf("expected %d commands; got %d", len(tc.cmds), len(cmds))
            }
            for i := range cmds {
                if cmds[i] != tc.cmds[i] {
                    t.Errorf("expected %q; got %q", tc.cmds[i], cmds[i])
                }
            }
        })
    }
}

最佳答案

你基本上需要有一些示例文件,其中包含你要测试的内容,然后在你的测试代码中你可以调用 generateCmds 函数传入模板字符串和文件,然后验证结果如您所愿。

它与您可能在更简单的情况下看到的示例没有太大区别。

您可以将这些文件放在同一包内的 testdata 文件夹下(testdata 是 Go 工具在构建期间将忽略的特殊名称)。

然后你可以这样做:

func TestCSVProcessing(t *testing.T) {
    templateStr := `<your template here>`
    testFile := "testdata/yourtestfile.csv"
    result, err := generateCmds(templateStr, testFile)
    if err != nil {
        // fail the test here, unless you expected an error with this file
    }
    // compare the "result" contents with what you expected
    // failing the test if it does not match
}

编辑

关于你后面添加的具体问题:

Would a test table be useful in a function like this? And, if so, would the test struct need to include the returned cmds string slice and the value of err?

是的,包含要返回的预期字符串和预期错误(如果有)是有意义的。

How do you handle errors that are supposed to be returned for specific test cases? For example, os.Open() returns an error of type *PathError if an error is encountered. How do I initialize a *PathError that is equivalent to the one returned by os.Open()?

我认为您无法为每种情况“初始化”一个等效错误。有时,库可能会使用内部类型来解决错误,从而使这成为不可能。最简单的方法是使用 Error() 方法中返回的相同值“初始化”一个常规错误,然后只需将返回错误的 Error() 值与预期值进行比较.

关于unit-testing - 学习编写单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51181347/

有关unit-testing - 学习编写单元测试的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby-on-rails - rails : "missing partial" when calling 'render' in RSpec test - 2

    我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou

  3. ruby - 在 Ruby 中编写命令行实用程序 - 2

    我想用ruby​​编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序

  4. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  5. ruby - Ruby 的 Hash 在比较键时使用哪种相等性测试? - 2

    我有一个围绕一些对象的包装类,我想将这些对象用作散列中的键。包装对象和解包装对象应映射到相同的键。一个简单的例子是这样的:classAattr_reader:xdefinitialize(inner)@inner=innerenddefx;@inner.x;enddef==(other)@inner.x==other.xendenda=A.new(o)#oisjustanyobjectthatallowso.xb=A.new(o)h={a=>5}ph[a]#5ph[b]#nil,shouldbe5ph[o]#nil,shouldbe5我试过==、===、eq?并散列所有无济于事。

  6. ruby - RSpec - 使用测试替身作为 block 参数 - 2

    我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

  7. ruby - Sinatra:运行 rspec 测试时记录噪音 - 2

    Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/

  8. ruby-on-rails - 迷你测试错误 : "NameError: uninitialized constant" - 2

    我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test

  9. ruby - 即使失败也继续进行多主机测试 - 2

    我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r

  10. ruby-on-rails - 如何使辅助方法在 Rails 集成测试中可用? - 2

    我在app/helpers/sessions_helper.rb中有一个帮助程序文件,其中包含一个方法my_preference,它返回当前登录用户的首选项。我想在集成测试中访问该方法。例如,这样我就可以在测试中使用getuser_path(my_preference)。在其他帖子中,我读到这可以通过在测试文件中包含requiresessions_helper来实现,但我仍然收到错误NameError:undefinedlocalvariableormethod'my_preference'.我做错了什么?require'test_helper'require'sessions_hel

随机推荐