在Go中,用于POST请求的单元测试不起作用。

huangapple go评论76阅读模式
英文:

Unit test for post request is not working in Go

问题

我在Go中编写了一个简单的POST请求处理程序,它运行良好,但是当我运行单元测试时,它会抛出错误。

与数据库的连接:

var err error
server.DBConn, err = sql.Open("mysql", "root:root@tcp(localhost:8889)/infomatrix_project")
defer server.DBConn.Close()
http.HandleFunc("/reg_startup", post.RegStartup)
http.ListenAndServe(":9000", nil)

处理程序函数:

type Startup struct {
	Name              string `json:"name"`
	Login             string `json:"login"`
	Password          string `json:"password"`
	Email             string `json:"email"`
	Description       string `json:"description"`
	Logo              string `json:"logo"`
	LowestInvestment  int    `json:"lowestInvestment"`
	HighestInvestment int    `json:"highestInvestment"`
	Region            string `json:"region"`
	WebSite           string `json:"website"`
	Industry          string `json:"industry"`
}

func RegStartup(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	if r.Method != "POST" {
		fmt.Fprintf(w, "error: the request is not a POST type")
		return
	}
	//other.AccessSetter(w)
	var query Startup
	err := json.NewDecoder(r.Body).Decode(&query)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	stmt, err := server.DBConn.Prepare("INSERT INTO startups (name, login, password, email, " +
		"description, logo, lowest_investment, highest_investment, region, website, industry) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
	defer stmt.Close()
	if err != nil {
		log.Fatal(err)
		return
	}
	_, err = stmt.Exec(query.Name, query.Login, query.Password, query.Email, query.Description, query.Logo,
		query.LowestInvestment, query.HighestInvestment, query.Region, query.WebSite, query.Industry)
	if err != nil {
		log.Fatal(err)
		return
	}
	fmt.Fprintf(w, "data entered successfully")
}

测试函数:

func TestRegStartup(t *testing.T) {
	load := Startup{"название на кириллице", "test_startup", "test_password", "test@example.com", "This is a test startup.",
		"https://example.com/test_startup_logo.jpg", 1000, 10000, "Kazakhstan", "https://teststartup.com", "IT"}
	loadBytes, _ := json.Marshal(load)
	request, err := http.NewRequest("POST", "/reg_startup", bytes.NewBuffer(loadBytes))
	if err != nil {
		t.Fatal(err)
	}
	rr := httptest.NewRecorder()
	handler := http.HandlerFunc(postRequests.RegStartup)
	handler.ServeHTTP(rr, request)
	status := rr.Code
	if status != http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
		return
	}

	expected := `"data entered successfully"`
	if rr.Body.String() != expected {
		t.Errorf("handler returned unexpected body: got %v want %v",
			rr.Body.String(), expected)
		return
	}
}

错误信息:

--- FAIL: TestRegStartup (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
        panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x102534408]

goroutine 18 [running]:
testing.tRunner.func1.2({0x1025bfa40, 0x102723f80})
        /usr/local/go/src/testing/testing.go:1526 +0x1c8
testing.tRunner.func1()
        /usr/local/go/src/testing/testing.go:1529 +0x384
panic({0x1025bfa40, 0x102723f80})
        /usr/local/go/src/runtime/panic.go:884 +0x204
database/sql.(*DB).conn(0x1025a4c9c?, {0x1025f75b8?, 0x1400009c018?}, 0xe0?)
        /usr/local/go/src/database/sql/sql.go:1282 +0x28
database/sql.(*DB).prepare(0x140000e8190?, {0x1025f75b8, 0x1400009c018}, {0x102549c1b, 0xb1}, 0x0?)
        /usr/local/go/src/database/sql/sql.go:1587 +0x34
database/sql.(*DB).PrepareContext.func1(0xa0?)
        /usr/local/go/src/database/sql/sql.go:1561 +0x48
database/sql.(*DB).retry(0x140000d5c28?, 0x140000d5be8)
        /usr/local/go/src/database/sql/sql.go:1538 +0x4c
database/sql.(*DB).PrepareContext(0x140000e8168?, {0x1025f75b8?, 0x1400009c018?}, {0x102549c1b?, 0x140000d5c01?})
        /usr/local/go/src/database/sql/sql.go:1560 +0x74
database/sql.(*DB).Prepare(0x140000e8140?, {0x102549c1b?, 0x140000baaa0?})
        /usr/local/go/src/database/sql/sql.go:1577 +0x40
