网站建设域名空间,模拟网站建设软件有哪些,seo网络推广教程,类似抖音网站开发费用文章目录 一、构建 HTTP server1.1 model.go1.2 server.go1.3 curl 验证 server 功能1.3.1 新建1.3.2 查询1.3.3 更新1.3.4 删除 二、httptest 测试2.1 完整示例2.2 实现逻辑2.3 其他示例2.4 用 TestMain 避免重复的测试代码2.5 gin 框架的 httptest 一、构建 HTTP server
1.1… 文章目录 一、构建 HTTP server1.1 model.go1.2 server.go1.3 curl 验证 server 功能1.3.1 新建1.3.2 查询1.3.3 更新1.3.4 删除 二、httptest 测试2.1 完整示例2.2 实现逻辑2.3 其他示例2.4 用 TestMain 避免重复的测试代码2.5 gin 框架的 httptest 一、构建 HTTP server
1.1 model.go
package mainimport (errorstime
)var TopicCache make([]*Topic, 0, 16)type Topic struct {Id int json:idTitle string json:titleContent string json:contentCreatedAt time.Time json:created_at
}// 从数组中找到一项, 根据 id 找到数组的下标
func FindTopic(id int) (*Topic, error) {if err : checkIndex(id); err ! nil {return nil, err}return TopicCache[id-1], nil
}// 创建一个 Topic 实例, 没有输入参数, 内部根据 Topic 数组的长度来确定新 Topic 的 id
func (t *Topic) Create() error {// 初始时len 为 0, id 为 1, 即数组下标为0时并不放置元素, 而数组从下标为1才开始放置元素t.Id len(TopicCache) 1 // 忽略用户传入的 id, 而是根据数组的长度, 决定此项的 Idt.CreatedAt time.Now()TopicCache append(TopicCache, t) // 初始时数组为空, 放入的第一个元素是 Id 1return nil
}// 更新一个 Topic 实例, 通过 id 找到数组下标, 最终改的还是数组里的值
func (t *Topic) Update() error {if err : checkIndex(t.Id); err ! nil {return err}TopicCache[t.Id-1] treturn nil
}func (t *Topic) Delete() error {if err : checkIndex(t.Id); err ! nil {return err}TopicCache[t.Id-1] nilreturn nil
}func checkIndex(id int) error {if id 0 len(TopicCache) id-1 {return errors.New(The topic is not exists!)}return nil
}1.2 server.go
package mainimport (encoding/jsonnet/httppathstrconv
)func main() {http.HandleFunc(/topic/, handleRequest)http.ListenAndServe(:2017, nil)
}// main handler function
func handleRequest(w http.ResponseWriter, r *http.Request) {var err errorswitch r.Method {case http.MethodGet:err handleGet(w, r)case http.MethodPost:err handlePost(w, r)case http.MethodPut:err handlePut(w, r)case http.MethodDelete:err handleDelete(w, r)}if err ! nil {http.Error(w, err.Error(), http.StatusInternalServerError)return}
}// 获取一个帖子
// 如 GET /topic/1
func handleGet(w http.ResponseWriter, r *http.Request) error {// 用户输入的 url 中有 id, 通过 path.Base(r.URL.Path) 获取 idid, err : strconv.Atoi(path.Base(r.URL.Path))if err ! nil {return err}topic, err : FindTopic(id)if err ! nil {return err}// 序列化结果并输出output, err : json.MarshalIndent(topic, , \t\t)if err ! nil {return err}w.Header().Set(Content-Type, application/json)w.Write(output)return nil
}// 增加一个帖子
// POST /topic/
func handlePost(w http.ResponseWriter, r *http.Request) (err error) {// 构造长度为 r.ContentLength 的缓冲区body : make([]byte, r.ContentLength)// 读取到缓冲区r.Body.Read(body)// 反序列化到对象var topic new(Topic)err json.Unmarshal(body, topic)if err ! nil {return}// 执行操作err topic.Create()if err ! nil {return}w.WriteHeader(http.StatusOK)return
}// 更新一个帖子
// PUT /topic/1
func handlePut(w http.ResponseWriter, r *http.Request) error {id, err : strconv.Atoi(path.Base(r.URL.Path))if err ! nil {return err}topic, err : FindTopic(id)if err ! nil {return err}body : make([]byte, r.ContentLength)r.Body.Read(body)json.Unmarshal(body, topic)err topic.Update()if err ! nil {return err}w.WriteHeader(http.StatusOK)return nil
}// 删除一个帖子
// DELETE /topic/1
func handleDelete(w http.ResponseWriter, r *http.Request) (err error) {id, err : strconv.Atoi(path.Base(r.URL.Path))if err ! nil {return}topic, err : FindTopic(id)if err ! nil {return}err topic.Delete()if err ! nil {return}w.WriteHeader(http.StatusOK)return
}1.3 curl 验证 server 功能
1.3.1 新建
curl -i -X POST http://localhost:2017/topic/ -H content-type: application/json -d {title:a, content:b}HTTP/1.1 200 OK
Date: Mon, 11 Mar 2024 02:54:08 GMT
Content-Length: 01.3.2 查询
curl -i -X GET http://localhost:2017/topic/1HTTP/1.1 200 OK
Content-Type: application/json
Date: Mon, 11 Mar 2024 03:00:11 GMT
Content-Length: 99{id: 1,title: a,content: b,created_at: 2024-03-11T10:59:44.04302908:00
}1.3.3 更新
curl -i -X PUT http://localhost:2017/topic/1 -H content-type: application/json -d {title: c, content: d}HTTP/1.1 200 OK
Date: Mon, 11 Mar 2024 03:01:51 GMT
Content-Length: 0curl -i -X GET http://localhost:2017/topic/1 HTTP/1.1 200 OK
Content-Type: application/json
Date: Mon, 11 Mar 2024 03:01:54 GMT
Content-Length: 99{id: 1,title: c,content: d,created_at: 2024-03-11T10:59:44.04302908:00
}1.3.4 删除
curl -i -X DELETE http://localhost:2017/topic/1HTTP/1.1 200 OK
Date: Mon, 11 Mar 2024 03:03:41 GMT
Content-Length: 0curl -i -X GET http://localhost:2017/topic/1
HTTP/1.1 200 OK
Content-Type: application/json
Date: Mon, 11 Mar 2024 03:04:27 GMT
Content-Length: 4null二、httptest 测试
上文通过 curl 自测了 controller现在通过 net/http/httptest 测试这种测试方式其实是没有 HTTP 调用的是通过将 handler() 函数绑定到 url 上实现的。
2.1 完整示例
package mainimport (net/httpnet/http/httpteststringstesting
)func TestHandlePost(t *testing.T) {// mux 是多路复用器的意思mux : http.NewServeMux()mux.HandleFunc(/topic/, handleRequest) // 将 [业务的 handleRequest() 函数] 注册到 mux 的 /topic/ 路由上// 构造一个请求reader : strings.NewReader({title:e, content:f})r, _ : http.NewRequest(http.MethodPost, /topic/, reader)// 构造一个响应 (httptest.ResponseRecorder 实现了 http.ResponseWriter 接口)w : httptest.NewRecorder()mux.ServeHTTP(w, r)//handleRequest(w, r)// 获取响应结果resp : w.Result()if resp.StatusCode ! http.StatusOK {t.Errorf(Expected status OK; got %v, resp.Status)}
}2.2 实现逻辑
实现逻辑如下 首先配置路由将 /topic 的请求都路由给 handleRequest() 函数实现。
mux : http.NewServeMux()
mux.HandleFunc(/topic/, handleRequest)因为 handleRequest(w http.ResponseWriter, r *http.Request) 函数的签名是 w 和 r 两个参数所以为了测试需要构造这两个参数实例。
因为 httptest.ResponseRecorder 实现了 http.ResponseWriter 接口所以可以用 httptest.NewRecorder() 表示 w。
准备好之后就可以执行了
可以只调用 handleRequest(w, r)也可以调用 mux.ServeHTTP(w, r)其内部也会调用 handleRequest(w, r)这会更完整的测试整个流程。
最后通过 go test -v 可以执行测试。
$ go test -v RUN TestHandlePost
--- PASS: TestHandlePost (0.00s)
PASS
ok benchmarkdemo 0.095s2.3 其他示例
func TestHandleGet(t *testing.T) {mux : http.NewServeMux()mux.HandleFunc(/topic/, handleRequest)r, _ : http.NewRequest(http.MethodGet, /topic/1, nil)w : httptest.NewRecorder()mux.ServeHTTP(w, r)resp : w.Result()if resp.StatusCode ! http.StatusOK {t.Errorf(Expected status OK; got %v, resp.Status)}topic : new(Topic)json.Unmarshal(w.Body.Bytes(), topic)if topic.Id ! 1 {t.Errorf(cannot get topic by id)}
}注意因为数据没有落地存储为了保证后面的测试正常请将 TestHandlePost 放在最前面。
如果 go test -v 测试整个包的话TestHandlePost 和 TestHandleGet 两个单测都能成功但如果分开测试的话只有 TestHandlePost 能成功而 TestHandleGet 会失败因为没有 POST 创建流程而只有 GET 创建流程的话在业务逻辑的数组中找不到 id 1 的项就会报错
2.4 用 TestMain 避免重复的测试代码
细心的朋友应该会发现上面的测试代码有重复比如
mux : http.NewServeMux()
mux.HandleFunc(/topic/, handleRequest)以及
w : httptest.NewRecorder()这正好是前面学习的 setup 可以做的事情因此可以使用 TestMain 来做重构。实现如下
var w *httptest.ResponseRecorderfunc TestMain(m *testing.M) {w httptest.NewRecorder()os.Exit(m.Run())
}2.5 gin 框架的 httptest
package serviceimport (fmtlognet/httpnet/http/httpteststringstestinggithub.com/gin-gonic/gin
)type userINfo struct {ID uint64 json:idName string json:name
}func handler(c *gin.Context) {var info userINfoif err : c.ShouldBindJSON(info); err ! nil {log.Panic(err)}fmt.Println(info)c.Writer.Write([]byte({status: 200}))
}func TestHandler(t *testing.T) {rPath : /userrouter : gin.Default()router.GET(rPath, handler)req, _ : http.NewRequest(GET, rPath, strings.NewReader({id: 1,name: joe}))w : httptest.NewRecorder()router.ServeHTTP(w, req)t.Logf(status: %d, w.Code)t.Logf(response: %s, w.Body.String())
}