Модульное тестирование httptest на основе Gin framework
2.1 ParseToStr выводит пары ключ-значение на карте в виде строки запроса.
-
2.3 ParseToStr выводит пары ключ-значение на карте в виде строки запроса
Прошлой ночью, когда я изучал курс МООК, я написал простую демонстрацию лотереи, намереваясь просто проверить, изменяются ли критические ресурсы в параллельном сценарии.
Затем, после долгого метания, тест прошел успешно, и я записал то, что делал.httptest
Чему вы научились во время модульного тестирования.
1. Пример кода
Следующий код является необходимым тестовым содержимым, которое примерно имеет три функции:
- домашняя страница индекса, запрос GET
- Импорт пользователей лотереи, запрос POST
- Розыгрыши, запрос GET
1. Глобальные переменные и основная функция
Не забудьте инициализировать блокировку, иначе она не будет работать.
// 用户列表 共享变量(临界资源)
var userList []string
// gin引擎
var router *gin.Engine
// 互斥锁
var mux sync.Mutex
func main() {
router.Run(":8080")
}
2. Инициализируйте маршрутизацию
В основном инициализируйте маршрутизацию трех функций
func init() {
router = gin.Default()
// 路由组
userGroup := router.Group("/user")
{
// 首页
userGroup.GET("/index", Index)
// 导入用户
userGroup.POST("/import", ImportUsers)
// 抽奖
userGroup.GET("/lucky", GetLuckyUser)
}
}
3. Три основные функции
После успешного выполнения запроса каждая страница возвращает строку (содержащую собственную информацию)
3.1 Главная
func Index(c *gin.Context) {
c.String(http.StatusOK, "当前参与抽奖的用户人数:%d", len(userList))
}
3.2 Импорт пользователей
func ImportUsers(c *gin.Context) {
strUsers := c.Query("users")
users := strings.Split(strUsers, ",")
// 在操作 全局变量 userList 之前加互斥锁,加完锁记得释放
mux.Lock()
defer mux.Unlock()
// 统计当前已经在参加抽奖的用户数量
currUserCount := len(userList)
// 将页面提交的用户导入到 userList 中,参与抽奖
for _, user := range users {
user = strings.TrimSpace(user)
if len(user) > 0 {
userList = append(userList, user)
}
}
// 统计当前总共参加抽奖人数
userTotal := len(userList)
c.String(http.StatusOK, "当前参与抽奖的用户数量:%d,导入的用户数量:%d", userTotal, (userTotal - currUserCount))
}
3.3 Удачный розыгрыш
func GetLuckyUser(c *gin.Context) {
var user string
// 在操作 全局变量 userList 之前加互斥锁,加完锁记得释放
mux.Lock()
defer mux.Unlock()
count := len(userList)
if count > 1 {
seed := time.Now().UnixNano()
// 以随机数设置中奖用户, [0,count)中的随机值
lottery_index := rand.New(rand.NewSource(seed)).Int31n(int32(count))
user = userList[lottery_index]
// 当前参与抽奖用户减 1
userList = append(userList[0:lottery_index], userList[lottery_index+1:]...)
c.String(http.StatusOK, "中奖用户为:%s,剩余用户数:%d", user, count-1)
} else if count == 1 {
user = userList[0]
userList = userList[0:0] // 清空参与抽奖的用户列表
c.String(http.StatusOK, "中奖用户为:%s,剩余用户数:%d", user, count-1)
} else {
c.String(http.StatusOK, "当前无参与抽奖的用户,请导入新的用户。")
}
}
2. Функция тестового инструмента
существуетhttptestUtil.go
Файл в основном инкапсулирует следующие служебные функции:
2.1 ParseToStr выводит пары ключ-значение на карте в виде строки запроса
// ParseToStr 将map中的键值对输出成querystring形式
func ParseToStr(mp map[string]string) string {
values := ""
for key, val := range mp {
values += "&" + key + "=" + val
}
temp := values[1:]
values = "?" + temp
return values
}
2.2 Получить В соответствии с конкретным uri запроса инициировать запрос на получение и вернуть ответ
func Get(uri string, router *gin.Engine) *httptest.ResponseRecorder {
// 构造get请求
req := httptest.NewRequest("GET", uri, nil)
// 初始化响应
w := httptest.NewRecorder()
// 调用相应的handler接口
router.ServeHTTP(w, req)
return w
}
2.3 ParseToStr выводит пары ключ-значение на карте в виде строки запроса
Создайте запрос POST, данные формы начинаются сquerystring
Форма добавляется после uri
Примечание. Параметры формы form можно передавать черезquerystring
Форма прикреплена к адресу URI для доставки
Таким образом, запрос POST для получения параметров должен вызываться, когдаc.Query("users")
, вместоc.PostFprm("users")
, не говоря уже оc.Param("users)
Конечно, использовать напрямуюc.ShouldBind()
Пусть GIN автоматически определяет, каким способом является параметры запроса.
код показывает, как показано ниже:
// PostForm 根据特定请求uri和参数param,以表单形式传递参数,发起post请求返回响应
func PostForm(uri string, param map[string]string, router *gin.Engine) *httptest.ResponseRecorder {
req := httptest.NewRequest("POST", uri+ParseToStr(param), nil)
// 初始化响应
w := httptest.NewRecorder()
// 调用相应handler接口
router.ServeHTTP(w, req)
return w
}
2.4 PostJson В соответствии с конкретным uri запроса и параметром param параметры передаются в форме Json, и инициируется почтовый запрос для возврата ответа.
// PostJson 根据特定请求uri和参数param,以Json形式传递参数,发起post请求返回响应
func PostJson(uri string, param map[string]interface{}, router *gin.Engine) *httptest.ResponseRecorder {
// 将参数转化为json比特流
jsonByte, _ := json.Marshal(param)
// 构造post请求,json数据以请求body的形式传递
req := httptest.NewRequest("POST", uri, bytes.NewReader(jsonByte))
// 初始化响应
w := httptest.NewRecorder()
// 调用相应的handler接口
router.ServeHTTP(w, req)
return w
}
3. Запустите тест httptest
GolangСпецификация должна рекомендовать способ написания тестовой функции, которая начинается сTest
В начале следуют название метода.
Чтобы проверить, является ли код одновременно безопасным, тесты трех функций написаны в одной и той же тестовой функции, поэтому они называютсяTestMVC
.
func TestMVC(t *testing.T) {
var w *httptest.ResponseRecorder
assert := assert.New(t)
// 1.测试 index 请求
urlIndex := "/user/index"
w = Get(urlIndex, router)
assert.Equal(200, w.Code)
assert.Equal("当前参与抽奖的用户人数:0", w.Body.String())
// 2.测试 import 请求,导入用户数
var wg sync.WaitGroup // 定义wg, 用来阻塞 goroutine
for i := 0; i < 100000; i++ {
// 开一个等待
wg.Add(1)
go func(i int) { // i 不属于临界资源,是安全的
defer wg.Done() // 一个 goroutine 跑完后要减1,
// 测试 /user/import 请求,模拟从 form 表单中获取数据
param := make(map[string]string)
param["users"] = "user" + strconv.Itoa(i)
urlImport := "/user/import"
w = PostForm(urlImport, param, router)
assert.Equal(200, w.Code)
}(i)
}
// 等待上面的协程运行完,再接着测试
wg.Wait()
// 3.测试 urlIndex 请求,查看当前参与抽奖用户是否为 for 循环总数
w = Get(urlIndex, router)
assert.Equal(200, w.Code)
assert.Equal("当前参与抽奖的用户人数:100000", w.Body.String())
// 4.测试 抽奖
urlLucky := "/user/lucky"
w = Get(urlLucky, router)
assert.Equal(200, w.Code)
// 5.抽奖一次之后,再发起 index 请求,查看查看当前参与抽奖用户是否减 1
w = Get(urlIndex, router)
assert.Equal(200, w.Code)
assert.Equal("当前参与抽奖的用户人数:99999", w.Body.String())
}
4. Запустите модульные тесты и просмотрите результаты
Результат работы показан на рисунке:
Как показано на рисунке:
На моем персональном компьютере время выполнения теста: 9,21 с, по названию поля пользователей также видно, что он выполнялся 100 000 раз, потому что он выполняется параллельно, поэтому порядок не должен отображаться последовательно от 1 до 100 000 (кто захватывает ЦП, кто выполняет ресурс)
5. Резюме
Я начал практиковать проект в 7 часов вечера прошлой ночью, проводил модульные тесты и спал 6 часов между ними. После утреннего пробуждения, после обучения и изучения теста прошлой ночью, мысли на утро были очень ясными.Не только модульный тест был успешным, но и тестовый код, с которым я возился до этого, был отрефакторен и оптимизирован. Официально он не был завершен до 11 часов утра.
пишу в первый разGolangМодульный тест httptest, весь процесс состоит в том, чтобы учиться и практиковаться во время поиска, и, наконец, преуспел. Напишите об опыте тестирования httptest:
- Перед тестированием инкапсулируйте методы запроса, такие как get put, и инкапсулируйте их в httptestUtil для упрощения тестирования.
- Гибкие среды тестирования приложений, такие как
Testify
, можно написать гораздо меньше, если судить (в основном используется для оценки кода ответа и сущности ответа). Я только началif else
Я написал много суждений, а позже изучил этот тестовый фреймворк - Тестовый код должен быть максимально кратким, чтобы обеспечить удобочитаемость и удобство сопровождения. В противном случае напишите кучу кода, который легко логически запутать, и выглядит очень раздражающим, что влияет на тестовый разум и точность теста.
- Если вы столкнулись с новой тестовой проблемой, постарайтесь провести как можно больше исследований, успокоиться и подумать об этом. Скорее всего, проблема, с которой вы боретесь, не является настоящей причиной
- Если тест прошел хорошо, то все в порядке, если тест не прошел, вы искали много информации и потратили много времени на тест, то вы должны написать блог (или заметку) в конце, чтобы запишите, что вы узнали и подумали, иначе в будущем вы столкнетесь с проблемами.
- Тестовый код лучше всего размещать в блоге (или примечании к приложению) для удобного просмотра позже.
- Самое главное иметь ясный ум. Тест легко вызвать у людей головокружение и раздражительность, не сдавайтесь, остановитесь на некоторое время, сделайте перерыв и дайте мозгу расслабиться.
Использованная литература:
1.Официальный тестовый документ Джина
2.Модульное тестирование на основе фреймворка golang gin
3.Улучшайте тесты и макеты GO с помощью Testify