Golang 网络请求单元测试
参考
Building and Testing a REST API in Go with Gorilla Mux and PostgreSQL
https://semaphoreci.com/community/tutorials/building-and-testing-a-rest-api-in-go-with-gorilla-mux-and-postgresql
https://blog.csdn.net/u011957758/article/details/81267972
代码结构
app.go
package app
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
m "go_gorilla/model"
"github.com/gorilla/mux"
_ "github.com/go-sql-driver/mysql"
)
type App struct {
Router *mux.Router
DB *sql.DB
}
func (a *App) Initialize(user, password, host, dbname string) {
connectionString := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local", user, password, host, dbname)
var err error
a.DB, err = sql.Open("mysql", connectionString)
if err != nil {
log.Fatal(err)
}
a.Router = mux.NewRouter()
a.initializeRoutes()
}
func (a *App) Run(addr string) {
log.Fatal(http.ListenAndServe(addr, a.Router))
}
func (a *App) initializeRoutes() {
a.Router.HandleFunc("/products", a.getProducts).Methods("GET")
a.Router.HandleFunc("/product", a.createProduct).Methods("POST")
a.Router.HandleFunc("/product/{id:[0-9]+}", a.getProduct).Methods("GET")
a.Router.HandleFunc("/product/{id:[0-9]+}", a.updateProduct).Methods("PUT")
a.Router.HandleFunc("/product/{id:[0-9]+}", a.deleteProduct).Methods("DELETE")
}
func (a *App) getProducts(w http.ResponseWriter, r *http.Request) {
count, _ := strconv.Atoi(r.FormValue("count"))
start, _ := strconv.Atoi(r.FormValue("start"))
if count > 10 || count < 1 {
count = 10
}
if start < 0 {
start = 0
}
products, err := m.GetProducts(a.DB, start, count)
if err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
respondWithJSON(w, http.StatusOK, products)
}
func (a *App) createProduct(w http.ResponseWriter, r *http.Request) {
var p m.Product
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&p); err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid request payload")
return
}
defer r.Body.Close()
if err := p.CreateProduct(a.DB); err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
respondWithJSON(w, http.StatusCreated, p)
}
func (a *App) getProduct(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid product ID")
return
}
p := m.Product{ ID: id}
if err := p.GetProduct(a.DB); err != nil {
switch err {
case sql.ErrNoRows:
respondWithError(w, http.StatusNotFound, "Product not found")
default:
respondWithError(w, http.StatusInternalServerError, err.Error())
}
return
}
respondWithJSON(w, http.StatusOK, p)
}
func (a *App) updateProduct(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid product ID")
return
}
var p m.Product
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&p); err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid resquest payload")
return
}
defer r.Body.Close()
p.ID = id
if err := p.UpdateProduct(a.DB); err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
respondWithJSON(w, http.StatusOK, p)
}
func (a *App) deleteProduct(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid Product ID")
return
}
p := m.Product{ ID: id}
if err := p.DeleteProduct(a.DB); err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
respondWithJSON(w, http.StatusOK, map[string]string{"result": "success"})
}
func respondWithError(w http.ResponseWriter, code int, message string) {
respondWithJSON(w, code, map[string]string{"error": message})
}
func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
response, _ := json.Marshal(payload)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
w.Write(response)
}
model.go
package model
import (
"database/sql"
)
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
}
func (p *Product) GetProduct(db *sql.DB) error {
return db.QueryRow("SELECT name, price FROM products WHERE id=?", p.ID).Scan(&p.Name, &p.Price)
}
func (p *Product) UpdateProduct(db *sql.DB) error {
_, err := db.Exec("UPDATE products SET name=?, price=? WHERE id=?", p.Name, p.Price, p.ID)
return err
}
func (p *Product) DeleteProduct(db *sql.DB) error {
_, err := db.Exec("DELETE FROM products WHERE id=?", p.ID)
return err
}
func (p *Product) CreateProduct(db *sql.DB) error {
_, err := db.Exec("INSERT INTO products(name, price) VALUES(?, ?)", p.Name, p.Price)
return err
}
func GetProducts(db *sql.DB, start, count int) ([]Product, error) {
rows, err := db.Query("SELECT id, name, price FROM products LIMIT ? OFFSET ?", count, start)
if err != nil {
return nil, err
}
defer rows.Close()
products := []Product{}
for rows.Next() {
var p Product
if err := rows.Scan(&p.ID, &p.Name, &p.Price); err != nil {
return nil, err
}
products = append(products, p)
}
return products, nil
}
main.go
package main
import "go_gorilla/app"
const (
db_username = ""
db_password = ""
host_name = ""
db_name = ""
port = ""
)
func main() {
a := app.App{}
a.Initialize(db_username, db_password, host_name, db_name)
a.Run(port)
}
main_test.go
package main_test
import (
"bytes"
"encoding/json"
"fmt"
"go_gorilla/app"
"log"
"net/http"
"net/http/httptest"
"os"
"strconv"
"testing"
)
const tableCreationQuery = `CREATE TABLE IF NOT EXISTS products
(
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price DECIMAL(10,2) NOT NULL DEFAULT 0.00
)`
const (
db_username = ""
db_password = ""
host_name = ""
db_name = ""
)
var a app.App
func TestMain(m *testing.M) {
a = app.App{}
a.Initialize(db_username, db_password, host_name, db_name)
ensureTableExists()
code := m.Run()
clearTable()
os.Exit(code)
}
func ensureTableExists() {
if _, err := a.DB.Exec(tableCreationQuery); err != nil {
log.Fatal(err)
}
}
func clearTable() {
_, err := a.DB.Exec("TRUNCATE products")
if err != nil {
fmt.Println(err.Error())
}
}
func addProducts(count int) {
if count < 1 {
count = 1
}
for i := 0; i < count; i++ {
a.DB.Exec("INSERT INTO products(name, price) VALUES(?, ?)", "Product "+strconv.Itoa(i), (i+1.0)*10)
}
}
func TestUpdateProduct(t *testing.T) {
clearTable()
addProducts(1)
req, _ := http.NewRequest("GET", "/product/1", nil)
response := executeRequest(req)
var originalProduct map[string]interface{}
json.Unmarshal(response.Body.Bytes(), &originalProduct)
data := make(map[string]interface{})
data["name"] = "test product - updated name"
data["price"] = 11.22
jsonObj, _ := json.Marshal(data)
payload := []byte(string(jsonObj))
req, _ = http.NewRequest("PUT", "/product/1", bytes.NewBuffer(payload))
response = executeRequest(req)
checkResponseCode(t, http.StatusOK, response.Code)
var m map[string]interface{}
json.Unmarshal(response.Body.Bytes(), &m)
if m["id"] != originalProduct["id"] {
t.Errorf("Expected the id to remain the same (%v). Got %v", originalProduct["id"], m["id"])
}
if m["name"] == originalProduct["name"] {
t.Errorf("Expected the name to change from '%v' to '%v'. Got '%v'", originalProduct["name"], m["name"], m["name"])
}
if m["price"] == originalProduct["price"] {
t.Errorf("Expected the price to change from '%v' to '%v'. Got '%v'", originalProduct["price"], m["price"], m["price"])
}
}
func TestDeleteProduct(t *testing.T) {
clearTable()
addProducts(1)
req, _ := http.NewRequest("GET", "/product/1", nil)
response := executeRequest(req)
checkResponseCode(t, http.StatusOK, response.Code)
req, _ = http.NewRequest("DELETE", "/product/1", nil)
response = executeRequest(req)
checkResponseCode(t, http.StatusOK, response.Code)
req, _ = http.NewRequest("GET", "/product/1", nil)
response = executeRequest(req)
checkResponseCode(t, http.StatusNotFound, response.Code)
}
func TestGetProduct(t *testing.T) {
clearTable()
addProducts(1)
req, _ := http.NewRequest("GET", "/product/1", nil)
response := executeRequest(req)
checkResponseCode(t, http.StatusOK, response.Code)
}
func TestEmptyTable(t *testing.T) {
clearTable()
req, _ := http.NewRequest("GET", "/products", nil)
response := executeRequest(req)
checkResponseCode(t, http.StatusOK, response.Code)
if body := response.Body.String(); body != "[]" {
t.Errorf("Expected an empty array. Got %s", body)
}
}
func checkResponseCode(t *testing.T, expected, actual int) {
if expected != actual {
t.Errorf("Expected response code %d. Got %d\n", expected, actual)
}
}
func executeRequest(req *http.Request) *httptest.ResponseRecorder {
rr := httptest.NewRecorder()
a.Router.ServeHTTP(rr, req)
return rr
}
xx:go_gorilla ad$ go test -v
=== RUN TestUpdateProduct
--- PASS: TestUpdateProduct (0.38s)
=== RUN TestDeleteProduct
--- PASS: TestDeleteProduct (0.47s)
=== RUN TestGetProduct
--- PASS: TestGetProduct (0.31s)
=== RUN TestEmptyTable
--- PASS: TestEmptyTable (0.22s)
PASS
ok go_gorilla 1.754s
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下