Я выполнил этот учебник, чтобы имитировать соединение с базой данных. Но когда я пытаюсь пойти дальше и реализовать интерфейс для типа возвращаемого значения метода Query, я получаю сообщение об ошибке.

Вот рабочая версия, которая не работает для MockDB, но работает для main:

// The DB interface
type SQLDB interface {
    Query(query string, args ...interface{}) (*sql.Rows, error)
}

type MockDB struct {
    // ...
}

func (m *MockDB) Query(query string, args ...interface{}) (SQLRows, error) {
    // var row MockRows
    // ...
    return rows, nil
}

// #################################

// The Rows interface
type SQLRows interface {
    Next() bool
    Scan(dest ...interface{}) error
}

type MockRows struct {
    // ...
}

func (m *MockRows) Next() bool {
    // ...
    return true
}

func (m *MockRows) Scan(dest ...interface{}) error {
    // ...
    return nil
}


// #################################

// Usage

func GetStuff(db SQLDB) (*sql.Rows, error) {
    results := db.Query("query")
    // ...
    return results, nil
}

// #################################

// Main
var db SQLDB
func main() {
    db = sql.Open("driver", "uri")
    results := GetResult(db)
}

// #################################

// Test
func TestGetStuff(t *testing.T) {
    mockDB = new(MockDB)
    results := GetResult(mockDB)
    // ...
}


Это работает для основного, но не для теста, и я получаю следующую ошибку при запуске тестов:

*MockDB does not implement SQLDB (wrong type for Query method)
    have Query(string, ...interface {}) (SQLRows, error)
    want Query(string, ...interface {}) (*sql.Rows, error)

Вот версия, которая работает для MockDB, но не для main:

// The DB interface
type SQLDB interface {
    Query(query string, args ...interface{}) (SQLRows, error)
}

type MockDB struct {
    // ...
}

func (m *MockDB) Query(query string, args ...interface{}) (SQLRows, error) { // <<<<<< Changed
    // var row MockRows
    // ...
    return rows, nil
}

// #################################

// The Rows interface
type SQLRows interface {
    Next() bool
    Scan(dest ...interface{}) error
}

type MockRows struct {
    // ...
}

func (m *MockRows) Next() bool {
    // ...
    return true
}

func (m *MockRows) Scan(dest ...interface{}) error {
    // ...
    return nil
}


// #################################

// Usage

func GetStuff(db SQLDB) (SQLRows, error) { // <<<<<< Changed
    results := db.Query("query")
    // ...
    return results, nil
}

// #################################

// Main
var db SQLDB
func main() {
    db = sql.Open("driver", "uri")
    results := GetResult(db)
}

// #################################

// Test
func TestGetStuff(t *testing.T) {
    mockDB = new(MockDB)
    results := GetResult(mockDB)
    // ...
}


И вот ошибка:

 *sql.DB does not implement SQLDB (wrong type for Query method)
        have Query(string, ...interface {}) (*sql.Rows, error)
        want Query(string, ...interface {}) (SQLRows, error)

Как я могу заставить это работать для обоих? Или это просто невозможно сделать?

go
0
XO39 28 Июл 2020 в 20:27
Заставив оба возвращать один и тот же тип, который, если вы хотите иметь возможность имитировать его, должен быть типом интерфейса. *sql.Rows не является типом интерфейса.
 – 
mkopriva
28 Июл 2020 в 21:36
Если это не очевидно из приведенного выше, вы не можете использовать *sql.DB как SQLDB, потому что *sql.DB не реализует интерфейс SQLDB. Однако вы можете обернуть *sql.DB в тонкую оболочку, которая реализует SQLDB и чья реализация просто делегирует вызов обернутому *sql.DB.
 – 
mkopriva
28 Июл 2020 в 21:40
1
Как я сказал, вы должны обернуть соединение *sql.DB тонкой оболочкой, которая реализует интерфейс SQLDB, на самом деле это так просто.
 – 
mkopriva
29 Июл 2020 в 11:12
1
 – 
mkopriva
29 Июл 2020 в 11:22
1
Я добавил ответ. Имейте в виду, что метод (*sql.Rows).Close() ДОЛЖЕН быть вызван. как только вы закончите со строками, иначе вы потеряете соединения, и ваша программа в конечном итоге выйдет из строя. Поэтому вам нужно открыть его через интерфейс SQLRows и вызвать вручную или убедиться, что он вызывается каким-то другим способом, каким бы он ни был.
 – 
mkopriva
29 Июл 2020 в 11:57

1 ответ

Лучший ответ

Что вы можете сделать, так это обернуть *sql.DB тонкой оболочкой, которая реализует SQLDB и чья реализация просто делегирует вызов обернутому *sql.DB.

type dbwrapper struct{ db *sql.DB }

func (w *dbwrapper) Query(query string, args ...interface{}) (SQLRows, error) {
    return w.db.Query(query, args...) // delegate
}

Теперь вместо прямого использования *sql.DB вы оборачиваете его в dbwrapper, который затем можно использовать как интерфейс SQLDB.

func GetStuff(db SQLDB) (SQLRows, error) {
    // ...
}

func main() {
    db, err := sql.Open("driver", "uri")
    if err != nil {
        panic(err)
    }

    w := &dbwrapper{db}
    rows, err := GetStuff(w) // will compile
    // ...
}
1
mkopriva 29 Июл 2020 в 11:53