parrasajad / ramsql

In-memory SQL engine in Go sql/driver for testing purpose

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

RamSQL

Disposable SQL engine

RamSQL has been written to be used in your project's test suite.

Unit testing in Go is simple:

  • create a foo_test.go
  • add import testing
  • add func Test...(t *testing.T) {...}
  • run go test ./...

But then there are SQL queries, constraints, CRUD... and suddenly you need a PostgresSQL, setup scripts, and nothing is easy anymore.

The idea is to avoid setup, DBMS installation, and credentials management as long as possible. A unique engine is tied to a single sql.DB with as much sql.Conn as needed providing a unique DataSourceName. Bottom line: One DataSourceName per test and you have full test isolation in no time.

Installation

go get github.com/mlhoyt/ramsql

Usage

Let's say you want to test the function LoadUserAddresses:

func LoadUserAddresses(db *sql.DB, userID int64) ([]string, error) {
  query := `SELECT
      address.street_number,
      address.street
    FROM address 
    JOIN user_addresses
    ON address.id = user_addresses.address_id 
    WHERE user_addresses.user_id = $1;`

  rows, err := db.Query(query, userID)
  if err != nil {
    return nil, err
  }

  var addresses []string
  for rows.Next() {
    var number int
    var street string
    if err := rows.Scan(&number, &street); err != nil {
      return nil, err
    }
    addresses = append(addresses, fmt.Sprintf("%d %s", number, street))
  }

  return addresses, nil
}

Use RamSQL to test it in a disposable isolated in-memory SQL engine:

package myproject 

import (
  "database/sql"
  "fmt"
  "testing"

  _ "github.com/mlhoyt/ramsql/driver"
)


func TestLoadUserAddresses(t *testing.T) {
  batch := []string{
    `CREATE TABLE address (id BIGSERIAL PRIMARY KEY, street TEXT, street_number INT);`,
    `CREATE TABLE user_addresses (address_id INT, user_id INT);`,
    `INSERT INTO address (street, street_number) VALUES ('rue Victor Hugo', 32);`,
    `INSERT INTO address (street, street_number) VALUES ('boulevard de la République', 23);`,
    `INSERT INTO address (street, street_number) VALUES ('rue Charles Martel', 5);`,
    `INSERT INTO address (street, street_number) VALUES ('chemin du bout du monde ', 323);`,
    `INSERT INTO address (street, street_number) VALUES ('boulevard de la liberté', 2);`,
    `INSERT INTO address (street, street_number) VALUES ('avenue des champs', 12);`,
    `INSERT INTO user_addresses (address_id, user_id) VALUES (2, 1);`,
    `INSERT INTO user_addresses (address_id, user_id) VALUES (4, 1);`,
    `INSERT INTO user_addresses (address_id, user_id) VALUES (2, 2);`,
    `INSERT INTO user_addresses (address_id, user_id) VALUES (2, 3);`,
    `INSERT INTO user_addresses (address_id, user_id) VALUES (4, 4);`,
    `INSERT INTO user_addresses (address_id, user_id) VALUES (4, 5);`,
  }

  db, err := sql.Open("ramsql", "TestLoadUserAddresses")
  if err != nil {
    t.Fatalf("sql.Open : Error : %s\n", err)
  }
  defer db.Close()

  for _, b := range batch {
    _, err = db.Exec(b)
    if err != nil {
      t.Fatalf("sql.Exec: Error: %s\n", err)
    }
  }

  addresses, err := LoadUserAddresses(db, 1)
  if err != nil {
    t.Fatalf("Too bad! unexpected error: %s", err)
  }

  if len(addresses) != 2 {
    t.Fatalf("Expected 2 addresses, got %d", len(addresses))
  }

}

Done. No need for a running PostgreSQL or a setup. Your tests are isolated, and compliant with go tools.

RamSQL binary

Let's say you have a SQL describing your application structure:

CREATE TABLE IF NOT EXISTS address (id BIGSERIAL PRIMARY KEY, street TEXT, street_number INT);
CREATE TABLE IF NOT EXISTS user_addresses (address_id INT, user_id INT);

You may want to test its validity:

$ go install github.com/mlhoyt/ramsql
$ ramsql < schema.sql
ramsql> Query OK. 1 rows affected
ramsql> Query OK. 1 rows affected
$ echo $?
0

Features

Unit testing

  • Full isolation between tests
  • No setup (either file or databases)
  • Good performance

SQL parsing

  • Databse schema validation
  • ALTER file validation

Stress testing

  • File system full error with configurable maximum database size
  • Random configurable slow queries
  • Random deconnection

About

In-memory SQL engine in Go sql/driver for testing purpose

License:BSD 3-Clause "New" or "Revised" License


Languages

Language:Go 99.9%Language:Makefile 0.1%