Я пытаюсь научиться писать тесты для своего кода, чтобы писать более качественный код, но мне, кажется, сложнее всего понять, как на самом деле протестировать некоторый код, который я написал. Я прочитал так много учебных пособий, большинство из которых, кажется, охватывают только функции, которые добавляют два числа или издеваются над базой данных или сервером.
У меня есть простая функция, которую я написал ниже, которая принимает текстовый шаблон и файл 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
}
Редактировать: Спасибо за все предложения до сих пор! Мой вопрос был помечен как слишком широкий, поэтому у меня есть несколько конкретных вопросов относительно моего примера.
- Была бы полезна тестовая таблица в такой функции? И если да, то должна ли тестовая структура включать возвращаемый
cmds
фрагмент строки и значение 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
}
- Как вы обрабатываете ошибки, которые предполагается возвращать для конкретных тестовых случаев? Например,
os.Open()
возвращает ошибку типа *PathError
, если обнаружена ошибка. Как инициализировать *PathError
, который эквивалентен тому, который возвращается os.Open()
? Та же идея для template.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])
}
}
})
}
}