Когда код превращается в историю
Представьте: вы пишете код на Go, запускаете тесты — зелёная полоска. Но менеджер смотрит на ваш t.Errorf и не понимает, что именно сломалось. Бизнес говорит: «Сделайте так, чтобы пользователь мог зарегистрироваться», а разработчик переводит это в «POST /api/v1/user с валидацией email». Разрыв между техническим и человеческим языком — главная головная боль многих проектов.
Решение есть — BDD (Behaviour-Driven Development) на Go. Это не просто тесты. Это мост между кодом и бизнес-логикой. В этой статье мы разберём, как писать BDD-тесты на Go, используя реальные примеры, и почему ваш продакт-менеджер будет вам благодарен.
Что такое BDD и почему Go?
Behaviour-Driven Development (разработка через поведение) — это эволюция TDD. Если TDD фокусируется на «как» (как реализовать функцию), то BDD отвечает на «что» (что должна делать система с точки зрения пользователя).
Go идеально подходит для BDD благодаря своей простоте, встроенному пакету testing и мощным фреймворкам вроде Ginkgo и Godog. Go-сообщество активно использует BDD для микросервисов, API и даже CLI-утилит.
Ключевые принципы BDD на Go
- Единый язык (Ubiquitous Language) — сценарии пишутся на понятном бизнесу английском или русском.
- Given-When-Then — структура любого сценария: предпосылки → действие → ожидаемый результат.
- Автоматизация — сценарии превращаются в исполняемые тесты на Go.
Инструменты для BDD в Go
Выбор фреймворка зависит от того, как вы хотите описывать сценарии. Вот два основных подхода:
1. Ginkgo + Gomega (для Go-разработчиков)
Ginkgo — это BDD-фреймворк, который пишется на чистом Go. Сценарии выглядят как Go-код с читаемыми описаниями. Gomega — библиотека матчеров для ассертов.
Плюсы: полный контроль над кодом, отличная интеграция с Go-тулингом, поддержка асинхронных тестов.
2. Godog (Cucumber для Go)
Godog — это имплементация Cucumber на Go. Вы пишете сценарии на Gherkin (Feature-файлы на естественном языке), а затем реализуете шаги на Go.
Плюсы: сценарии читают даже не-программисты, легко менять логику без правки кода.
Практика: пишем BDD-тест на Go с Ginkgo
Давайте создадим простой сервис — корзину интернет-магазина. Напишем BDD-тест, который проверяет добавление товара.
Шаг 1: Установка Ginkgo
go get github.com/onsi/ginkgo/v2
go get github.com/onsi/gomega
Шаг 2: Инициализация тестового пакета
cd mycart
ginkgo bootstrap
Создаётся файл mycart_suite_test.go.
Шаг 3: Пишем сценарий
Создадим файл cart_test.go:
package mycart_test
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"mycart"
)
var _ = Describe("Корзина", func() {
var cart *mycart.Cart
BeforeEach(func() {
cart = mycart.NewCart()
})
Describe("Добавление товара", func() {
Context("с корректным ID и ценой", func() {
It("должна увеличить количество элементов на 1", func() {
cart.AddItem("sku-123", 100.0)
Expect(cart.TotalItems()).To(Equal(1))
})
It("должна корректно считать общую сумму", func() {
cart.AddItem("sku-123", 100.0)
cart.AddItem("sku-456", 250.0)
Expect(cart.TotalPrice()).To(BeNumerically("~", 350.0, 0.01))
})
})
Context("с товаром, который уже есть", func() {
It("должна увеличить количество, а не добавлять новый элемент", func() {
cart.AddItem("sku-123", 100.0)
cart.AddItem("sku-123", 100.0)
Expect(cart.TotalItems()).To(Equal(1))
Expect(cart.Quantity("sku-123")).To(Equal(2))
})
})
})
})
Шаг 4: Реализация кода
package mycart
type Cart struct {
items map[string]*Item
}
type Item struct {
SKU string
Price float64
Quantity int
}
func NewCart() *Cart {
return &Cart{items: make(map[string]*Item)}
}
func (c *Cart) AddItem(sku string, price float64) {
if item, ok := c.items[sku]; ok {
item.Quantity++
} else {
c.items[sku] = &Item{SKU: sku, Price: price, Quantity: 1}
}
}
func (c *Cart) TotalItems() int {
return len(c.items)
}
func (c *Cart) TotalPrice() float64 {
total := 0.0
for _, item := range c.items {
total += item.Price * float64(item.Quantity)
}
return total
}
func (c *Cart) Quantity(sku string) int {
if item, ok := c.items[sku]; ok {
return item.Quantity
}
return 0
}
Шаг 5: Запуск тестов
ginkgo -v
Вы увидите читаемый вывод: «Корзина Добавление товара с корректным ID и ценой должна увеличить количество элементов на 1 — PASSED». Это и есть BDD в действии.
BDD с Godog: сценарии на русском языке
Если ваша команда включает аналитиков или менеджеров, используйте Godog. Сценарии пишутся на Gherkin.
Установка Godog
go get github.com/cucumber/godog/cmd/godog
Feature-файл (cart.feature)
Функция: Корзина покупок
Как покупатель
Я хочу добавлять товары в корзину
Чтобы оформить заказ
Сценарий: Добавление нового товара
Допустим корзина пуста
Когда я добавляю товар "sku-123" с ценой 100
Тогда в корзине 1 элемент
И общая сумма равна 100
Реализация шагов (cart_steps.go)
package main
import (
"github.com/cucumber/godog"
"mycart"
)
var cart *mycart.Cart
func iCartIsEmpty() error {
cart = mycart.NewCart()
return nil
}
func iAddItemWithPrice(sku string, price float64) error {
cart.AddItem(sku, price)
return nil
}
func cartHasItems(count int