ORM 盛行下,你知道真正執行的 sql 么
VSole2022-08-03 16:11:35
推薦理由
在這個 orm 盛行的服務端開發環境下,已經很少有人寫 raw sql 了吧,畢竟 orm 框架可以幫助開發人員屏蔽 db 的細節,同時還能在一定成程度上預防 sql 注入的風險,大多數情況下,對業務代碼的單測,會使用 sqlite 來 mock 真正的 db,以驗證功能的完備性,但當你寫完一條 orm 語句后,是否會校驗,生成的真正執行的 sql 是你的預期么?是否會校驗,在代碼變更的時候 sql 語句是否也發生了變更呢?
針對以上兩個問題,sqlmock 可以完成對 sql 語句的單側,讓你對 orm 生成的 sql 了如指掌,同時清晰 test raw sql 也讓 review 的同事快樂加倍。
實現原理
其實 sql mock 只是實現了 go sql/driver 的接口,用于模擬數據庫的連接,本質上并不會進行存儲數據,只能特定的返回。
實際栗子
通過 sqlmock 測試創建 user 的sql語句
package main
import (
"github.com/DATA-DOG/go-sqlmock"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"testing"
)
type User struct {
ID int64 `gorm:"column:id"`
UserName string `gorm:"column:user_name"`
}
func (User) TableName() string {
return "users"
}
func CreateUser(db *gorm.DB, user *User) error{
return db.Table(user.TableName()).Create(&user).Error
}
const (
rawSQLCreateUser = "INSERT INTO `users` (`user_name`,`id`) VALUES (?,?)"
)
func TestShouldUpdateStats(t *testing.T) {
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
dialector := mysql.New(mysql.Config{
DriverName: "mysql",
Conn: db,
SkipInitializeWithVersion:true,
})
user := &User{
ID: 1,
UserName: "zr",
}
gdb, err := gorm.Open(dialector, &gorm.Config{})
gdb = gdb.Debug()
mock.ExpectBegin()
mock.ExpectExec(rawSQLCreateUser).
WithArgs(user.UserName,user.ID).WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
if err = CreateUser(gdb, user); err != nil {
t.Errorf("gorm create fail, err=%v", err)
}
if err = mock.ExpectationsWereMet(); err != nil {
t.Errorf("unexcept sql, err=%v", err)
}
}
總結
PS:筆者的老板對 sql 治理比較看重,所以在業務實現中會在存儲層對于每一個 sql 寫 sqlmock。
sqlmock 雖然不能對整個業務功能進行集成測試,但是在 dao 層,對存儲數據的操作進行 sql 語句層面的校驗個人覺得是有必要的,一方面這會使 sql 的管理更加高效(將定義的 rawsql 單獨放在一個文件中),另一方面,數據庫層面改動時,有完整的 sql 校驗邏輯,方便 review 的人直觀的看出來具體數據有什么變化。
順帶一提:這個庫的開源作者想要找個開源維護者,感興趣的也可以參與嘗試下。
VSole
網絡安全專家