github.com/SayatAbdikul/rest_api_for_startup/postRequests.RegStartup({0x1025f73a0, 0x140000a8ac0}, 0x14000104000)
        /Users/sayat/Documents/GitHub/rest_api_for_startup/postRequests/regStartup.go:38 +0x1d4
net/http.HandlerFunc.ServeHTTP(...)
        /usr/local/go/src/net/http/server.go:2122
github.com/SayatAbdikul/rest_api_for_startup/package_test_test.TestRegStartup(0x14000082d00)
        /Users/sayat/Documents/GitHub/rest_api_for_startup/package_test/regStartup_test.go:37 +0x1c0
testing.tRunner(0x14000082d00, 0x1025f4db8)
        /usr/local/go/src/testing/testing.go:1576 +0x10c
created by testing.(*T).Run
        /usr/local/go/src/testing/testing.go:1629 +0x368
FAIL    github.com/SayatAbdikul/rest_api_for_startup/package_test       0.515s
--- FAIL: TestRegStartup (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
        panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x102371168]

goroutine 18 [running]:
testing.tRunner.func1.2({0x102467a40, 0x1025cbfa0})
        /usr/local/go/src/testing/testing.go:1526 +0x1c8
testing.tRunner.func1()
        /usr/local/go/src/testing/testing.go:1529 +0x384
panic({0x102467a40, 0x1025cbfa0})
        /usr/local/go/src/runtime/panic.go:884 +0x204
database/sql.(*DB).conn(0x10244cc9c?, {0x10249f5b8?, 0x14000112018?}, 0x60?)
        /usr/local/go/src/database/sql/sql.go:1282 +0x28
database/sql.(*DB).prepare(0x14000160190?, {0x10249f5b8, 0x14000112018}, {0x1023f1c8b, 0xb1}, 0x0?)
        /usr/local/go/src/database/sql/sql.go:1587 +0x34
database/sql.(*DB).PrepareContext.func1(0x40?)
        /usr/local/go/src/database/sql/sql.go:1561 +0x48
database/sql.(*DB).retry(0x1400014bc18?, 0x1400014bbd8)
        /usr/local/go/src/database/sql/sql.go:1538 +0x4c
database/sql.(*DB).PrepareContext(0x14000160168?, {0x10249f5b8?, 0x14000112018?}, {0x1023f1c8b?, 0x1400014bc01?})
        /usr/local/go/src/database/sql/sql.go:1560 +0x74
database/sql.(*DB).Prepare(0x14000160140?, {0x1023f1c8b?, 0x14000130b40?})
        /usr/local/go/src/database/sql/sql.go:1577 +0x40
github.com/SayatAbdikul/rest_api_for_startup/postRequests.RegStartup({0x10249f3a0, 0x14000120ac0}, 0x1400017c000)
        /Users/sayat/Documents/GitHub/rest_api_for_startup/postRequests/regStartup.go:38 +0x1d4
net/http.HandlerFunc.ServeHTTP(...)
        /usr/local/go/src/net/http/server.go:2122
github.com/SayatAbdikul/rest_api_for_startup/postRequests_test.TestRegStartup(0x1400011e9c0)
        /Users/sayat/Documents/GitHub/rest_api_for_startup/postRequests/regStartup_test.go:38 +0x1c0
testing.tRunner(0x1400011e9c0, 0x10249cdc0)
        /usr/local/go/src/testing/testing.go:1576 +0x10c
created by testing.(*T).Run
        /usr/local/go/src/testing/testing.go:1629 +0x368
FAIL    github.com/SayatAbdikul/rest_api_for_startup/postRequests       0.935s
FAIL

我尝试将与数据库的连接更改为本地连接(原来是通过IP连接到服务器),还尝试更改测试的位置,但错误信息没有改变。

英文:

I wrote simple post request handler in go that works fine, but when I run the unit test for it, it throws errors.
The connection to db:

var err error
server.DBConn, err = sql.Open("mysql", "root:root@tcp(localhost:8889)/infomatrix_project")
defer server.DBConn.Close()
http.HandleFunc("/reg_startup", post.RegStartup)
http.ListenAndServe(":9000", nil)

The handler function:

