【问题标题】:TDD Constructors GolangTDD 构造函数 Golang
【发布时间】:2018-11-16 09:50:24
【问题描述】:

尽管有一些关于此的帖子,但我还没有找到一个内容丰富的帖子。所以希望有几个人能就此发表意见。

让我无法拥有真正的 TDD 工作流程的一件事是,我无法找到一种干净的方法来测试必须连接到网络服务(如数据库)的事物。

例如:

type DB struct {
    conn *sql.DB
}

func NewDB(URL string) (*DB, err) {
    conn, err := sql.Open("postgres", URL)
    if err != nil {
        return nil, err
    }
}

我知道我可以将 sql 连接传递给 NewDB,或者直接传递给结构并将其分配给具有我需要的所有方法的接口,这将很容易测试。但在某个地方,我将不得不连接。我能找到的唯一测试方法是......

var sqlOpen = sql.Open
func CreateDB() *DB {
    conn, err := sqlOpen("postgres", "url...")
    if err != nil {
         log.Fatal(err)
    }

    dataBase = DB{
        conn: conn
    }
}

然后在测试中,您将 sqlOpen 函数替换为返回具有相同签名的函数,该函数将在一个测试用例中给出错误,而在另一个测试用例中不给出错误。但这感觉像是一种 hack,尤其是当您为同一个文件中的多个函数执行此操作时。有没有更好的办法?我正在使用的代码库在包和网络连接中有很多功能。因为我正在努力以干净的方式测试事物,所以这让我远离了 TDD。

【问题讨论】:

    标签: sql go tdd


    【解决方案1】:

    典型的业务应用程序在查询中有很多逻辑。如果未进行测试,我们会显着降低测试覆盖率并为回归错误留出空间。因此,模拟数据库存储库不是最佳选择。相反,我们可以模拟数据库本身并测试我们如何在 SQL 级别上使用它。

    以下是使用 DATA-DOG/go-sqlmock 的示例代码,但可能还有其他模拟 sql 数据库的库。

    首先,我们需要将 sql 连接注入到我们的代码中。 GO sql 连接是一个误导性的名称,它实际上是连接池,而不仅仅是单个数据库连接。这就是为什么在您的组合根目录中创建单个 *sql.DB 并在您的代码中重用即使您不编写测试也是有意义的。

    下面的示例展示了如何模拟 Web 服务。

    一开始,我们需要使用注入连接创建新的处理程序:

    // New creates new handler
    func New(db *sql.DB) http.Handler {
        return &handler{
            db:     db,
        }
    }
    

    处理程序代码:

    type handler struct {
        db     *sql.DB
    }
    
    func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
      // some code that loads person name from database using id
    }
    

    对模拟 DB 的代码进行单元测试。它使用stretchr/testify 进行断言:

    func TestHandler(t *testing.T) {
        db, sqlMock, _ := sqlmock.New()
        rows := sqlmock.NewRows([]string{"name"}).AddRow("John")
        // regex is used to match query
        // assert that we execute SQL statement with parameter and return data
        sqlMock.ExpectQuery(`select name from person where id \= \?`).WithArgs(42).WillReturnRows(rows)
        defer db.Close()
    
        sut := mypackage.New(db)
    
        r, err := http.NewRequest(http.MethodGet, "https://example.com", nil)
        require.NoError(t, err, fmt.Sprintf("Failed to create request: %v", err))
        w := httptest.NewRecorder()
    
        sut.ServeHTTP(w, r)
        // make sure that all DB expectations were met 
        err = sqlMock.ExpectationsWereMet()
        assert.NoError(t, err)
        // other assertions that check DB data should be here 
        assert.Equal(t, http.StatusOK, w.Code)
    }
    

    我们的测试针对 DB 断言简单的 SQL 语句。但是使用 go-sqlmock 可以测试所有 CRUD 操作和数据库事务。

    上面的测试还有一个弱点。我们测试了我们的 SQL 语句是从代码中执行的,但我们没有测试它是否适用于我们的真实数据库。这个问题不能通过单元测试来解决。唯一的解决方案是针对真实数据库的集成测试。

    不过,我们现在处于更好的位置。 Out 业务逻辑已经在单元测试中进行了测试。我们不需要创建大量集成测试来涵盖不同的场景和参数,相反,我们只需要对每个查询进行一个测试来验证 SQL 语法并匹配我们的数据库模式。

    测试愉快!

    【讨论】:

    • 谢谢。我同意测试 SQL 语法,这是专门针对 SQL 的好方法。但是其他网络服务,如 web-sockets、消息队列等呢?有没有比将它们设为全局并在运行时在测试中将它们换出更好的方法来测试连接函数?
    • @A.Rowden 您可以模拟对外部 Web 服务的调用 -stackoverflow.com/a/53231951/1420332。至少为oauth 重用令牌重用Http 客户端是有意义的。
    猜你喜欢
    • 2015-07-26
    • 2014-01-23
    • 1970-01-01
    • 2018-05-21
    • 1970-01-01
    • 2012-06-30
    • 1970-01-01
    • 2013-10-13
    • 2017-01-21
    相关资源
    最近更新 更多