[TOC]
# Mock依賴
有的時候,由于業務邏輯的復雜性,功能代碼并不會就這么直接,往往還會摻雜很多其他組件,這就給我們的測試工作帶來很大的麻煩,我這里列舉幾個常見的依賴:
* 組件依賴
* 函數依賴
組件依賴和函數依賴是兩種比較常見的依賴,但是,這兩種依賴也是可以擴展開來說的,既可能來自于我們自己編寫的組件/函數,也可能是引入其他人寫的。但是,無妨,對于這些情況,我們都會做一些分析
## 組件依賴處理
傳一個 Stub 組件進入,從而達到控制依賴組件行為的效果
舉一個例子先,例如我們比較常見的 Service 層和 DAO 層的操作,Service 處理完邏輯之后,交給 DAO 層進行持久化,或者需要調用 DAO 層從持久化中獲取一些必要的數據;在測試的時候,我們很多時候不希望真的持久化或者從持久化中獲取數據,那么就會對 DAO 層進行一些 Mock
~~~
import "fmt"
type Data struct {
Field string
}
type Dao interface {
ReadAll() []Data
SaveData(d *Data)
}
type MongoDao struct {
}
func (d MongoDao) ReadAll() []Data {
return []Data{}
}
func (d MongoDao) SaveData(data *Data) {
//...
}
type Service struct {
Dao *Dao
}
func (s *Service) Login (username string) bool {
users := (*s.Dao).ReadAll()
for _, user := range users {
if username == user.Field {
return true
}
}
return false
}
func Newservice(d Dao) *Service {
srv := Service{Dao: &d}
return &srv
}
func main() {
d := MongoDao{}
srv := Newservice(d)
fmt.Println(srv.Login("abc"))
}
~~~
這里我們想要測試**Service**的正確性,但是又不想要真的持久化 DAO,所以,這個時候我們會自己創建一個 Stub,然后提供給 Service,同時,我們還能操作 DAO 的行為,達到運行得效果
~~~
//用StubDao代替Mongodb
type StubDao struct {
}
func (d StubDao) ReadAll() []Data {
return []Data{Data{"abc"}}
}
func (d StubDao) SaveData(data *Data) {
}
func TestLogin(t *testing.T) {
d := StubDao{}
srv := NewService(d)
rst := srv.Login("abc")
if !rst {
t.Error("login error")
}
}
~~~
這里對測試代碼稍微改了一下,可以發現,我們可以通過修改一個變量來控制 Stub 的輸出,從而達到測試不同功能的效果,這就解決了組件依賴的問題
## 函數依賴
函數依賴相比于組件依賴會更麻煩一點,因為我們在前面可以看到,組件依賴的話我們可以傳遞 Stub 進行,這樣我們可以隨意得控制 Stub 的行為,但是函數不行呀,這里我們又不能傳函數進去,因為函數是被 import 進去的啊。問題就在這了,因為函數是被 import 進去的,所以可以理解為函數是全局的了,既然這樣,那么我們為什么不修改一下函數呢?什么意思?我們先來看著正常的業務例子
~~~
var Login = func(username, password string) bool {
if username == password {
return false
}
return true
}
func Reply(username, password, msg string) bool {
if Login(username, password) {
fmt.Println(msg)
return true
}
return false
}
func stu() {
Reply("a", "b", "aa")
Reply("a", "b", "bb")
}
~~~
要先登錄,然后登錄完之后我們才能回復消息,這里我們的登錄邏輯是簡單的,但是,在實際業務中可能這里的登錄邏輯就設計到 DB 訪問等等,我們希望不走真實的邏輯,而是自己來控制`Login`的行為
先分析一下我們的 UT 目的,我們的目的是測試`Reply`函數,我們期望是`Login`成功,那么`Reply`也應該是成功的;如果`Login`失敗,那么`Reply`也應該是失敗的。這個測試結論不應該被`Login`所影響,及時以后`Login`邏輯修改了,我們也應該是這個邏輯,不會受到影響,那么我們可以這么編寫 UT
~~~
func TestSuccReply(t *testing.T) {
origLogin := Login
defer func() {
Login = origLogin
}()
Login = func(username, password string) bool {
return true
}
if !Reply("a", "a", "aaa") {
t.Errorf("reply false for login success")
}
}
func TestLogin(t *testing.T) {
origLogin := Login
defer func() {
Login = origLogin
}()
Login = func(username, password string) bool {
return false
}
if Reply("a", "a", "aa") {
t.Errorf("reply true for login fail")
}
}
~~~
這里可以發現,我們是修改了`Login`這個函數的代碼,從而控制`Login`函數的返回值,這樣我們就可以測試我們寫的代碼的邏輯是否正確了
- 基礎
- 簡介
- 主要特征
- 變量和常量
- 編碼轉換
- 數組
- byte與rune
- big
- sort接口
- 和mysql類型對應
- 函數
- 閉包
- 工作區
- 復合類型
- 指針
- 切片
- map
- 結構體
- sync.Map
- 隨機數
- 面向對象
- 匿名組合
- 方法
- 接口
- 權限
- 類型查詢
- 異常處理
- error
- panic
- recover
- 自定義錯誤
- 字符串處理
- 正則表達式
- json
- 文件操作
- os
- 文件讀寫
- 目錄
- bufio
- ioutil
- gob
- 棧幀的內存布局
- shell
- 時間處理
- time詳情
- time使用
- new和make的區別
- container
- list
- heap
- ring
- 測試
- 單元測試
- Mock依賴
- delve
- 命令
- TestMain
- path和filepath包
- log日志
- 反射
- 詳解
- plugin包
- 信號
- goto
- 協程
- 簡介
- 創建
- 協程退出
- runtime
- channel
- select
- 死鎖
- 互斥鎖
- 讀寫鎖
- 條件變量
- 嵌套
- 計算單個協程占用內存
- 執行規則
- 原子操作
- WaitGroup
- 定時器
- 對象池
- sync.once
- 網絡編程
- 分層模型
- socket
- tcp
- udp
- 服務端
- 客戶端
- 并發服務器
- Http
- 簡介
- http服務器
- http客戶端
- 爬蟲
- 平滑重啟
- context
- httptest
- 優雅中止
- web服務平滑重啟
- beego
- 安裝
- 路由器
- orm
- 單表增刪改查
- 多級表
- orm使用
- 高級查詢
- 關系查詢
- SQL查詢
- 元數據二次定義
- 控制器
- 參數解析
- 過濾器
- 數據輸出
- 表單數據驗證
- 錯誤處理
- 日志
- 模塊
- cache
- task
- 調試模塊
- config
- 部署
- 一些包
- gjson
- goredis
- collection
- sjson
- redigo
- aliyunoss
- 密碼
- 對稱加密
- 非對稱加密
- 單向散列函數
- 消息認證
- 數字簽名
- mysql優化
- 常見錯誤
- go run的錯誤
- 新手常見錯誤
- 中級錯誤
- 高級錯誤
- 常用工具
- 協程-泄露
- go env
- gometalinter代碼檢查
- go build
- go clean
- go test
- 包管理器
- go mod
- gopm
- go fmt
- pprof
- 提高編譯
- go get
- 代理
- 其他的知識
- go內存對齊
- 細節總結
- nginx路由匹配
- 一些博客
- redis為什么快
- cpu高速緩存
- 常用命令
- Go 永久阻塞的方法
- 常用技巧
- 密碼加密解密
- for 循環迭代變量
- 備注
- 垃圾回收
- 協程和纖程
- tar-gz
- 紅包算法
- 解決golang.org/x 下載失敗
- 逃逸分析
- docker
- 鏡像
- 容器
- 數據卷
- 網絡管理
- 網絡模式
- dockerfile
- docker-composer
- 微服務
- protoBuf
- GRPC
- tls
- consul
- micro
- crontab
- shell調用
- gorhill/cronexpr
- raft
- go操作etcd
- mongodb