На днях писал тесты для модуля, который взаимодействует с базой данных. Привязывать модуль тестов к настоящей базе данных не хотелось — это создаёт дополнительные требования к окружению, где будет выполняться тестирование. Создавать сразу экземпляры типа sql.Rows, с нужными табличками также не хотелось — для меня этот тип "чёрный ящик", и хотелось бы, чтобы так оно и оставалось. Поиском по теме ничего интересного также не нашлось.
Захотел поделиться с сообществом своей находкой: в основной поставке Go, есть готовый инструмент для таких нужд: называется FakeDb.
Что умеет данный инструмент?
По сути это простейшая, но полноценная как объект DB, база данных, умеющая создавать таблицы, наполнять их и делать из них простейшие выборки. Все созданные таблицы временные и существуют, пока существует экземпляр FakeDb. Кроме того, доступна эмуляция задержки при вызове команды и эмуляция исключения.
Команды FakeDb
Команды состоят из фраз, разделённых вертикальной чертой — |. Фраза — это или ключевое слово, или выражение вида "ключ=значение". Где ключ — это имя столбца.
WIPE
Команда уничтожает таблицы и возвращает пустой результат. Кроме того это проверка, что нет взаимоблокировки.
Пример вызова:
WIPE
CREATE
Команда создаёт таблицу с указанными столбцами указанного типа.
Формат вызова:
CREATE|Имя_таблицы|Столбец1=Тип,...,СтолбецN=Тип
Поддерживаемые типы:
- bool
- nullbool — булево или NULL
- int32
- string
- nullstring — строка или NULL
- int64
- nullint64 — int64 или NULL
- float64
- nullfloat64 — float64 или NULL
- datetime
- any — пустой интерфейс, т. е. любой тип
Пример вызова:
CREATE|people|name=string,age=int32,photo=any,dead=bool,bdate=datetime
INSERT
Команда добавляет строки в созданную таблицу.
Формат вызова:
INSERT|Имя_таблицы|Столбец1=Значение,...,СтолбецN
Здесь и далее значение можно задать тремя способами:
1) Явно указать значение, например 123
.
2) Указать "?", а значение передать параметром.
3) Указать "? имя_параметра" и значение передать именованным параметром.
Пример вызова:
INSERT|people|name=Alice,age=?,photo=?photo
SELECT
Команда позволяет выбрать или все строки таблицы, или с некоторым отбором.
Формат вызова:
SELECT|Имя_таблицы|Столбец1,...,СтолбецN| SELECT|Имя_таблицы|Столбец1,...,СтолбецN|ОтборПоСтолбцу1=?,...,ОтборПоСтолбцуM
Пример вызова:
SELECT|categories|category_id,category_parent_id,category_name| SELECT|categories|category_id,category_parent_id,category_name|category_parent_id=?parent
В результате возвращается полновесный экземпляр sql.Rows.
WAIT
Имитирует задержку при выполнении команды. Необходимо добавить перед любой указанной выше командой.
Формат вызова:
WAIT|Задержка|Команда
Задержка указывается как количество и постфикс, обозначающий единицу измерения: s — секунды, n — наносекунды, u — миллисекунды, h — часы, и так далее (используется функция time.ParseDuration()).
Пример вызова:
WAIT|1s|SELECT|categories|category_id,category_parent_id,category_name|
PANIC
Генерирует исключение при вызове команды.
Формат вызова:
PANIC|Имя_метода|Команда
Значение "Имя_метода" будет помещено в поле stmt.panic
(тип fakeStmt).
Пример вызова:
PANIC|blablabla|SELECT|categories|category_id,category_parent_id,category_name|
Использование
FakeDb создавался для тестирования пакета "sql" основной поставки в виде скрипта тестирования. В виде отдельного пакета я его не нашёл. Поэтому выделил в пакет сам и разместил здесь: gihub.
Для выделения в пакет потребовались незначительные правки.
Пример использования
package packname import ( "fmt" "testing" "io/ioutil" "database/sql" "github.com/a1div0/fakedb" ) func TestFoo(t *testing.T) { fc := &fakedb.FakeConnector{ } db := sql.OpenDB(fc) if db.Driver() != fakedb.Fdriver { t.Error("OpenDB should return the driver of the Connector") return } if _, err := db.Exec("WIPE"); err != nil { t.Error("exec wipe: %v", err) } defer db.Close() db.Exec("CREATE|users|user_email=string,user_id=int64") db.Exec("INSERT|users|user_email=?,user_id=?", "test@email.com", 345) rows, err := db.Query( "SELECT|users|user_id|user_email=?email", sql.Named("email", user_email), ) if err != nil { t.Error(err) } result, err := Foo(rows) // тестируемая функция if err != nil { t.Error(err) } if result != 123 { t.Error("Торжественно сообщаем, что функция Foo() не работает ;)") } }
Спасибо за внимание.
P. S.: Пожалуйста напишите в комментариях — используете ли вы в тестах эмуляцию БД? Если да — то какой пакет?
ссылка на оригинал статьи https://habr.com/ru/post/485280/