go每日一题:mock打桩、defer、recovery、panic的调用顺序
题目一:单元测试中使用—打桩
- 打桩概念:使用A替换 原函数B,那么A就是打桩函数
- 打桩原理:运行时,通过一个包,将内存中函数的地址替换为桩函数的地址
- 打桩操作:利用Patch()函数,将调用外部依赖等函数(数据库等依赖函数),替换为自己写的函数,这样就是实现了不对外部数据的一个强依赖
- 最简单的用法,官方解释:Monkey’s API is very simple and straightfoward. Call monkey.Patch(, ) to replace a function. For example:
package main
import (
"fmt"
"os"
"strings"
"bou.ke/monkey"
)
func main() {
monkey.Patch(fmt.Println, func(a ...interface{}) (n int, err error) {
s := make([]interface{}, len(a))
for i, v := range a {
s[i] = strings.Replace(fmt.Sprint(v), "hell", "*bleep*", -1)
}
return fmt.Fprintln(os.Stdout, s...)
})
fmt.Println("what the hell?") // what the *bleep*?
}
下面是对一个io文件的mock示例,文件为外部依赖,存在不稳定性质,因此使用mock
package test
import (
"bou.ke/monkey"
"bufio"
"github.com/stretchr/testify/assert"
"os"
"strings"
"testing"
)
func readFirstLine() string {
open, err := os.Open("file.txt")
defer open.Close()
if err != nil {
return ""
}
scanner := bufio.NewScanner(open)
for scanner.Scan() {
return scanner.Text()
}
return ""
}
func replaceLine() string {
line := readFirstLine()
afterReplace := strings.ReplaceAll(line, "11", "00")
return afterReplace
}
func TestReplace(t *testing.T) {
monkey.Patch(readFirstLine, func() string { //注意是readFirstLine函数名,而不是readFirstLine()
return "line11"
})
defer monkey.Unpatch(readFirstLine) // 注意是readFirstLine函数名,而不是readFirstLine()
res := replaceLine()
assert.Equal(t, "line00", res)
}
题二:defer、recovery、panic执行顺序
- 准则:panic当前函数的下一行代码不会被执行到
- 当 panic 被触发后,Go 语言的运行时机制会开始查找当前函数以及调用栈上的所有 defer 函数,然后依次执行它们。
- 在这个过程中,如果某个 defer 函数中使用了 recover 函数,那么 recover 就可以捕获到之前触发的 panic 异常,恢复程序的正常执行流程从引发 panic 的函数返回,继续执行后续代码,注意是直接在panic那里return,panic当前函数的下面的代码依然不会被执行,但是其他后续代码可以执行
- 并且 recover 会返回 panic 时传递的参数(在这里就是 “触发一个异常” 这个字符串),可以在 defer 函数中根据返回值进行相应的处理,比如打印异常信息等。
package main
import "fmt"
func inner() {
defer func() {
if r := recover(); r!= nil {
fmt.Printf("inner函数中的defer通过recover捕获到异常,异常信息: %v\n", r)
}
}()
panic("inner函数中触发异常")
defer fmt.Println("这一行代码直接不会被执行")
}
func outer() {
defer func() {
fmt.Println("outer函数中的defer执行了")
}()
inner()
}
func main() {
fmt.Println("程序开始执行")
outer() //其中发生了panic,如果没有recovery,下面的代码将不再执行
fmt.Println("outer函数调用结束后,继续执行main函数后面的代码")
}