type Startup struct {
Name              string `json:"name"`
Login             string `json:"login"`
Password          string `json:"password"`
Email             string `json:"email"`
Description       string `json:"description"`
Logo              string `json:"logo"`
LowestInvestment  int    `json:"lowestInvestment"`
HighestInvestment int    `json:"highestInvestment"`
Region            string `json:"region"`
WebSite           string `json:"website"`
Industry          string `json:"industry"`
}
func RegStartup(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if r.Method != "POST" {
fmt.Fprintf(w, "error: the request is not a POST type")
return
}
//other.AccessSetter(w)
var query Startup
err := json.NewDecoder(r.Body).Decode(&query)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
stmt, err := server.DBConn.Prepare("INSERT INTO startups (name, login, password, email, " +
"description, logo, lowest_investment, highest_investment, region, website, industry) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
defer stmt.Close()
if err != nil {
log.Fatal(err)
return
}
_, err = stmt.Exec(query.Name, query.Login, query.Password, query.Email, query.Description, query.Logo,
query.LowestInvestment, query.HighestInvestment, query.Region, query.WebSite, query.Industry)
if err != nil {
log.Fatal(err)
return
}
fmt.Fprintf(w, "data entered successfully")
}

The testing function:

func TestRegStartup(t *testing.T) {
load := Startup{"название на кириллице", "test_startup", "test_password", "test@example.com", "This is a test startup.",
"https://example.com/test_startup_logo.jpg", 1000, 10000, "Kazakhstan", "https://teststartup.com", "IT"}
loadBytes, _ := json.Marshal(load)
request, err := http.NewRequest("POST", "/reg_startup", bytes.NewBuffer(loadBytes))
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(postRequests.RegStartup)
handler.ServeHTTP(rr, request)
status := rr.Code
if status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
return
}
expected := `"data entered successfully"`
if rr.Body.String() != expected {
t.Errorf("handler returned unexpected body: got %v want %v",
rr.Body.String(), expected)
return
}
}

the errors:

--- FAIL: TestRegStartup (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x102534408]
goroutine 18 [running]:
testing.tRunner.func1.2({0x1025bfa40, 0x102723f80})
/usr/local/go/src/testing/testing.go:1526 +0x1c8
testing.tRunner.func1()
/usr/local/go/src/testing/testing.go:1529 +0x384
panic({0x1025bfa40, 0x102723f80})
/usr/local/go/src/runtime/panic.go:884 +0x204
database/sql.(*DB).conn(0x1025a4c9c?, {0x1025f75b8?, 0x1400009c018?}, 0xe0?)
/usr/local/go/src/database/sql/sql.go:1282 +0x28
database/sql.(*DB).prepare(0x140000e8190?, {0x1025f75b8, 0x1400009c018}, {0x102549c1b, 0xb1}, 0x0?)
/usr/local/go/src/database/sql/sql.go:1587 +0x34
database/sql.(*DB).PrepareContext.func1(0xa0?)
/usr/local/go/src/database/sql/sql.go:1561 +0x48
database/sql.(*DB).retry(0x140000d5c28?, 0x140000d5be8)
/usr/local/go/src/database/sql/sql.go:1538 +0x4c
database/sql.(*DB).PrepareContext(0x140000e8168?, {0x1025f75b8?, 0x1400009c018?}, {0x102549c1b?, 0x140000d5c01?})
/usr/local/go/src/database/sql/sql.go:1560 +0x74
database/sql.(*DB).Prepare(0x140000e8140?, {0x102549c1b?, 0x140000baaa0?})
/usr/local/go/src/database/sql/sql.go:1577 +0x40
github.com/SayatAbdikul/rest_api_for_startup/postRequests.RegStartup({0x1025f73a0, 0x140000a8ac0}, 0x14000104000)
/Users/sayat/Documents/GitHub/rest_api_for_startup/postRequests/regStartup.go:38 +0x1d4
net/http.HandlerFunc.ServeHTTP(...)
/usr/local/go/src/net/http/server.go:2122
github.com/SayatAbdikul/rest_api_for_startup/package_test_test.TestRegStartup(0x14000082d00)
/Users/sayat/Documents/GitHub/rest_api_for_startup/package_test/regStartup_test.go:37 +0x1c0
testing.tRunner(0x14000082d00, 0x1025f4db8)
/usr/local/go/src/testing/testing.go:1576 +0x10c
created by testing.(*T).Run
/usr/local/go/src/testing/testing.go:1629 +0x368
FAIL    github.com/SayatAbdikul/rest_api_for_startup/package_test       0.515s
--- FAIL: TestRegStartup (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x102371168]
goroutine 18 [running]:
testing.tRunner.func1.2({0x102467a40, 0x1025cbfa0})
/usr/local/go/src/testing/testing.go:1526 +0x1c8
testing.tRunner.func1()
/usr/local/go/src/testing/testing.go:1529 +0x384
panic({0x102467a40, 0x1025cbfa0})
/usr/local/go/src/runtime/panic.go:884 +0x204
database/sql.(*DB).conn(0x10244cc9c?, {0x10249f5b8?, 0x14000112018?}, 0x60?)
/usr/local/go/src/database/sql/sql.go:1282 +0x28
database/sql.(*DB).prepare(0x14000160190?, {0x10249f5b8, 0x14000112018}, {0x1023f1c8b, 0xb1}, 0x0?)
/usr/local/go/src/database/sql/sql.go:1587 +0x34
database/sql.(*DB).PrepareContext.func1(0x40?)
/usr/local/go/src/database/sql/sql.go:1561 +0x48
database/sql.(*DB).retry(0x1400014bc18?, 0x1400014bbd8)
/usr/local/go/src/database/sql/sql.go:1538 +0x4c
database/sql.(*DB).PrepareContext(0x14000160168?, {0x10249f5b8?, 0x14000112018?}, {0x1023f1c8b?, 0x1400014bc01?})
/usr/local/go/src/database/sql/sql.go:1560 +0x74
database/sql.(*DB).Prepare(0x14000160140?, {0x1023f1c8b?, 0x14000130b40?})
/usr/local/go/src/database/sql/sql.go:1577 +0x40
github.com/SayatAbdikul/rest_api_for_startup/postRequests.RegStartup({0x10249f3a0, 0x14000120ac0}, 0x1400017c000)
/Users/sayat/Documents/GitHub/rest_api_for_startup/postRequests/regStartup.go:38 +0x1d4
net/http.HandlerFunc.ServeHTTP(...)
/usr/local/go/src/net/http/server.go:2122
github.com/SayatAbdikul/rest_api_for_startup/postRequests_test.TestRegStartup(0x1400011e9c0)
/Users/sayat/Documents/GitHub/rest_api_for_startup/postRequests/regStartup_test.go:38 +0x1c0
testing.tRunner(0x1400011e9c0, 0x10249cdc0)
/usr/local/go/src/testing/testing.go:1576 +0x10c
created by testing.(*T).Run
/usr/local/go/src/testing/testing.go:1629 +0x368
FAIL    github.com/SayatAbdikul/rest_api_for_startup/postRequests       0.935s
FAIL

