Правильное именование пакетов для тестирования с языком Go

103

Я видел несколько различных стратегий именования тестовых пакетов в Go и хотел узнать, каковы преимущества и недостатки каждой из них и какую из них мне следует использовать.

Стратегия 1:

Имя файла: github.com/user/myfunc.go

package myfunc

Имя тестового файла: github.com/user/myfunc_test.go

package myfunc

См. Пример bzip2 .

Стратегия 2:

Имя файла: github.com/user/myfunc.go

package myfunc

Имя тестового файла: github.com/user/myfunc_test.go

package myfunc_test

import (
    "github.com/user/myfunc"
)

См. Пример провода .

Стратегия 3:

Имя файла: github.com/user/myfunc.go

package myfunc

Имя тестового файла: github.com/user/myfunc_test.go

package myfunc_test

import (
    . "myfunc"
)

См. Пример строк .

Стандартная библиотека Go, похоже, использует комбинацию стратегии 1 и 2. Какую из всех трех мне следует использовать? Добавление package *_testк моим тестовым пакетам проблематично, поскольку это означает, что я не могу тестировать частные методы своего пакета, но, может быть, есть скрытое преимущество, о котором я не знаю?

Дэн
источник
9
Этот вопрос приведет только к разным мнениям, но я добавлю свое. Вам не нужно тестировать свои частные методы. Вы хотите протестировать интерфейс вашего пакета, который будут использовать другие разработчики. Если тесты терпят неудачу, значит, вы знаете, что ваши частные методы требуют внимания.
Brenden
2
Пример [wire] ( github.com/btcsuite/btcd/blob/master/wire/msgtx_test.go ) для Стратегии 2 на самом деле теперь также является примером Стратегии 1 ...
durp 06

Ответы:

133

Фундаментальное различие между тремя перечисленными стратегиями заключается в том, находится ли тестовый код в том же пакете, что и тестируемый код. Решение использовать package myfuncили package myfunc_testв тестовом файле зависит от того, хотите ли вы выполнить белый ящик или черный ящик тестирование .

Нет ничего плохого в использовании обоих методов в проекте. Например, у вас может быть myfunc_whitebox_test.goиmyfunx_blackbox_test.go .

Сравнение пакетов тестового кода

  • Тестирование черного ящика: используйте package myfunc_test, чтобы убедиться, что вы используете только экспортированные идентификаторы .
  • Тестирование белого ящика: используйте, package myfuncчтобы у вас был доступ к неэкспортируемым идентификаторам. Подходит для модульных тестов, требующих доступа к неэкспортируемым переменным, функциям и методам.

Сравнение рассматриваемых стратегий

  • Стратегия 1: файл myfunc_test.goиспользуетpackage myfunc - В этом случае тест - код в myfunc_test.goбудет находиться в том же пакете, что и код тестируемого в myfunc.go, что myfuncв этом примере.
  • Стратегия 2: файл myfunc_test.goиспользуетpackage myfunc_test - в этом случае тестовый код в myfunc_test.go«будет скомпилирован как отдельный пакет, а затем скомпонован и запущен с основным тестовым двоичным кодом ». [Источник: строки 58–59 в исходном коде test.go ]
  • Стратегия 3: файл myfunc_test.goиспользует, package myfunc_testно импортирует, myfuncиспользуя точечную нотацию - это вариант стратегии 2, но для импорта используется точечная нотация myfunc.
Мэтью Рэнкин
источник
1
Следует отметить, что при использовании стратегии 1 файлы будут храниться _test.goотдельно от тестируемого пакета (такое же поведение, как и в стратегии 2). Похоже, это не задокументировано согласно github.com/golang/go/issues/15315
Кевин Динанаут,
Я видел сильную стратегию использования пакетов 3, но не понимаю, в чем смысл?
PickBoy
1
Я форкнул пакет и внес изменения, и теперь все мои тесты пытаются импортировать исходное репо вместо моего разветвленного пакета. В Стратегии 3 мне не нужно менять «github.com/original/link» на «github.com/my/fork», потому что он просто ссылается на '.' вместо.
nmarley
1
@KevinDeenanauth Это меня просто удивило. Я подумал, что обнаружил ловушку, когда только что нашел a _test.goс _testименем, отличным от пакета, содержащим a, func init()который изменяет некоторую глобальную переменную пакета для тестирования. Я ошибался.
Зил
1
@nmarley .не решает проблему с вилкой. Это не относительное значение. Он просто импортирует идентификаторы «в текущий пакет».
qaisjp
19

Это зависит от объема ваших тестов. Тесты высокого уровня (интеграция, приемка и т. Д.), Вероятно, следует поместить в отдельный пакет, чтобы убедиться, что вы используете пакет через экспортированный API.

Если у вас большой пакет с множеством внутренних компонентов, которые необходимо протестировать, используйте тот же пакет для своих тестов. Но это не приглашение вашим тестам получить доступ к какому-либо частному состоянию. Это превратило бы рефакторинг в кошмар. Когда я пишу структуры на ходу, я часто реализую интерфейсы. Это те методы интерфейса, которые я вызываю из своих тестов, а не все вспомогательные методы / функции по отдельности.

mdwhatcott
источник
13

По возможности следует использовать стратегию 1. Вы можете использовать специальное foo_testимя пакета, чтобы избежать циклов импорта, но в основном оно есть, поэтому стандартную библиотеку можно протестировать с помощью того же механизма. Например, stringsнельзя протестировать со стратегией 1, поскольку testingпакет зависит от strings. Как вы сказали, со стратегией 2 или 3 у вас нет доступа к частным идентификаторам пакета, поэтому обычно лучше не использовать его, если вам не нужно.

Guelfey
источник
10
Как отсутствие доступа к частным идентификаторам в тестах не является достоинством?
jub0bs
3
согласно хорошей практике тестирования, вы не тестируете детали внутренней реализации для артефакта кода; делаешь сына, это кодовый запах
Херардо Лима
0

Одно важное замечание, которое я хотел бы добавить import .из Golang CodeReviewComments :

import .Форма может быть полезна в тестах , которые, из - за круговые зависимости, не могут быть сделаны частью пакета тестируются:

package foo_test

import (
    "bar/testutil" // also imports "foo"
    . "foo"
)

В этом случае тестовый файл не может быть в пакете foo, потому что он использует bar/testutil, который импортирует foo. Итак, мы используем «импорт». форма, позволяющая файлу притворяться частью пакета foo, даже если это не так.

За исключением этого случая, не используйтеimport . в своих программах. Это значительно затрудняет чтение программ, поскольку неясно, является ли такое имя, как Quux, идентификатором верхнего уровня в текущем пакете или в импортированном пакете.

Эрик
источник