之前我们做过错误处理实例 现在我们来实际测试这个例子 先附上错误处理代码
自己写的库
package filelisting
import (
"net/http"
"os"
"io/ioutil"
"strings"
)
const prefix = "/list/"
type userHandle string //给接口使用的类型
func (u userHandle)Error()string{ //这个函数是给开发人员看的 实际也是Message只不过名字不同而已
return u.Message() //返回值就是Messa
}
func (u userHandle)Message()string{ //这个函数式给用户看的 返回的是调用者的错误信息 调用者保存的错误信息在下方例子中
return string(u)
}
func HandleFileList(writer http.ResponseWriter,request *http.Request) error { // 优化完后是在尾部添加返回值 进行统一错误管理
if strings.Index(request.URL.Path,prefix) != 0{ //检查用户是否使用/list/查找
return userHandle("panic this path not "+prefix) //接口的整个数据核心 将错误信息放入userHandle中
}
path := request.URL.Path[len(prefix):]
file, err := os.Open(path)
if err != nil{
return err
}
defer file.Close()
all, err :=ioutil.ReadAll(file)
if err != nil{
return err
}
writer.Write(all)
return nil
}
main函数
package main
import (
"net/http"
"awesomeProject1/test/filelistingserver/filelisting"
"os"
"awesomeProject1/golog"
"log"
)
type appHandle func(writer http.ResponseWriter,request *http.Request)error // 将函数定义为类型 并且函数参数,返回值与HandleFileList完全相同
//appHandle类型唯一作用是用来传输错误信息
func errWrapper(handler appHandle) func (http.ResponseWriter, *http.Request){ //此函数的作用是错误信息统一打印 并且返回正确函数
return func(writer http.ResponseWriter,request *http.Request){ //闭包
defer func(){ //截断http自带defer保护 自己设置想要的内容
if r:= recover(); r != nil{ //判断信息是否为空
log.Printf("Panic: %v",r) //打印给开发人员详细信息
http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)//打印给用户错误报告
}
}()
err := handler(writer, request) //这个函数的关键在于这行代码 如果有错误信息则进行下面代码反之返回正确函数
code := http.StatusOK
if err != nil{ //假设有错误信息 进行统一的分类 这段代码只有两个错误信息 实际情况可以自行添加
golog.Printf("Error handleing request: %s", err.Error()) //这是给开发人员看的错误信息比较详细
if usererr,ok := err.(userHandle); ok{ //判断接口是否有信息并赋值给变量usererr
http.Error(writer,usererr.Message(),http.StatusBadRequest) //将给用户看的错误信息及错误报告返回给用户
return
}
switch {
case os.IsNotExist(err):
code = http.StatusNotFound
default :
code = http.StatusInternalServerError
}
http.Error(writer, //这是给用户看的信息
http.StatusText(code),
code)
}
}
}
type userHandle interface{ // 定义接口及方法 实现在handler.go内 先看handler.go
error //自带包函数 借用
Message() string //用户专用返回错误信息
}
func main(){
http.HandleFunc("/",errWrapper(filelisting.HandleFileList))
err :=http.ListenAndServe(":8888",nil)
if err != nil{
panic(err)
}
}
下面我们直接贴上测试代码的实例 每行代码的含有我都有备注
package main
import (
"net/http"
"awesomeProject1/test/filelistingserver/filelisting"
"os"
"awesomeProject1/golog"
"log"
)
type appHandle func(writer http.ResponseWriter,request *http.Request)error // 将函数定义为类型 并且函数参数,返回值与HandleFileList完全相同
//appHandle类型唯一作用是用来传输错误信息
func errWrapper(handler appHandle) func (http.ResponseWriter, *http.Request){ //此函数的作用是错误信息统一打印 并且返回正确函数
return func(writer http.ResponseWriter,request *http.Request){ //闭包
defer func(){ //截断http自带defer保护 自己设置想要的内容
if r:= recover(); r != nil{ //判断信息是否为空
log.Printf("Panic: %v",r) //打印给开发人员详细信息
http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)//打印给用户错误报告
}
}()
err := handler(writer, request) //这个函数的关键在于这行代码 如果有错误信息则进行下面代码反之返回正确函数
code := http.StatusOK
if err != nil{ //假设有错误信息 进行统一的分类 这段代码只有两个错误信息 实际情况可以自行添加
golog.Printf("Error handleing request: %s", err.Error()) //这是给开发人员看的错误信息比较详细
if usererr,ok := err.(userHandle); ok{ //判断接口是否有信息并赋值给变量usererr
http.Error(writer,usererr.Message(),http.StatusBadRequest) //将给用户看的错误信息及错误报告返回给用户
return
}
switch {
case os.IsNotExist(err):
code = http.StatusNotFound
case os.IsPermission(err):
code = http.StatusForbidden
default :
code = http.StatusInternalServerError
}
http.Error(writer, //这是给用户看的信息
http.StatusText(code),
code)
}
}
}
type userHandle interface{ // 定义接口及方法 实现在handler.go内 先看handler.go
error //自带包函数 借用
Message() string //用户专用返回错误信息
}
func main(){
http.HandleFunc("/",errWrapper(filelisting.HandleFileList))
err :=http.ListenAndServe(":8888",nil)
if err != nil{
panic(err)
}
}
这个例子虽然简单 但是把实际开发时的处理方式都表现出来了
下面我们查看测试覆盖率
我们可以看到并没有完全覆盖 我们把剩下的代码全都覆盖
package main
import (
"testing"
"net/http"
"net/http/httptest"
"io/ioutil"
"fmt"
"strings"
"os"
"errors"
)
type testUserHandle string
func (u testUserHandle)Error()string{
return u.Message()
}
func (u testUserHandle)Message()string{
return string(u)
}
func errPain(writer http.ResponseWriter,request *http.Request)error{ //测试专用函数 与web.go中的errWrapper结合使用
panic(123) //返回错误 由web.go的deferfunc()进行错误处理
}
func errUserError(writer http.ResponseWriter,request *http.Request)error {
return testUserHandle("user error")
}
func errNotFound(writer http.ResponseWriter,request *http.Request)error {
return os.ErrNotExist
}
func errNoPermission(writer http.ResponseWriter,request *http.Request)error {
return os.ErrPermission
}
func errUnknown(writer http.ResponseWriter,request *http.Request)error {
return errors.New("unknown error")
}
func TestErrwrapper(t *testing.T){ //测试函数结构
tests:= []struct{ //初始化结构体类型
h appHandle //作用是给web.go的errWrapper的Handele传参 每个h由自己定义 传参测试 如errPain函数整体传过去
code int //预想错误代码
Message string //预想错误报告
}{
{errPain,500,"Internal Server Error"},// 初始化结构体内容 注意 errPain就是连接web.go的关键
{errUserError,400,"user error"},
{errNotFound,404,"Not Found"},
{errNoPermission,403,"Forbidden"},
{errUnknown,500,"Internal Server Error"},
}
for _,tt := range tests{ //循环得出结构体中的每一条 这里只有一条
f := errWrapper(tt.h) //将函数传值到errWrapper 并且当做类型赋值给f变量
recorder :=httptest.NewRecorder() //获得测试数据
request :=httptest.NewRequest(http.MethodGet,"http://www.imooc.com",nil)//获得errWrapper需要的参数
f(recorder,request) //将测试数据传入f变量中 由deferfunc()一旦panic 返回到下面代码
b,_ := ioutil.ReadAll(recorder.Body) //将测试数据转为类型
body := strings.Trim(string(b),"\n") //将类型强制转换为string并把结尾的\0去掉
if recorder.Code != tt.code || body != tt.Message { //判断初始化值与测试值是否相同 不同打印错误报告
fmt.Printf("error (%d, %s); got (%d, %s)", tt.code,tt.Message,recorder.Code,body)
fmt.Println()
}
}
}
下面是覆盖率图 貌似除了main函数之外其他都覆盖到了
但其实细心的同学已经发现了 我们只是测试了错误发生的代码 并没有测试错误信息为空
那接下来我们把错误为空测试了
package main
import (
"testing"
"net/http"
"net/http/httptest"
"io/ioutil"
"fmt"
"strings"
"os"
"errors"
)
type testUserHandle string
func (u testUserHandle)Error()string{
return u.Message()
}
func (u testUserHandle)Message()string{
return string(u)
}
func errPain(writer http.ResponseWriter,request *http.Request)error{ //测试专用函数 与web.go中的errWrapper结合使用
panic(123) //返回错误 由web.go的deferfunc()进行错误处理
}
func errUserError(writer http.ResponseWriter,request *http.Request)error {
return testUserHandle("user error")
}
func errNotFound(writer http.ResponseWriter,request *http.Request)error {
return os.ErrNotExist
}
func errNoPermission(writer http.ResponseWriter,request *http.Request)error {
return os.ErrPermission
}
func errUnknown(writer http.ResponseWriter,request *http.Request)error {
return errors.New("unknown error")
}
func noError(writer http.ResponseWriter,request *http.Request)error {
fmt.Fprintf(writer,"no error")
return nil
}
func TestErrwrapper(t *testing.T){ //测试函数结构
tests:= []struct{ //初始化结构体类型
h appHandle //作用是给web.go的errWrapper的Handele传参 每个h由自己定义 传参测试 如errPain函数整体传过去
code int //预想错误代码
Message string //预想错误报告
}{
{errPain,500,"Internal Server Error"},// 初始化结构体内容 注意 errPain就是连接web.go的关键
{errUserError,400,"user error"},
{errNotFound,404,"Not Found"},
{errNoPermission,403,"Forbidden"},
{errUnknown,500,"Internal Server Error"},
{noError,200,"no error"},
}
for _,tt := range tests{ //循环得出结构体中的每一条 这里只有一条
f := errWrapper(tt.h) //将函数传值到errWrapper 并且当做类型赋值给f变量
recorder :=httptest.NewRecorder() //获得测试数据
request :=httptest.NewRequest(http.MethodGet,"http://www.imooc.com",nil)//获得errWrapper需要的参数
f(recorder,request) //将测试数据传入f变量中 由deferfunc()一旦panic 返回到下面代码
b,_ := ioutil.ReadAll(recorder.Body) //将测试数据转为类型
body := strings.Trim(string(b),"\n") //将类型强制转换为string并把结尾的\0去掉
if recorder.Code != tt.code || body != tt.Message { //判断初始化值与测试值是否相同 不同打印错误报告
fmt.Printf("error (%d, %s); got (%d, %s)", tt.code,tt.Message,recorder.Code,body)
fmt.Println()
}
}
}
测试为空的代码已经被我/分隔出来了
我们继续往下看 我们测试使用的对比数据其实是我们捏造出来的 并不是实际发生时的情况
我们还需要测试出实际打开时的情况
下面是实际启动http进行错误测试
package main
import (
"testing"
"net/http"
"net/http/httptest"
"io/ioutil"
"fmt"
"strings"
"os"
"errors"
)
type testUserHandle string
func (u testUserHandle)Error()string{
return u.Message()
}
func (u testUserHandle)Message()string{
return string(u)
}
func errPain(writer http.ResponseWriter,request *http.Request)error{ //测试专用函数 与web.go中的errWrapper结合使用
panic(123) //返回错误 由web.go的deferfunc()进行错误处理
}
func errUserError(writer http.ResponseWriter,request *http.Request)error {
return testUserHandle("user error")
}
func errNotFound(writer http.ResponseWriter,request *http.Request)error {
return os.ErrNotExist
}
func errNoPermission(writer http.ResponseWriter,request *http.Request)error {
return os.ErrPermission
}
func errUnknown(writer http.ResponseWriter,request *http.Request)error {
return errors.New("unknown error")
}
func noError(writer http.ResponseWriter,request *http.Request)error {
fmt.Fprintf(writer,"no error")
return nil
}
var tests= []struct{ //初始化结构体类型
h appHandle //作用是给web.go的errWrapper的Handele传参 每个h由自己定义 传参测试 如errPain函数整体传过去
code int //预想错误代码
Message string //预想错误报告
}{
{errPain,500,"Internal Server Error"},// 初始化结构体内容 注意 errPain就是连接web.go的关键
{errUserError,400,"user error"},
{errNotFound,404,"Not Found"},
{errNoPermission,403,"Forbidden"},
{errUnknown,500,"Internal Server Error"},
{noError,200,"no error"},
}
func TestErrwrapper(t *testing.T){ //测试函数结构
for _,tt := range tests{ //循环得出结构体中的每一条 这里只有一条
f := errWrapper(tt.h) //将函数传值到errWrapper 并且当做类型赋值给f变量
recorder :=httptest.NewRecorder() //获得测试数据
request :=httptest.NewRequest(http.MethodGet,"http://www.imooc.com",nil)//获得errWrapper需要的参数
f(recorder,request) //将测试数据传入f变量中 由deferfunc()一旦panic 返回到下面代码
verifyResponse(recorder.Result(),tt.code,tt.Message,t) //将recorder转换后放入
}
}
func TestErrWrapperInServer(t *testing.T){ //没加注释代表与上方相同
for _, tt := range tests{
f := errWrapper(tt.h)
server := httptest.NewServer(http.HandlerFunc(f)) //将f变量代理的错误函数放入Server中并返回真实值Sever类型
resp ,_:= http.Get(server.URL) //将server转换为Response类型
verifyResponse(resp,tt.code,tt.Message,t) //传入共用函数 resp同上方recorder作用相同
}
}
func verifyResponse(resp *http.Response, expectedCode int, expectedMsg string, t *testing.T){
b,_ := ioutil.ReadAll(resp.Body) //将测试数据转为类型
body := strings.Trim(string(b),"\n") //将类型强制转换为string并把结尾的\0去掉
if resp.StatusCode != expectedCode || body != expectedMsg { //判断初始化值与测试值是否相同 不同打印错误报告
fmt.Printf("error (%d, %s); got (%d, %s)", expectedCode,expectedMsg,resp.StatusCode,body)
fmt.Println()
}
}
通过实际调试后 两段代码返回值相同
为什么同样的返回值要用两段代码表示呢
因为 TestErrwrapper(t *testing.T)是一种假象代码在实际运行中会发生的错误 运行的原理比较简单 时间复杂度较低
而 TestErrWrapperInServer(t *testing.T)使用的是真实http服务器进行测试 运行原理比较复杂 运行时间长