i tried to change the connection to db(connection was to server by ip and i changed it to localhost) and also tried to change the location of test. Nothing in error has changed.

答案1

得分: 1

让我分享一下我的解决方案以及关于编写和测试Go代码的一些见解。通常,我会按照以下方式进行,但可能还有更好的管理方式。

代码包含在两个文件中。让我们从生产代码开始。

handlers/handlers.go 文件

package handlers

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
)

type Startup struct {
	Name     string `json:"name"`
	Password string `json:"password"`
	Email    string `json:"email"`
}

func RegStartup(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	if r.Method != "POST" {
		fmt.Fprintf(w, "error: the request is not a POST type")
		return
	}
	//other.AccessSetter(w)
	var query Startup
	err := json.NewDecoder(r.Body).Decode(&query)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	dbConn := r.Context().Value("Db").(*sql.DB)
	if dbConn == nil {
		log.Fatal("database connection must be provided!")
		return
	}
	stmt, err := dbConn.Prepare(`INSERT INTO startups (name, password, email) VALUES (?, ?, ?)`)
	defer stmt.Close()
	if err != nil {
		log.Fatal(err)
		return
	}
	_, err = stmt.Exec(query.Name, query.Password, query.Email)
	if err != nil {
		log.Fatal(err)
		return
	}
	fmt.Fprintf(w, "data entered successfully")
}

这里的代码与你的代码非常相似。我简化了Startup结构体以处理较少的字段。最重要的是将*sql.DB传递给处理程序函数。这是通过上下文完成的,这是实现它的最佳方式之一。在main.go文件中,你可以自由地设置超时时间,在时间到期时取消每个正在进行的调用。

请记住,当从上下文中获取值时(还有值的类型!),始终检查nil值。

handlers/handlers_test.go 文件

package handlers

import (
	"context"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/stretchr/testify/assert"
)

func TestRegStartup(t *testing.T) {
	db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
	defer db.Close()
	if err != nil {
		t.Errorf("err not expected while opening mock connection %v", err)
	}

	mock.ExpectPrepare(`INSERT INTO startups (name, password, email) VALUES (?, ?, ?)`)
	mock.ExpectExec(`INSERT INTO startups (name, password, email) VALUES (?, ?, ?)`).WithArgs("john doe", "123456789", "john.doe@example.com").WillReturnResult(sqlmock.NewResult(1, 0))

	w := httptest.NewRecorder()
	r := &http.Request{
		Header: make(http.Header),
	}
	r.Header.Set("Content-Type", "application/json")
	r.Method = http.MethodPost
	r.Body = io.NopCloser(strings.NewReader(`{"name": "john doe", "password": "123456789", "email": "john.doe@example.com"}`))
	r = r.WithContext(context.WithValue(r.Context(), "Db", db))

	RegStartup(w, r)

	assert.Equal(t, "data entered successfully", w.Body.String())
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Errorf("not all expectations were met %v", err)
	}
}

