beego使用(4)-其它说明

前面已经说明beego使用的主要部分,另外beego还提供了一些其他内容,比如日志,接口文档,测试,以及前端的一些设置等,这里罗列出之前没有说明的一些,如果有其它的再补充说明。

日志

日志设置可以在main.go文件中加入,在init函数中调用。需要引入模块github.com/astaxie/beego/logs。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func initLog() {
runmode := beego.AppConfig.String("runmode")
logs.Reset()
if runmode != "prod" {
logs.SetLogger(logs.AdapterFile, fmt.Sprintf(`{"filename": "%s"}`, beego.AppConfig.String("LogFile")))
logs.EnableFuncCallDepth(true)
logs.SetLogFuncCallDepth(4)
logs.SetLevel(logs.LevelDebug)
} else {
logs.SetLogger(logs.AdapterFile,
fmt.Sprintf(`{"filename": "%s"}`, beego.AppConfig.String("LogFile")))
l, err := beego.AppConfig.Int("LogLevel")
if err != nil {
panic("log level not found")
}
logs.EnableFuncCallDepth(true)
logs.SetLogFuncCallDepth(4)
logs.SetLevel(l)
}
}

在日志设置中,首先是从conf文件中获取当前的运行模式,一般会有开发、上线、测试等。然后将log重置。接着是设置输出引擎,主要有AdapterConsole(输出到控制台)、AdapterFile(输出到文件)等,另外其它引擎和参数参考使用手册即可。
然后需要设置日志级别。级别越低会打印更多信息,一般情况有错误、警告、普通信息、debug调试信息等。不同的信息在beego中对应不同的输出函数,比如:

1
2
3
4
5
6
7
8
beego.Emergency("this is emergency")
beego.Alert("this is alert")
beego.Critical("this is critical")
beego.Error("this is error")
beego.Warning("this is warning")
beego.Notice("this is notice")
beego.Informational("this is informational")
beego.Debug("this is debug")

接下来是设置日志是否输出文件名和行号,默认是不输出。这种输出可以设置输出深度,因为存在调用,所以通过设置输出深度来输出调用的信息。

API文档

beego提供了可以生成api文档的方式。beego通过注解路由的方式生成swagger标准的json格式,从而能通过swagger应用的方式展示给用户。
生成api文档的方式是在运行的时候加上参数

1
bee run -downdoc=true -docgen=true

其访问页面如下:

这里可以看到有一些说明,这个在router.go顶部进行注释:

1
2
3
4
// @APIVersion 1.0.0
// @Title test api
// @Description test api
// @Contact xxx@email.com

其访问地址默认是swagger为URI的,这里改成了doc,在router.go的init中修改:

1
beego.BConfig.WebConfig.StaticDir["/doc"] = "swagger"

swagger页面根据api中的不同router分类,可以看到project下面有CURD操作。

对于每一个操作下面都可以展开看到参数和说明,也可以直接通过页面访问接口。

测试用例

对于代码需要自己做单元测试。测试对于beego来说主要是api接口提供的Controller层,以及逻辑处理和orm的model层。对于model层直接import对应的模块然后调用函数测试返回值即可,这里说明一下api的测试。测试也需要有一个base.go文件,该文件主要用于提供测试需要用的一些基础操作,比如连接数据库,每次清空数据库,添加一些依赖数据的函数,判断结果的函数,以及模拟请求等。
init函数中连接数据库

1
2
3
4
5
6
func init() {
_, file, _, _ := runtime.Caller(1)
apppath, _ := filepath.Abs(filepath.Dir(filepath.Join(file, ".."+string(filepath.Separator))))
orm.RegisterDataBase("default", "mysql", "root@tcp(127.0.0.1:3306)/test_db?charset=utf8&loc=Asia%2FShanghai", 30)
beego.TestBeegoInit(apppath)
}

其中显示获取当前文件路径,然后定位到app路径,然后启动测试。
在每个函数测试之前应该清空数据库,所以设置一个清空数据库的函数,在每个测试用例中写入。

1
2
3
func setUp() {                 
orm.RunSyncdb("default", true, false) //rebuild all tables
}

发送请求的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func sendRequest(t *testing.T, method, url string, params *Params) (rsp *controllers.APIRsp) {
var rd io.Reader
if params != nil {
p := params.Encode()
if method == "GET" {
url = fmt.Sprintf("%s?%s", url, p)
} else {
rd = strings.NewReader(params.Encode())
}
}
r, err := http.NewRequest(method, url, rd)
if err != nil {
t.Fatalf("gen request err|%v", err)
}
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
w := httptest.NewRecorder()
beego.BeeApp.Handlers.ServeHTTP(w, r)
assertEqual(t, w.Code, 200, "statu code err|%d", w.Code)
rsp = new(controllers.APIRsp)
bs := w.Body.Bytes()
err = json.Unmarshal(bs, rsp)
if err != nil {
t.Fatalf("unmarshal rsp|%v", err)
}
return
}

由于有些请求比如查询需要有数据,这时应该先提供数据插入的函数。

1
2
3
4
5
func addEntity(v interface{}) (id int64, err error) {
o := orm.NewOrm()
id, err = o.Insert(v)
return
}

对于响应内容,我们需要判断是否正确,一般是要给出等于或者不等于的判断。

1
2
3
4
5
6
7
8
9
10
func assertEqual(t *testing.T, a, b interface{}, msg string, intf ...interface{}) {
if !reflect.DeepEqual(a, b) {
t.Fatalf(msg, intf...)
}
}
func assertNotEqual(t *testing.T, a, b interface{}, msg string, intf ...interface{}) {
if reflect.DeepEqual(a, b) {
t.Fatalf(msg, intf...)
}
}

下面是具体的测试用例,以上节的TestUser为例,用例需要以Test开头。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func TestUser_Query_By_Name(t *testing.T) {
setUp()
models.AddTestUser(&models.TestUser{Name: "x1"})
models.AddTestUser(&models.TestUser{Name: "x2"})

params := Params{"name": "x"}
rsp := sendRequest(t, "GET", "/api/testuser/query", &params)
assertEqual(t, rsp.Code, 0, "code err|%v", rsp.Msg)

data := rsp.Data.(map[string]interface{})
assertEqual(t, data["total"], float64(2), "total err|%v", data["total"])
items := data["items"].([]interface{})
names := []string{}
for _, item := range items {
i := item.(map[string]interface{})
names = append(names, i["name"].(string))
}
assertEqual(t, names, []string{"x1", "x2"}, "items err|%v", names)
}

另外对于有第三方调用的http请求使用mock的方式模拟数据。

1
2
3
4
5
func registerTestAPIMock(method, url, rsp string) {
url = fmt.Sprintf("http://%s%s", beego.AppConfig.String("TESTAPI_HOST"), url)
httpmock.RegisterResponder(method, url,
httpmock.NewStringResponder(200, rsp))
}

测试用例的执行可以按函数执行,也可以按文件执行:
go test -v -test.run + 函数
go test -v 测试文件

前端设置

Controller中已经说明了view中存放html主页,以及在static中存放js、css等文件。这里说明可以通过设置开启静态文件的列表显示,在router.go中加入设置:

1
2
beego.BConfig.WebConfig.DirectoryIndex = true
beego.BConfig.WebConfig.StaticDir["/static"] = "static"

效果如下: