TDD, также известная как Test-Driven Development, представляет собой методологию программирования «сначала тестирование», и ее основной процесс вращается вокруг цикла тестирование->кодирование (рефакторинг)->тестирование. Концепция TDD не нова, но похоже, что она не получила широкого распространения и применения, возможно из-за слишком высокой стоимости, а может из-за исключения разработчиков, но это не скрывает преимуществ и уникальности Сам ТДД. Попробовав какое-то время попрактиковаться в разработке TDD на языке Go, я обнаружил, что программы Go очень подходят для создания с помощью TDD — встроенная поддержка тестирования на языке Go и идеальная структура тестовой библиотеки делают стоимость реализации TDD относительно низкой. , что эквивалентно Усиливает преимущества TDD. Вот и волна Amway огромному количеству сусликов, может и вы полюбите его. Эта статья будет написана с практической точки зрения бизнеса, с примером, демонстрирующим, как использовать TDD для создания нашей программы Go.
Полный пример кода в этом посте можно найти здесь:tdd-example
Три принципа TDD
- Запрещается писать производственный код, кроме как для прохождения модульного теста.
- В модульном тесте разрешено только писать, что именно приведет к его сбою.
- Вы можете писать производственный код, который проходит только один модульный тест за раз, не больше.
- You are not allowed to write any production code unless it is to make a failing unit test pass.
- You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
- You are not allowed to write any more production code than is sufficient to pass the one failing unit test.
В соответствии с тремя принципами процесс разработки TDD описывается следующим образом:
В следующем примере мы будем следовать трем вышеуказанным принципам, вокруг этих пяти шагов покажем, как использовать TDD для разработки нашей программы Go.
В разработке программного обеспечения нет серебряной пули.Три принципа являются воплощением мышления TDD, а не нерушимой догмой. Если вы какое-то время использовали TDD или научились лучшему способу, вполне возможно применить другой подход. Но когда мы только знакомимся с новой технологией, следование принципам зачастую является самым быстрым способом начать работу.
Пример
фон спроса
Чтобы обеспечить более качественные услуги доставки, платформа доставки еды решила добавить активную отправку заказов на основе активного захвата заказов братом на вынос.Функции резюмируются следующим образом: получать запросы на доставку заказа и выбирать брата на вынос для пользователя. доставить в соответствии с правилами планирования. , и уведомить курьера, чтобы он забрал еду. После семинара по требованиям продакт-менеджер дал первую версию требований:
- Выбирайте курьеров только из курьеров в радиусе 5 километров от продавца.
- Для пользователей, которые приобрели Jushibao, приоритет отдается доставке младшего брата с наименьшим количеством доставки заказа.
- Для других пользователей случайным образом назначьте младшего брата служить.
- Когда текущий номер заказа на доставку брата на вынос >=10, новые заказы не распределяются.
Организация тестовых случаев
Согласно трем принципам TDD, сначала нам нужно написать тестовые методы, поэтому сначала нам нужно разобраться с тест-кейсами. Эта часть работы может быть выполнена совместно путем разработки и тестирования. После разбора требований разбираются следующие тест-кейсы:
- Если в радиусе 5 километров от продавца нет курьера, будет возвращена ошибка и последующая операция отправки заказа не будет выполнена.
- Когда количество всех заказов на доставку всех курьеров в радиусе 5 километров от продавца >=10, будет возвращена ошибка, и последующая операция отправки заказа не будет выполнена.
- Пользователь следующего заказа купил Jushibao, выбрал младшего брата с наименьшим заказом и уведомил младшего брата, чтобы он забрал еду.
- Если следующий пользователь заказа не покупает Дзюшибао, младший брат случайным образом назначается для подачи и уведомляет младшего брата, чтобы он забрал еду.
Определите зависимости и абстрагируйте их в интерфейсы
Проанализируйте требования и тестовые примеры, определите их зависимости и абстрагируйте их в интерфейсы. Вообще говоря, мы можем сначала абстрагировать наиболее легко абстрагируемые зависимости — внешние зависимости, такие как сеть, ввод-вывод и промежуточное ПО, в интерфейсы. Предположим, что модуль доставки заказа использует MongoDB (поддерживает индексацию географического местоположения) для хранения местоположения доставщика в реальном времени; использует очередь сообщений, чтобы уведомить доставщика о необходимости забрать еду. То есть эта функция включает в себя две зависимости, базу данных и очередь сообщений, Мы определяем вышеуказанные зависимости как следующие интерфейсы:
// DeliverBoyRepository 外卖小哥仓储接口
type DeliverBoyRepository interface {
// GetNearBy 获取指定shopID内distance公里范围内的外卖小哥列表
GetNearBy(shopID int, distance int) ([]*DeliveryBoy, error)
}
// DeliveryBoy 表示一个外卖小哥
type DeliveryBoy struct {
ID int
OrderNum int // 正在配送的订单数
}
// Notifier 消息队列接口
type Notifier interface {
// 通知指定外卖小哥取餐
NotifyDeliveryBoy(boyID int, orderID int)
}
обернуть все зависимости в структуру
Используйте структуру для инкапсуляции только что определенного интерфейса, следующий фрагмент кода обертывает вышеуказанный интерфейс вHandler
структуру, черезNewHandler
Внедрение зависимостей (внедрение конструктора). один из нихHandle
Метод – это метод, который необходимо проверить.
// Handler 主动派单业务处理结构体
type Handler struct {
boyRepo DeliverBoyRepository
notifier Notifier
}
// NewHandler 使用构造函数将依赖注入
func NewHandler(d DeliverBoyRepository, n Notifier) *Handler {
return &Handler{
boyRepo:d,
notifier:n,
}
}
// Request 表示一个要处理的请求
type Request struct {
// OrderID 订单ID
OrderID int
// ShopID 商户ID
ShopID int
// Insured 是否购买“准时宝”
Insured bool
}
// Handle 订单配送逻辑处理
func (h *Handler) Handle(req *Request) (err error) { // <--- 待测方法
return nil
}
На данный момент наш тестируемый метод готов, и следующее начинает формально входить в цикл кодирования TDD.
Тест -> Кодирование (Рефакторинг) -> Тестовый цикл
Напишите методы тестирования для каждого варианта использования, а затем напишите бизнес-код, чтобы тесты прошли успешно. Начнем с первого варианта использования: когда в радиусе 5 километров от продавца нет курьера, возвращается ошибка и последующая операция отправки заказа не выполняется. Здесь мы используемgomockиtestifyчтобы помочь нам написать тестовый код быстро. Для тех, кто не знаком с ними, не волнуйтесь, разобраться в этом примере не составит особого труда.
Get Go Unit Testing (2) — Mock Framework (gomock)
Get Go Unit Testing (3) — Утверждение (свидетельство)
1. Пишите тесты
// 1. 商户5公里内没有外卖小哥存在时,返回错误,不执行后续派单操作
func TestHandler_Handle_NoBoy(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
a := assert.New(t)
// 使用gomock 来mock依赖
d := NewMockDeliveryBoyRepository(ctrl)
n := NewMockNotifier(ctrl)
h := NewHandler(d,n)
req := &Request{
OrderID:1,
ShopID:2,
}
// 5公里内没有外卖小哥
d.EXPECT().GetNearBy(req.ShopID, 5).Return(nil, errors.New("o no..5公里内没有外卖小哥"))
err := h.Handle(req)
a.Error(err)
}
2. Выполните тест и получите отрицательный результат
=== RUN TestHandler_Handle_NoBoy
--- FAIL: TestHandler_Handle_NoBoy (0.00s)
handler_test.go:29:
Error Trace: handler_test.go:29
Error: An error is expected but got nil.
Test: TestHandler_Handle_NoBoy
handler_test.go:30: missing call(s) to *handler.MockDeliveryBoyRepository.GetNearBy(is equal to 2, is equal to 5) D:/yushen/gopath/src/github.com/DrmagicE/tdd-example/handler/handler_test.go:27
handler_test.go:30: aborting test due to missing call(s)
FAIL
Результат отказа говорит нам о двух вещах:
- ожидать
Handle
метод должен возвращатьerror
но вернулсяnil
- отсутствует право
GetNearBy(2,5)
вызов метода
3. Напишите бизнес-код
Затем мы пишем бизнес-код с помощью описанного выше метода тестирования, не забывайте писать слишком много, а только код, соответствующий тестовому примеру:
// Handle 订单配送逻辑处理
func (h *Handler) Handle(req *Request) (error) {
_, err := h.boyRepo.GetNearBy(req.ShopID, 5)
return err
}
4. Тест пройден
Выполните тест еще раз, чтобы убедиться, что тестовый пример пройден:
=== RUN TestHandler_Handle_NoBoy
--- PASS: TestHandler_Handle_NoBoy (0.00s)
PASS
После того, как тест пройден, начните писать второй тест-кейс, за которым следует соответствующий бизнес-код, и так далее, и так далее, пока все тест-кейсы не пройдут тест. Полный метод тестирования см. в образце исходного кода, который здесь не раскрывается.
5. Рефакторинг
С увеличением цикла тестовый код -> бизнес-код увеличивается и бизнес-код, при необходимости нам нужно провести рефакторинг бизнес-кода. Модульное тестирование гарантирует, что старая логика не будет нарушена рефакторингом. Рефакторинг в начале может просто включатьif...else
,for ... range
Измените или инкапсулируйте повторно используемый код в функции и т. д. Однако с развитием бизнеса вышеперечисленноеHandle()
Код метода становится все больше и больше, и когда придет время, нам нужно абстрагировать новый интерфейсный слой. Например, с развитием вышеупомянутых платформ доставки еды правила планирования заказов будут становиться все более и более сложными.Например, когда маршрут доставки заказа и концентрация пользователей заказа используются в качестве основы для планирования заказа, мы может абстрагировать новый интерфейсный слой:
// FactorsCalculator 计算各种配送因子
type FactorsCalculator interface {
// GetDirectionFactor 分析小哥订配送路线,得到路线因子
GetDirectionFactor(boyID int, orderID int) int
// GetUserLocationFactor 分析小哥订单的用户集中度,得到用户集中度因子
GetUserLocationFactor(boyID int, orderID int) int
}
заHandle
Другими словами, его тестовый пример не должен понимать, как рассчитать фактор маршрута и фактор концентрации пользователя, просто имитирует его интерфейс. Для расчета коэффициента это не правильно, это реализацияFactorsCalculator
Необходимо учитывать интерфейсные модули. (многоуровневое, подмодульное тестирование)
Каковы преимущества TDD
1. Углубить понимание потребностей
Как видно из приведенного выше примера, написание тестов сначала требует от нас организации тестовых случаев, а это означает, что разработчики должны усвоить требования перед написанием бизнес-кода. Мало того, что разработчик имеет более глубокое понимание требований, TDD также способствует достижению консенсуса между тестовыми, продуктовыми и другими командными ролями, чтобы избежать следующих неловких ситуаций:
Тест: Одноклассники по развитию, я нашел баг!
Разработка: Нет... Нет... Нет, проблема с вашим вариантом использования, это нормальная ситуация, если вы нам не верите, спросите у продукта
Продукт: эмм.. как будто я не так задумал
2. Повысить эффективность кодирования и качество программы
Мы пишем ошибки каждый день, и хотя мы не можем предотвратить генерацию ошибок, мы можем устранить большинство из них на этапе разработки и кодирования с помощью TDD. Как мы все знаем, в жизненном цикле разработки программного обеспечения чем позже обнаруживаются ошибки, тем выше стоимость. TDD может значительно улучшить качество наших программ и сэкономить нам много денег. Хотя использование TDD в краткосрочной перспективе может привести к небольшому снижению эффективности разработки, эта небольшая потеря незначительна по сравнению с потерями, вызванными ошибками. А после знакомства с TDD можно сказать, что эффективность кодирования только возрастает.
3. Помогает с программированием
Хороший дизайн должен развиваться постепенно.Никто не может четко спроектировать структуру и иерархию программы с самого начала.Чем больше дизайнов, тем легче "перепроектировать". С TDD дизайн программы развивается с постоянным рефакторингом, избегая «чрезмерного проектирования» и сохраняя программу слабо связанной и гибкой.
4. Имеет ценность для документации
Две самые раздражающие вещи в разработке:
- Читать код без документации
- Документируйте свой собственный код
Неоспорим тот факт, что документация необходима, но поддержание ее точности и актуальности в то же время требует значительных затрат. Разве не было бы прекрасно, если бы был документ, который обновлялся бы сам по мере обновления программы, и точность гарантировалась бы? Вы все еще беспокоитесь о написании документации? Тогда пусть TDD поможет вам!
Документация является неотъемлемой частью программного обеспечения. Как и остальное программное обеспечение, его необходимо часто тестировать, чтобы убедиться, что он точен и актуален. Самый эффективный способ добиться этого — интегрировать этот исполняемый документ в вашу систему непрерывной интеграции. TDD — очевидный выбор в этом направлении. На низком уровне модульные тесты хорошо подходят для этой документации. С другой стороны, на функциональном уровне BDD — это хороший способ использовать естественный язык для описания, что обеспечивает читабельность документа.
51testing.com/HTML/54/you-8…
Другие ссылки
Практика разработки через тестирование — Разработка через тестирование