测试代码需要更多的解释:

  1. 你应该依赖sqlmock包来处理数据库的使用。你可以指定如何匹配查询/命令以及它们应该返回的结果。
  2. 你必须创建一个http.ResponseWriter*http.Request来调用RegStartup函数。你可以轻松使用httptest包。
  3. 确保设置HTTP请求的预期值。
  4. *sql.DB包添加到HTTP请求的上下文中。

如果你使用go test -v -cover命令运行测试,一切都应该正常工作。

当然,还有很多可以改进的地方,但这一点可以作为实现更好的方式来编写HTTP处理程序和测试的良好起点。如果你需要更多细节或解释,请随时提问,谢谢!

英文:

let me share my solution with some insights on writing and testing Go code. Usually, I follow this way, but there could be better ways to manage it.
The code is contained in two files. Let's start with the production code.

handlers/handlers.go file

package handlers

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
)

type Startup struct {
	Name     string `json:"name"`
	Password string `json:"password"`
	Email    string `json:"email"`
}

func RegStartup(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	if r.Method != "POST" {
		fmt.Fprintf(w, "error: the request is not a POST type")
		return
	}
	//other.AccessSetter(w)
	var query Startup
	err := json.NewDecoder(r.Body).Decode(&query)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	dbConn := r.Context().Value("Db").(*sql.DB)
	if dbConn == nil {
		log.Fatal("database connection must be provided!")
		return
	}
	stmt, err := dbConn.Prepare(`INSERT INTO startups (name, password, email) VALUES (?, ?, ?)`)
	defer stmt.Close()
	if err != nil {
		log.Fatal(err)
		return
	}
	_, err = stmt.Exec(query.Name, query.Password, query.Email)
	if err != nil {
		log.Fatal(err)
		return
	}
	fmt.Fprintf(w, "data entered successfully")
}

Here, the code is pretty similar to yours. I simplified the Startup struct to deal with fewer fields. The most important thing is passing the *sql.DB to your handler function. This is done through the context which is one of the best ways to achieve it. In the main.go file you're free to set a timeout on it to cancel each ongoing call when the time is expired.

> Keep in mind to always check for nil value when you get from the context (and also the type of the value!).

handlers/handlers_test.go file

package handlers

import (
	"context"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/stretchr/testify/assert"
)

func TestRegStartup(t *testing.T) {
	db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
	defer db.Close()
	if err != nil {
		t.Errorf("err not expected while opening mock connection %v", err)
	}

	mock.ExpectPrepare(`INSERT INTO startups (name, password, email) VALUES (?, ?, ?)`)
	mock.ExpectExec(`INSERT INTO startups (name, password, email) VALUES (?, ?, ?)`).WithArgs("john doe", "123456789", "john.doe@example.com").WillReturnResult(sqlmock.NewResult(1, 0))

	w := httptest.NewRecorder()
	r := &http.Request{
		Header: make(http.Header),
	}
	r.Header.Set("Content-Type", "application/json")
	r.Method = http.MethodPost
	r.Body = io.NopCloser(strings.NewReader(`{"name": "john doe", "password": "123456789", "email": "john.doe@example.com"}`))
	r = r.WithContext(context.WithValue(r.Context(), "Db", db))

	RegStartup(w, r)

	assert.Equal(t, "data entered successfully", w.Body.String())
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Errorf("not all expectations were met %v", err)
	}
}

The test code deserves more explanations:

  1. You should rely on the sqlmock package to deal with the database usage. You can specify how to match the queries/commands and which results they are supposed to return.
  2. You have to create an http.ResponseWriter and *http.Request to invoke the RegStartup function. You can easily use the httptest package.
  3. Make sure to set up the HTTP request as expected.
  4. Add to the context of the HTTP request the *sql.DB package.

If you run the test with the command go test -v -cover everything should work as expected.

For sure, there are a lot of things to improve but this point could be a good starting point to implement HTTP handlers and tests in a better way. Feel free to ask if you need more details or clarifications, thanks!

huangapple
  • 本文由 发表于 2023年5月8日 11:20:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/76197311.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定