[Перевод] SmartyStreets 'Go тестовая дорога разведки

задняя часть Go контрольная работа Программа перевода самородков

Меня часто спрашиваютЭти два интересных вопроса:

  1. Почему вы протестировали инструмент (изGoConvey) заменяется наgunit?
  2. Вы всем рекомендуете это делать?

Эти два вопроса замечательны, и как соучредитель 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люди, следующие две вещи заставят вас чувствовать себя некомфортно:

  1. Поскольку нет единогоSetupМожно использовать функции/методы, все игры должны многократно создавать структуру игры.
  2. Все сообщения об ошибках утверждения должны быть написаны вами и смешаны с выражением 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)
		}
	}
}

Да, это полностью отличается от предыдущего кода.

преимущество:

  1. Новый код намного короче! Теперь для всего набора тестов существует только одна тестовая функция.
  2. Использование оператора цикла решает проблему дублирования настроек.
  3. Точно так же пользователь получит код ошибки только из оператора утверждения.
  4. В процессе отладки легко добавитьskip boolпропустить некоторые тесты

недостаток:

  1. Определение анонимной структуры смешано с объявлением цикла, что кажется странным.
  2. Табличные тесты эффективны только в некоторых относительно простых случаях, которые включают только чтение/чтение данных. Когда все усложняется, он становится громоздким, и непросто (или невозможно) расширить весь тест с помощью одной структуры.
  3. Использование ломтиков для бросков/бросков "раздражает". Хотя мы можем использовать наши мозги, чтобы упростить его, это сделает наш код шаблонаЛогика усложняется.
  4. Этот тест косвенности/отрицания злит меня, несмотря на то, что я написал только одно утверждение.

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,внешний интерфейс,задняя часть,блокчейн,продукт,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.