关于golang的测试实例

之前我们做过错误处理实例  现在我们来实际测试这个例子 先附上错误处理代码

自己写的库 

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服务器进行测试 运行原理比较复杂 运行时间长

猜你喜欢

转载自blog.csdn.net/weixin_42654444/article/details/82219785