- Оригинальный адрес:A History of Testing in Go at SmartyStreets
- Оригинальный автор:Michael Whatcott
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:kasheemlew
- Корректор:StellaBauhinia
Меня часто спрашиваютЭти два интересных вопроса:
- Почему вы протестировали инструмент (изGoConvey) заменяется наgunit?
- Вы всем рекомендуете это делать?
Эти два вопроса замечательны, и как соучредитель GoConvey и ведущий автор gunit я обязан прояснить их. Прямой ответ, слишком длинный, чтобы читать серию:
Вопрос 1: Зачем переключаться на gunit?
Было несколько проблем, которые беспокоили нас при использовании GoConvey, поэтому мы подумали об альтернативе, которая лучше отражает направленность нашей библиотеки тестирования на их решение. В сложившихся обстоятельствах мы больше не могли составить план переходного обновления для GoConvey. Ниже я будуЧетноепредставлено подробно и доработано дократкий декларативный вывод.
Вопрос 2: Рекомендуете ли вы, что мы все сделаем (от Goconvey заменили оружие)?
Нет. Я рекомендую вам использовать только те инструменты и библиотеки, которые помогут вам достичь ваших целей. Вы должны сначала уточнить свои потребности в инструментах тестирования, а затем как можно скорее найти или создать инструмент, который вам подходит. Инструменты тестирования — это основа, на которой вы строите свои проекты. Если вы резонируете с последним контентом, то Gunit будет очень привлекательным вариантом в вашем выборе. Вы должны сделать свое исследование и выбрать тщательно. Сообщество GoConvey все еще растет, и у него много активных сопровождающих. Если вы хотите поддержать этот проект, присоединяйтесь к нам.
Давным-давно в далекой галактике...
Перейти тест
Мы впервые начали использовать Go around, когда была выпущена версия Go 1.1 (в середине 2013 г.), и когда мы впервые начали программировать, мы, естественно, столкнулись сgo test
и"testing"
Сумка. Я рад видеть, что пакет тестирования был включен в стандартную библиотеку даже нашумлению набора инструментов, но это обычно, для этого метода ничего не почувствовала. Далее мы будем использовать знаменитыйПрактика «Игра в боулинг»Сравнение показывает эффект, который мы получаем после использования разных инструментов тестирования. (Вы можете воспользоваться моментом, чтобы ознакомиться спроизводственный код, чтобы лучше понять следующий раздел тестирования. )
В стандартной библиотеке используется следующее"testing"
Некоторые способы упаковать тест игры в боулинг:
import "testing"
// Helpers:
func (this *Game) rollMany(times, pins int) {
for x := 0; x < times; x++ {
this.Roll(pins)
}
}
func (this *Game) rollSpare() {
this.rollMany(2, 5)
}
func (this *Game) rollStrike() {
this.Roll(10)
}
// Tests:
func TestGutterBalls(t *testing.T) {
t.Log("Rolling all gutter balls... (expected score: 0)")
game := NewGame()
game.rollMany(20, 0)
if score := game.Score(); score != 0 {
t.Errorf("Expected score of 0, but it was %d instead.", score)
}
}
func TestOnePinOnEveryThrow(t *testing.T) {
t.Log("Each throw knocks down one pin... (expected score: 20)")
game := NewGame()
game.rollMany(20, 1)
if score := game.Score(); score != 20 {
t.Errorf("Expected score of 20, but it was %d instead.", score)
}
}
func TestSingleSpare(t *testing.T) {
t.Log("Rolling a spare, then a 3, then all gutters... (expected score: 16)")
game := NewGame()
game.rollSpare()
game.Roll(3)
game.rollMany(17, 0)
if score := game.Score(); score != 16 {
t.Errorf("Expected score of 16, but it was %d instead.", score)
}
}
func TestSingleStrike(t *testing.T) {
t.Log("Rolling a strike, then 3, then 7, then all gutters... (expected score: 24)")
game := NewGame()
game.rollStrike()
game.Roll(3)
game.Roll(4)
game.rollMany(16, 0)
if score := game.Score(); score != 24 {
t.Errorf("Expected score of 24, but it was %d instead.", score)
}
}
func TestPerfectGame(t *testing.T) {
t.Log("Rolling all strikes... (expected score: 300)")
game := NewGame()
game.rollMany(21, 10)
if score := game.Score(); score != 300 {
t.Errorf("Expected score of 300, but it was %d instead.", score)
}
}
для ранее использовавшихсяxUnitлюди, следующие две вещи заставят вас чувствовать себя некомфортно:
- Поскольку нет единого
Setup
Можно использовать функции/методы, все игры должны многократно создавать структуру игры. - Все сообщения об ошибках утверждения должны быть написаны вами и смешаны с выражением if, которое используется для нейтрализации написанного вами прямого утверждения утверждения. При использовании оператора сравнения (
<
,>
,<=
и>=
), эти негативные утверждения раздражают еще больше.
Итак, мы исследуем, как тестировать и понять, почему сообщество Go сдалось.«Наш любимый помощник по тестированию»и«Метод утверждения»Вид, использовать вместоТест ", управляемый столовым"для уменьшения кода шаблона. Перепишите приведенный выше пример с тестированием на основе таблиц:
import "testing"
func TestTableDrivenBowlingGame(t *testing.T) {
for _, test := range []struct {
name string
score int
rolls []int
}{
{"Gutter Balls", 0, []int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
{"All Ones", 20, []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}},
{"A Single Spare", 16, []int{5, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
{"A Single Strike", 24, []int{10, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
{"The Perfect Game", 300, []int{10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10}},
} {
game := NewGame()
for _, roll := range test.rolls {
game.Roll(roll)
}
if score := game.Score(); score != test.score {
t.Errorf("FAIL: '%s' Got: [%d] Want: [%d]", test.name, score, test.score)
}
}
}
Да, это полностью отличается от предыдущего кода.
преимущество:
- Новый код намного короче! Теперь для всего набора тестов существует только одна тестовая функция.
- Использование оператора цикла решает проблему дублирования настроек.
- Точно так же пользователь получит код ошибки только из оператора утверждения.
- В процессе отладки легко добавить
skip bool
пропустить некоторые тесты
недостаток:
- Определение анонимной структуры смешано с объявлением цикла, что кажется странным.
- Табличные тесты эффективны только в некоторых относительно простых случаях, которые включают только чтение/чтение данных. Когда все усложняется, он становится громоздким, и непросто (или невозможно) расширить весь тест с помощью одной структуры.
- Использование ломтиков для бросков/бросков "раздражает". Хотя мы можем использовать наши мозги, чтобы упростить его, это сделает наш код шаблонаЛогика усложняется.
- Этот тест косвенности/отрицания злит меня, несмотря на то, что я написал только одно утверждение.
GoConvey
Теперь мы не можем просто согласиться на вне коробкиgo test
, поэтому мы начали использовать инструменты и библиотеки, предоставляемые Go, для реализации собственных методов тестирования. Если вы посмотрите внимательноSmartyStreets GitHub page, вы заметите известный репозиторий — GoConvey. это нашеВклады сообщества Go OSSОдин из самых ранних проектов.
Можно сказать, что GoConvey является двойным инструментом тестирования. Во-первых, создайте средство запуска тестов, которое отслеживает ваш код и выполняет его при внесении изменений.go test
и визуализировать результат в классную веб-страницу, которая затем отображается в браузере. Во-вторых, он предоставляет библиотеку, которая позволяет вамgo test
Пишите тесты стиля разработки, основанные на поведении, в функциях. Еще одна хорошая новость: вы можете не использовать, частично или полностью использовать эти функции в GoConvey.
Мы разработали GoConvey по двум причинам: переработкаJetBrains IDEs(в то время мы использовали ReSharper) и создали набор вещей, которые нам нравились, напримерnUnitиMachine.Specifications(до того, как мы начали использовать Go, мы были магазинами .Net), такими как тестовая композиция и утверждения.
Вот результат переписывания вышеуказанного теста с помощью GoConvey:
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestBowlingGameScoring(t *testing.T) {
Convey("Given a fresh score card", t, func() {
game := NewGame()
Convey("When all gutter balls are thrown", func() {
game.rollMany(20, 0)
Convey("The score should be zero", func() {
So(game.Score(), ShouldEqual, 0)
})
})
Convey("When all throws knock down only one pin", func() {
game.rollMany(20, 1)
Convey("The score should be 20", func() {
So(game.Score(), ShouldEqual, 20)
})
})
Convey("When a spare is thrown", func() {
game.rollSpare()
game.Roll(3)
game.rollMany(17, 0)
Convey("The score should include a spare bonus.", func() {
So(game.Score(), ShouldEqual, 16)
})
})
Convey("When a strike is thrown", func() {
game.rollStrike()
game.Roll(3)
game.Roll(4)
game.rollMany(16, 0)
Convey("The score should include a strike bonus.", func() {
So(game.Score(), ShouldEqual, 24)
})
})
Convey("When all strikes are thrown", func() {
game.rollMany(21, 10)
Convey("The score should be 300.", func() {
So(game.Score(), ShouldEqual, 300)
})
})
})
}
Как и в табличном подходе, весь тест содержится в функции. Опять же, как и в исходном примере, мы делаем повторные броски/броски через вспомогательную функцию. В отличие от других примеров, у нас теперь есть гениальный,Нетсложный,на основе объемаизмодель исполнения. Все тесты общиеgame
переменная, но замечательная особенность GoConvey заключается в том, что каждая внешняя область видимости выполняется для каждой внутренней области видимости. Таким образом, каждый тест является относительно изолированным. Очевидно, что если вы не уделяете внимание инициализации и области видимости, вы легко можете попасть в беду.
Кроме того, когда вы помещаете вызовы Convey в цикл (например, пытаетесь объединить GoConvey с табличными тестами), могут происходить некоторые странные вещи.*testing.T
полностью сверхуConvey
управление вызовами (вы замечаете это и другиеConvey
Это немного отличается? ), поэтому вам также не нужно передавать этот параметр везде, где вам нужно утверждение. Но если вы когда-либо писали более сложные тесты в GoConvey, вы обнаружите, что процесс извлечения вспомогательных функций довольно сложен. Прежде чем я решил обойти эту проблему, я построил固定结构
хранить состояние всех тестов, а затем создавать в этой структуреConvey
Функция, которую будет использовать обратный вызов. Поэтому кажется странным, что в какой-то момент это будет блок и область действия Convey, а затем другая фиксированная структура и ее методы.
gunit
Так что, несмотря на то, что нам потребовалось некоторое время, мы, наконец, поняли, что нам просто нужна версия xUint для Go, в которой нужно избавиться от странного импорта точек и переменных регистра уровня пакета с подчеркиванием (см.GoCheck). Нам по-прежнему нравились утверждения в GoConvey, поэтому мы выделили одно из исходного проекта.независимый складГулен родился так:
import (
"testing"
"github.com/smartystreets/assertions/should"
"github.com/smartystreets/gunit"
)
func TestBowlingGameScoringFixture(t *testing.T) {
gunit.Run(new(BowlingGameScoringFixture), t)
}
type BowlingGameScoringFixture struct {
*gunit.Fixture
game *Game
}
func (this *BowlingGameScoringFixture) Setup() {
this.game = NewGame()
}
func (this *BowlingGameScoringFixture) TestAfterAllGutterBallsTheScoreShouldBeZero() {
this.rollMany(20, 0)
this.So(this.game.Score(), should.Equal, 0)
}
func (this *BowlingGameScoringFixture) TestAfterAllOnesTheScoreShouldBeTwenty() {
this.rollMany(20, 1)
this.So(this.game.Score(), should.Equal, 20)
}
func (this *BowlingGameScoringFixture) TestSpareReceivesSingleRollBonus() {
this.rollSpare()
this.game.Roll(4)
this.game.Roll(3)
this.rollMany(16, 0)
this.So(this.game.Score(), should.Equal, 21)
}
func (this *BowlingGameScoringFixture) TestStrikeReceivesDoubleRollBonus() {
this.rollStrike()
this.game.Roll(4)
this.game.Roll(3)
this.rollMany(16, 0)
this.So(this.game.Score(), should.Equal, 24)
}
func (this *BowlingGameScoringFixture) TestPerfectGame() {
this.rollMany(12, 10)
this.So(this.game.Score(), should.Equal, 300)
}
func (this *BowlingGameScoringFixture) rollMany(times, pins int) {
for x := 0; x < times; x++ {
this.game.Roll(pins)
}
}
func (this *BowlingGameScoringFixture) rollSpare() {
this.game.Roll(5)
this.game.Roll(5)
}
func (this *BowlingGameScoringFixture) rollStrike() {
this.game.Roll(10)
}
Как видите, процесс удаления вспомогательного метода громоздок, потому что мы манипулируем состоянием уровня структуры, а не состоянием локальных переменных функции. Кроме того, модель выполнения конфигурации/тестирования/очистки в xUnit намного проще для понимания, чем модель выполнения с ограниченной областью действия в GoConvey. здесь,*testing.T
теперь встроен*gunit.Fixture
управлять. Этот подход одинаково интуитивно понятен и прост для понимания как для простых, так и для сложных тестов, основанных на взаимодействии.
Еще одно огромное различие между gunit и GoConvey заключается в том, что, согласно тестовому режиму xUnit, GoConvey используетобщая фиксированная структурав то время как пистолет используетНовая фиксированная структура. Оба метода разумны, в основном зависит от сценария вашего приложения. Совершенно новая фиксированная структура обычно более желательна в модульных тестах, в то время как общая фиксированная структура более полезна в некоторых ситуациях с интенсивным конфигурированием, таких как интеграционные тесты или системные тесты.
Новая фиксированная структура гарантирует, что отдельные элементы теста не зависят друг от друга, поэтому gunit использует значение по умолчанию.t.Parallel()
. Кроме того, поскольку мы вызываем только подтесты с использованием отражения, также можно использовать-run
Параметр выбирает конкретный элемент теста для выполнения:
$ go test -v -run 'BowlingGameScoringFixture/TestPerfectGame'
=== RUN TestBowlingGameScoringFixture
=== PAUSE TestBowlingGameScoringFixture
=== CONT TestBowlingGameScoringFixture
=== RUN TestBowlingGameScoringFixture/TestPerfectGame
=== PAUSE TestBowlingGameScoringFixture/TestPerfectGame
=== CONT TestBowlingGameScoringFixture/TestPerfectGame
--- PASS: TestBowlingGameScoringFixture (0.00s)
--- PASS: TestBowlingGameScoringFixture/TestPerfectGame (0.00s)
PASS
ok github.com/smartystreets/gunit/advanced_examples 0.007s
Но нельзя отрицать, что некоторый код предыдущего примера все еще существует (например, некоторый код в заголовке файла). мы вGoLandВ следующих живых шаблонах установлены, и они автоматически генерируют большую часть предыдущего контента. Вот команда для установки живого шаблона в Goland:
- Открытые предпочтения в Голанд в.
- существует
编辑器/实时模板
выбранGo
список, а затем щелкните+
номер и выберите "Живые шаблоны" - дать ему сокращенное имя (мы используем
fixture
) - Вставьте код ниже в
模板文本
площадь:
func Test$NAME$(t *testing.T) {
gunit.Run(new($NAME$), t)
}
type $NAME$ struct {
*gunit.Fixture
}
func (this *$NAME$) Setup() {
}
func (this *$NAME$) Test$END$() {
}
- После этого нажмите рядом с предупреждением «Не указан контекст приложения».
定义
. - существует
Go
Отметьте передний план и нажмитеOK
.
Теперь давайте откроем тестовый файл, введитеfixture
Затем используйте вкладку, чтобы автоматически заполнить шаблон теста.
в заключении
позвольте мне следоватьМанифест гибкой разработки программного обеспечениястиль, чтобы сделать резюме:
Мы продолжаем практиковаться, помогать другим и, в конце концов, находим лучшие способы создания программного обеспечения.контрольная работа. Это заставляет нас добиться многих ценных вещей:
- существуетобщая фиксированная структурареализуется на основеСовершенно новая фиксированная структура
- Реализовано с умной семантикой области видимостиПростая модель исполнения
- Локальная функция (или уровень пакета) для достижения переменной областиСтруктурный объем
- Реализовано с перевернутыми проверками и созданными вручную сообщениями об ошибкахФункция прямого утверждения
Тем не менее, в то время как другие библиотеки тестирования великолепны (один аспект), мы предпочитаем оружитель (другой аспект).
Если вы обнаружите ошибки в переводе или в других областях, требующих доработки, добро пожаловать наПрограмма перевода самородковВы также можете получить соответствующие бонусные баллы за доработку перевода и PR. начало статьиПостоянная ссылка на эту статьюЭто ссылка MarkDown этой статьи на GitHub.
Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,продукт,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.