Go. FakeDb. Эмуляция работы БД в тестах

На днях писал тесты для модуля, который взаимодействует с базой данных. Привязывать модуль тестов к настоящей базе данных не хотелось — это создаёт дополнительные требования к окружению, где будет выполняться тестирование. Создавать сразу экземпляры типа 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|method_name|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() не работает ;)")
    }
}

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *