前⾯我们学习了很多的知识,也看到了⼀些例⼦,⼤家也对Go Test相关的之后有了⼀个全⾯的了解。这⼀章我通过⼀个常⻅的web程序示例,演示如何组织和应⽤单元测试。
⾸先这是⼀个普通的多层的web应⽤程序,按照分层的架构程序也分成了⼏个package:
- model: 数据模型, 定义了Book类型和Author,只是类型的定义,并没有业务逻辑,不需要单元测试
- dao: 提供数据库的访问。作为例⼦这⾥提供了⼀个 InsertBook 的实现,往数据库中插⼊⼀条书籍的信息
- service:提供CreateBook⽅法,实际可以检查书籍是否存在,作者是否存在等逻辑判断,如果满⾜则调⽤dao的函数插⼊⼀条书籍信息
- api:也叫controller,提供CreateBook handler,检查request的参数,如果满⾜则调⽤service层创建书籍
DAO层测试
dao层依赖数据库,根据先前的介绍,有多重⽅法可以提供测试。
这⼀次我想通过使⽤sqlmock的⽅式提供测试,不依赖任何数据库或者容器。
func TestBookStore_InsertBook(t *testing.T) {
db, mock, err := sqlmock.New()
assert.NoError(t, err, "an error '%s' was not expected when opening a stub
database connection", err)
defer db.Close()
store := &bookStore{
db: db,
}
book := &model.Book{
Title: "The Go Programming Language",
AuthorID: 1,
ISBN: "978-0134190440",
Subject: "computers",
}
mock.ExpectExec("INSERT INTO books").WillReturnResult(sqlmock.NewResult(1,
1))
err = store.InsertBook(context.TODO(), book)
require.NoError(t, err)
assert.NotZero(t, book.ID)
}
这⾥使⽤sqlmock mock Exec⽅法,假定它执⾏成功,返回lastID和影响⾏数。执⾏这个单元测试可以看到测试成功。
Service层的测试
service依赖dao层,依然可以使⽤sqlmock模拟测试,但是这⼀次我想换⼀种测试⽅法。使⽤嵌⼊式数据sqlite3进⾏真正的数据库访问。
func TestBookService_CreateBook(t *testing.T) {
db := util.CreateTestDB(t)
defer db.Close()
bookService := NewBookService(db)
book := &model.Book{
Title: "The Go Programming Language",
AuthorID: 1,
ISBN: "978-0134190440",
Subject: "computers",
}
err := bookService.CreateBook(context.TODO(), book)
require.NoError(t, err)
assert.NotZero(t, book.ID)
}
func CreateTestDB(t *testing.T) *sql.DB {
db, err := sql.Open("sqlite3", "file:../testdata/test.db?cache=shared")
assert.NoError(t, err)
db.Exec(`
CREATE TABLE IF NOT EXISTS books (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT,
isbn TEXT,
subject TEXT,
author_id INTEGER
);
DELETE FROM books;
`)
return db
}
⼀般我们会把测试⽤的材料放在testdata⽂件夹下。testdata⽂件夹是⼀个特殊的⽂件夹。go编译器不会把它作为⼀个⼦package进⾏编译。
API 层的测试
同样, API层也有多重测试⽅法。⾸先我们看使⽤嵌⼊式数据库的⽅法,它把service层、dao层串联起来,其实是⼀个集成测试:
package api
import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"github.com/smallnest/go_test_workshop/s7/book/model"
"github.com/smallnest/go_test_workshop/s7/book/service"
"github.com/smallnest/go_test_workshop/s7/book/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestBookController_GetBook(t *testing.T) {
db := util.CreateTestDB(t)
defer db.Close()
bookService := service.NewBookService(db)
bc := &BookController{bookService: bookService}
data := url.Values{}
data.Set("title", "The Go Programming Language")
data.Set("autherID", "1")
data.Set("isbn", "978-0134190440")
data.Set("subject", "computers")
r := httptest.NewRequest("POST", "http://example.com/foo",
strings.NewReader(data.Encode()))
r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
w := httptest.NewRecorder()
bc.CreateBook(w, r)
resp := w.Result()
require.Equal(t, http.StatusOK, resp.StatusCode)
body, _ := io.ReadAll(resp.Body)
var book model.Book
err := json.Unmarshal(body, &book)
require.NoError(t, err)
assert.NotZero(t, book.ID)
}
我们也可以使⽤gomonkey只针对api这⼀层进⾏测试。哪⼀种测试⽅法好呢?并⽆定式,哪⼀种写起来更⽅便,能够覆盖你的测试场景就⽤哪种。你最熟练使⽤哪⼀种就⽤哪⼀种。
package api
import (
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"github.com/agiledragon/gomonkey/v2"
"github.com/smallnest/go_test_workshop/s7/book/model"
"github.com/smallnest/go_test_workshop/s7/book/service"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestBookController_GetBook_By_Monkey(t *testing.T) {
bookService := service.NewBookService(nil)
bc := &BookController{bookService: bookService}
data := url.Values{}
data.Set("title", "The Go Programming Language")
data.Set("autherID", "1")
data.Set("isbn", "978-0134190440")
data.Set("subject", "computers")
r := httptest.NewRequest("POST", "http://example.com/foo",
strings.NewReader(data.Encode()))
r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
book := &model.Book{
ID: 6,
Title: "The Go Programming Language",
AuthorID: 1,
ISBN: "978-0134190440",
Subject: "computers",
}
patches := gomonkey.ApplyMethodFunc(bookService, "CreateBook", func(ctx
context.Context, b *model.Book) error {
b.ID = 6
return nil
})
defer patches.Reset()
w := httptest.NewRecorder()
bc.CreateBook(w, r)
resp := w.Result()
require.Equal(t, http.StatusOK, resp.StatusCode)
body, _ := io.ReadAll(resp.Body)
err := json.Unmarshal(body, &book)
require.NoError(t, err)
assert.NotZero(t, book.ID)
}
作者:admin 创建时间:2024-10-22 01:56
最后编辑:admin 更新时间:2024-10-22 02:10
最后编辑:admin 更新时间:2024-10-22 02:10