Как разделить тестовые интерфейсы между пакетами 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 (или любую другую библиотеку тестирования с интерфейсами) на практике?


person Mike Craig    schedule 26.05.2017    source источник


Ответы (1)


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

//+build testtools

package mypackage

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

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

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

person Adrian    schedule 26.05.2017