Как разделить тестовые интерфейсы между пакетами Go?

Go не делит код между тестовыми файлами разных пакетов, поэтому определения тестовых интерфейсов не используются автоматически. Как мы можем обойти это на практике?

Пример использования testing/quick:

foo/foo.go:

package foo

type Thing int

const (
  X Thing = iota
  Y
  Z
)

bar/bar.go:

package bar

import (
  "foo"
)

type Box struct {
  Thing foo.Thing
}

Мы хотим проверить свойства fooпоэтому мы определяем testing/quick.Generate на Thing:

foo_test.go:

package foo

import (
  "math/rand"
  "reflect"
  "testing"
  "testing/quick"
  "time"
)

func (_ Thing) Generate(r *rand.Rand, sz int) reflect.Value {
  return reflect.ValueOf(Thing(r.Intn(3)))
}

func TestGenThing(t *testing.T) {
  r := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
  for i := 0; i < 5; i++ {
    val, _ := quick.Value(reflect.TypeOf(Thing(0)), r)
    tng, _ := val.Interface().(Thing)
    t.Logf("%#v\n", tng)
  }
}

quick.Value возвращается Things в диапазоне [0,3), как и ожидалось:

$ go test -v foo
=== RUN   TestGenThing
--- PASS: TestGenThing (0.00s)
        foo_test.go:20: 0
        foo_test.go:20: 1
        foo_test.go:20: 2
        foo_test.go:20: 1
        foo_test.go:20: 2
PASS
ok      foo     0.026s

Давайте проверим свойство bar также:

package bar

import (
  "math/rand"
  "reflect"
  "testing"
  "testing/quick"
  "time"

  "foo"
)

func (_ Box) Generate(r *rand.Rand, sz int) reflect.Value {
  val, _ := quick.Value(reflect.TypeOf(foo.Thing(0)), r)
  tng, _ := val.Interface().(foo.Thing)
  return reflect.ValueOf(Box{tng})
}

func TestGenBox(t *testing.T) {
  r := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
  for i := 0; i < 5; i++ {
    val, _ := quick.Value(reflect.TypeOf(Box{}), r)
    box, _ := val.Interface().(Box)
    t.Logf("%#v\n", box)
  }
}

Но Box.Generate сломано. foo_test.go недоступно для bar_test.go, так quick.Value() не использует Thing.Generate():

$ GOPATH=$PWD go test -v bar
=== RUN   TestGenBox
--- PASS: TestGenBox (0.00s)
        bar_test.go:24: bar.Box{Thing:3919143124849004253}
        bar_test.go:24: bar.Box{Thing:-3486832378211479055}
        bar_test.go:24: bar.Box{Thing:-3056230723958856466}
        bar_test.go:24: bar.Box{Thing:-847200811847403542}
        bar_test.go:24: bar.Box{Thing:-2593052978030148925}
PASS
ok      bar     0.095s

Есть ли обходной путь для этого? Как люди используют testing/quick (или любая другая библиотека тестирования с интерфейсами) на практике?

1 ответ

Решение

Любой код, совместно используемый пакетами, должен находиться в не тестовом файле. Это не значит, что он должен быть включен в какие-либо окончательные сборки; Вы можете использовать ограничения сборки, чтобы исключить файлы из обычных сборок, и создавать теги, чтобы включать их при запуске тестов. Например, вы можете поместить свой общий тестовый код в файл с префиксом:

//+build testtools

package mypackage

(но не называется _test.go). При сборке это не будет включено в сборку. Когда вы тестируете, вы будете использовать что-то вроде:

go test -tags "testtools" ./...

Это включит ограниченный файл в сборку и тем самым сделает общий код доступным для тестов.

Другие вопросы по тегам