Я учусь на Go, кодируя небольшой личный проект. Несмотря на то, что он небольшой, я решил провести тщательное юнит-тестирование, чтобы с самого начала выучить хорошие привычки на Го.
Тривиальные юнит-тесты были хороши и хороши, но теперь я озадачен зависимостями; Я хочу иметь возможность заменить некоторые вызовы функций на ложные. Вот фрагмент моего кода:
func get_page(url string) string {
get_dl_slot(url)
defer free_dl_slot(url)
resp, err := http.Get(url)
if err != nil { return "" }
defer resp.Body.Close()
contents, err := ioutil.ReadAll(resp.Body)
if err != nil { return "" }
return string(contents)
}
func downloader() {
dl_slots = make(chan bool, DL_SLOT_AMOUNT) // Init the download slot semaphore
content := get_page(BASE_URL)
links_regexp := regexp.MustCompile(LIST_LINK_REGEXP)
matches := links_regexp.FindAllStringSubmatch(content, -1)
for _, match := range matches{
go serie_dl(match[1], match[2])
}
}
Я хотел бы иметь возможность протестировать downloader () без фактического получения страницы через http - т.е. путем насмешки либо get_page (проще, так как он возвращает только содержимое страницы в виде строки), либо http.Get ().
Я нашел эту ветку: https://groups.google.com/forum/#!topic/golang-nuts/6AN1E2CJOxI, которая, похоже, связана с аналогичной проблемой. Джулиан Филлипс представляет свою библиотеку Withmock ( http://github.com/qur/withmock ) как решение, но я не могу заставить его работать. Вот, честно говоря, соответствующие части моего тестового кода, который в значительной степени является для меня культовым кодом:
import (
"testing"
"net/http" // mock
"code.google.com/p/gomock"
)
...
func TestDownloader (t *testing.T) {
ctrl := gomock.NewController()
defer ctrl.Finish()
http.MOCK().SetController(ctrl)
http.EXPECT().Get(BASE_URL)
downloader()
// The rest to be written
}
Результат теста следующий:
ERROR: Failed to install '_et/http': exit status 1
output:
can't load package: package _et/http: found packages http (chunked.go) and main (main_mock.go) in /var/folders/z9/ql_yn5h550s6shtb9c5sggj40000gn/T/withmock570825607/path/src/_et/http
Является ли Withmock решением моей проблемы с тестированием? Что я должен сделать, чтобы заставить его работать?
источник
Ответы:
Слава вам за практику хорошего тестирования! :)
Лично я не использую
gomock
(или какую-либо насмешливую структуру в этом отношении; издеваться в Go очень легко без этого). Я бы либо передал зависимостьdownloader()
функции в качестве параметра, либо я создал быdownloader()
метод для типа, и тип мог бы содержатьget_page
зависимость:Способ 1: передать
get_page()
в качестве параметраdownloader()
Основной:
Тест:
Метод 2: Создайте
download()
метод типаDownloader
:Если вы не хотите передавать зависимость в качестве параметра, вы также можете сделать
get_page()
член типа и создатьdownload()
метод этого типа, который затем может использоватьget_page
:Основной:
Тест:
источник
Если вы измените определение своей функции, чтобы использовать вместо нее переменную:
Вы можете изменить это в своих тестах:
Осторожно, другие ваши тесты могут провалиться, если они проверяют функциональность переопределенной вами функции!
Авторы Go используют этот шаблон в стандартной библиотеке Go для вставки тестовых хуков в код, чтобы упростить тестирование:
https://golang.org/src/net/hook.go
https://golang.org/src/net/dial.go#L248
https://golang.org/src/net/dial_test.go#L701
источник
Я использую немного другой подход, где публичные методы структуры реализуют интерфейсы, но их логика ограничивается просто упаковкой частных (не экспортируемых) функций, которые принимают эти интерфейсы в качестве параметров. Это дает вам степень детализации, которая вам понадобится для моделирования практически любой зависимости, и в то же время иметь чистый API для использования вне вашего набора тестов.
Чтобы понять это, необходимо понимать, что у вас есть доступ к неэкспортированным методам в вашем тестовом примере (т. Е. Из ваших
_test.go
файлов), поэтому вы тестируете те, а не тестируете экспортированные, которые не имеют логики внутри упаковки.Подводя итог: протестируйте неэкспортированные функции вместо тестирования экспортированных!
Давайте сделаем пример. Скажем, у нас есть структура Slack API, которая имеет два метода:
SendMessage
метод , который посылает запрос HTTP к Slack webhookSendDataSynchronously
метод , который дал кусочек струны перебирает их и вызываетSendMessage
для каждой итерацииТаким образом, чтобы тестировать,
SendDataSynchronously
не делая HTTP-запрос каждый раз, мы должны были бы высмеятьSendMessage
, верно?Что мне нравится в этом подходе, так это то, что, глядя на неэкспортированные методы, вы можете четко увидеть, каковы зависимости. В то же время экспортируемый вами API намного чище и требует меньше параметров для передачи, поскольку истинная зависимость здесь - просто родительский приемник, который сам реализует все эти интерфейсы. Тем не менее, каждая функция потенциально зависит только от одной ее части (один, может быть, два интерфейса), что значительно упрощает рефакторинг. Приятно видеть, как ваш код действительно связан, просто посмотрев на сигнатуры функций, я думаю, что он делает мощный инструмент против нюхательного кода.
Чтобы упростить задачу, я собрал все в один файл, чтобы вы могли запустить код на игровой площадке, но я предлагаю вам также посмотреть полный пример на GitHub, здесь находится файл slack.go, а здесь slack_test.go .
И тут все дело :)
источник
Я бы сделал что-то вроде
Основной
Тест
И я бы избежал
_
в Голанге. Лучше использовать CamelCaseисточник
p := patch(mockGetPage, getPage); defer p.done()
. Я новичок и пытался сделать это с помощьюunsafe
библиотеки, но в общем случае это невозможно.Предупреждение: это может немного увеличить размер исполняемого файла и немного снизить производительность во время выполнения. IMO, было бы лучше, если бы у golang была такая функция, как макро или функция декоратора.
Если вы хотите смоделировать функции без изменения их API, самый простой способ - это немного изменить реализацию:
Таким образом, мы можем на самом деле высмеивать одну функцию из других. Для более удобного мы можем предоставить такой насмешливый шаблон:
В тестовом файле:
источник
Учитывая, что предметом этого вопроса является юнит-тестирование, настоятельно рекомендуем использовать https://github.com/bouk/monkey. . Этот пакет позволяет вам тестировать макет без изменения исходного кода. Сравните с другим ответом, это более "ненавязчивый"。
ОСНОВНОЙ
МОК ТЕСТ
Плохая сторона это:
- Напомним Dave.C, этот метод небезопасен. Так что не используйте его вне модульного теста.
- это не идиоматический Go.
Хорошая сторона это:
++ Ненавязчив. Заставить вас делать вещи без изменения основного кода. Как сказал Томас.
++ Заставить вас изменить поведение пакета (может быть предоставлено третьей стороной) с наименьшим количеством кода.
источник