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:
|
test:
|
||||||
mysql -u root -e 'DROP DATABASE IF EXISTS `godog_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 -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
|
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.
|
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.
|
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
|
The interesting point is, that we have [go-txdb](https://github.com/DATA-DOG/go-txdb) library,
|
||||||
to allow execute every and each scenario within a **transaction**. After it completes, transaction
|
which has an implementation of custom sql.driver to allow execute every and each scenario
|
||||||
is rolled back so the state could be clean for the next 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.
|
To run **users.feature** you need MySQL installed on your system with an anonymous root password.
|
||||||
Then run:
|
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")
|
rows, err := s.db.Query("SELECT id, email, username FROM users")
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
switch err {
|
switch err {
|
||||||
case sql.ErrNoRows:
|
|
||||||
users = make([]*user, 0) // an empty array in this case
|
|
||||||
case nil:
|
case nil:
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
user := &user{}
|
user := &user{}
|
||||||
|
@ -40,6 +38,9 @@ func (s *server) users(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
users = append(users, user)
|
users = append(users, user)
|
||||||
}
|
}
|
||||||
|
if len(users) == 0 {
|
||||||
|
users = make([]*user, 0) // an empty array in this case
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
fail(w, fmt.Sprintf("failed to fetch users: %s", err), http.StatusInternalServerError)
|
fail(w, fmt.Sprintf("failed to fetch users: %s", err), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|
|
@ -8,13 +8,14 @@ import (
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/DATA-DOG/go-txdb"
|
||||||
"github.com/DATA-DOG/godog"
|
"github.com/DATA-DOG/godog"
|
||||||
"github.com/cucumber/gherkin-go"
|
"github.com/cucumber/gherkin-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// we register an sql driver txdb
|
// we register an sql driver txdb
|
||||||
Register("mysql", "root@/godog_test")
|
txdb.Register("txdb", "mysql", "root@/godog_test")
|
||||||
}
|
}
|
||||||
|
|
||||||
type apiFeature struct {
|
type apiFeature struct {
|
||||||
|
@ -27,7 +28,7 @@ func (a *apiFeature) resetResponse(interface{}) {
|
||||||
if a.db != nil {
|
if a.db != nil {
|
||||||
a.db.Close()
|
a.db.Close()
|
||||||
}
|
}
|
||||||
db, err := sql.Open("txdb", "")
|
db, err := sql.Open("txdb", "api")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
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()
|
|
||||||
}
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче