update database test example
Этот коммит содержится в:
родитель
0d5e51f091
коммит
f748d3cb70
6 изменённых файлов: 26 добавлений и 162 удалений
|
@ -1,8 +1,22 @@
|
|||
.PHONY: test
|
||||
|
||||
define DB_SQL
|
||||
CREATE TABLE users (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT NOT NULL,
|
||||
`username` VARCHAR(32) NOT NULL,
|
||||
`email` VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE INDEX `uniq_email` (`email`)
|
||||
) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB;
|
||||
endef
|
||||
|
||||
export DB_SQL
|
||||
|
||||
SQL := "$$DB_SQL"
|
||||
|
||||
test:
|
||||
mysql -u root -e 'DROP DATABASE IF EXISTS `godog_test`'
|
||||
mysql -u root -e 'CREATE DATABASE IF NOT EXISTS `godog_test`'
|
||||
mysql -u root godog_test < db.sql
|
||||
@mysql -u root godog_test -e $(SQL)
|
||||
godog users.feature
|
||||
|
||||
.PHONY: test
|
||||
|
|
|
@ -4,9 +4,10 @@ The following example demonstrates steps how we describe and test our API with D
|
|||
To start with, see [API example](https://github.com/DATA-DOG/godog/tree/master/examples/api) before.
|
||||
We have extended it to be used with database.
|
||||
|
||||
The interesting point is, that we have **txdb.go** which has an implementation of custom sql.driver
|
||||
to allow execute every and each scenario within a **transaction**. After it completes, transaction
|
||||
is rolled back so the state could be clean for the next scenario.
|
||||
The interesting point is, that we have [go-txdb](https://github.com/DATA-DOG/go-txdb) library,
|
||||
which has an implementation of custom sql.driver to allow execute every and each scenario
|
||||
within a **transaction**. After it completes, transaction is rolled back so the state could
|
||||
be clean for the next scenario.
|
||||
|
||||
To run **users.feature** you need MySQL installed on your system with an anonymous root password.
|
||||
Then run:
|
||||
|
|
|
@ -29,8 +29,6 @@ func (s *server) users(w http.ResponseWriter, r *http.Request) {
|
|||
rows, err := s.db.Query("SELECT id, email, username FROM users")
|
||||
defer rows.Close()
|
||||
switch err {
|
||||
case sql.ErrNoRows:
|
||||
users = make([]*user, 0) // an empty array in this case
|
||||
case nil:
|
||||
for rows.Next() {
|
||||
user := &user{}
|
||||
|
@ -40,6 +38,9 @@ func (s *server) users(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
users = append(users, user)
|
||||
}
|
||||
if len(users) == 0 {
|
||||
users = make([]*user, 0) // an empty array in this case
|
||||
}
|
||||
default:
|
||||
fail(w, fmt.Sprintf("failed to fetch users: %s", err), http.StatusInternalServerError)
|
||||
return
|
||||
|
|
|
@ -8,13 +8,14 @@ import (
|
|||
"net/http/httptest"
|
||||
"strings"
|
||||
|
||||
"github.com/DATA-DOG/go-txdb"
|
||||
"github.com/DATA-DOG/godog"
|
||||
"github.com/cucumber/gherkin-go"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// we register an sql driver txdb
|
||||
Register("mysql", "root@/godog_test")
|
||||
txdb.Register("txdb", "mysql", "root@/godog_test")
|
||||
}
|
||||
|
||||
type apiFeature struct {
|
||||
|
@ -27,7 +28,7 @@ func (a *apiFeature) resetResponse(interface{}) {
|
|||
if a.db != nil {
|
||||
a.db.Close()
|
||||
}
|
||||
db, err := sql.Open("txdb", "")
|
||||
db, err := sql.Open("txdb", "api")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
CREATE TABLE users (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT NOT NULL,
|
||||
`username` VARCHAR(32) NOT NULL,
|
||||
`email` VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE INDEX `uniq_email` (`email`)
|
||||
) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB;
|
|
@ -1,146 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Register a txdb sql driver which can be used to open
|
||||
// a single transaction based database connection pool
|
||||
func Register(drv, dsn string) {
|
||||
sql.Register("txdb", &txDriver{dsn: dsn, drv: drv})
|
||||
}
|
||||
|
||||
// txDriver is an sql driver which runs on single transaction
|
||||
// when the Close is called, transaction is rolled back
|
||||
type txDriver struct {
|
||||
sync.Mutex
|
||||
tx *sql.Tx
|
||||
|
||||
drv string
|
||||
dsn string
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (d *txDriver) Open(dsn string) (driver.Conn, error) {
|
||||
// first open a real database connection
|
||||
var err error
|
||||
if d.db == nil {
|
||||
db, err := sql.Open(d.drv, d.dsn)
|
||||
if err != nil {
|
||||
return d, err
|
||||
}
|
||||
d.db = db
|
||||
}
|
||||
if d.tx == nil {
|
||||
d.tx, err = d.db.Begin()
|
||||
}
|
||||
return d, err
|
||||
}
|
||||
|
||||
func (d *txDriver) Close() error {
|
||||
err := d.tx.Rollback()
|
||||
d.tx = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *txDriver) Begin() (driver.Tx, error) {
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (d *txDriver) Commit() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *txDriver) Rollback() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *txDriver) Prepare(query string) (driver.Stmt, error) {
|
||||
return &stmt{drv: d, query: query}, nil
|
||||
}
|
||||
|
||||
type stmt struct {
|
||||
query string
|
||||
drv *txDriver
|
||||
}
|
||||
|
||||
func (s *stmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||
s.drv.Lock()
|
||||
defer s.drv.Unlock()
|
||||
|
||||
st, err := s.drv.tx.Prepare(s.query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer st.Close()
|
||||
var iargs []interface{}
|
||||
for _, arg := range args {
|
||||
iargs = append(iargs, arg)
|
||||
}
|
||||
return st.Exec(iargs...)
|
||||
}
|
||||
|
||||
func (s *stmt) NumInput() int {
|
||||
return -1
|
||||
}
|
||||
|
||||
func (s *stmt) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||
s.drv.Lock()
|
||||
defer s.drv.Unlock()
|
||||
|
||||
st, err := s.drv.tx.Prepare(s.query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// do not close the statement here, Rows need it
|
||||
var iargs []interface{}
|
||||
for _, arg := range args {
|
||||
iargs = append(iargs, arg)
|
||||
}
|
||||
rs, err := st.Query(iargs...)
|
||||
return &rows{rs: rs}, err
|
||||
}
|
||||
|
||||
type rows struct {
|
||||
err error
|
||||
rs *sql.Rows
|
||||
}
|
||||
|
||||
func (r *rows) Columns() (cols []string) {
|
||||
cols, r.err = r.rs.Columns()
|
||||
return
|
||||
}
|
||||
|
||||
func (r *rows) Next(dest []driver.Value) error {
|
||||
if r.err != nil {
|
||||
return r.err
|
||||
}
|
||||
if r.rs.Err() != nil {
|
||||
return r.rs.Err()
|
||||
}
|
||||
if !r.rs.Next() {
|
||||
return io.EOF
|
||||
}
|
||||
values := make([]interface{}, len(dest))
|
||||
for i := range values {
|
||||
values[i] = new(interface{})
|
||||
}
|
||||
if err := r.rs.Scan(values...); err != nil {
|
||||
return err
|
||||
}
|
||||
for i, val := range values {
|
||||
dest[i] = *(val.(*interface{}))
|
||||
}
|
||||
return r.rs.Err()
|
||||
}
|
||||
|
||||
func (r *rows) Close() error {
|
||||
return r.rs.Close()
|
||||
}
|
Загрузка…
Создание таблицы
Сослаться в новой задаче