Language/Golang

Golang (Go언어) Go로 만드는 웹 (5)

HeeWorld 2025. 1. 10. 18:34

Tucker의 Go 언어 프로그래밍 책과 유튜브를 통해 학습 중입니다.

Golang 마스코트 Gopher(고퍼)

 

POST를 사용하여 테스트 진행하기

- Go로 만드는 웹(4) 포스팅에서 사용했던 프로젝트 그대로 사용하여 POST 테스트 진행하기

func TestCreateUser(t *testing.T) {
	assert := assert.New(t)

	ts := httptest.NewServer(NewHandler())
	defer ts.Close()

	resp, err := http.Post(ts.URL+"/users", "application/json", strings.NewReader(`{"first_name":"Joy", "last_name":"Kim", "email": "joy@mail.com"}`))
	assert.NoError(err)
	assert.Equal(http.StatusCreated, resp.StatusCode)
}

 

- string으로 json 형태로 Body를 사용하여 테스트 코드를 작성함.

   그리고 'go test'로 확인하면, 201(StatusCreated)이 와야 하는데 200(StatusOK)이 와서 FAIL이 되는 것을 확인할 수 있음.

Error 확인

 

- 지난 번과 동일하게 app.go에 Handler 설정이 별도로 없어서 새로 만들어줌.

- User struct를 새로 만들고, createUserHandler도 새로 구성하여 테스트 진행.

// app.go

// User Struct
type User struct {
	ID        int       `json:"id"`
	FirstName string    `json:"first_name"`
	LastName  string    `json:"last_name"`
	Email     string    `json:"email`
	CreateAt  time.Time `json:"created_at"`
}

...

func createUserHandler(w http.ResponseWriter, r *http.Request) {
	user := new(User)
	err := json.NewDecoder(r.Body).Decode(user)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprint(w, err)
		return
	}
	// Create User
	user.ID = 2
	user.CreateAt = time.Now()
	w.WriteHeader(http.StatusCreated)
	data, _ := json.Marshal(user)
	fmt.Fprint(w, string(data))
}

...

// NewHandler make a new my app Handler
func NewHandler() http.Handler {
	mux := mux.NewRouter()
	// mux := http.NewServeMux()

	mux.HandleFunc("/", indexHandler)
	mux.HandleFunc("/users", usersHandler).Methods("GET") // GET 메서드일 떄는 usersHandler로 작동되게
	mux.HandleFunc("/users", createUserHandler).Methods("POST") // POST 메서드일 떄는 usersHandler로 작동되게
	mux.HandleFunc("/users/{id:[0-9]+}", getUserInfoHandler)
	return mux
}

 

- 'go test'를 통해 테스트를 진행하면 모두 PASS 가 되는 것을 확인할 수 있음.

 

// app_test.go

func TestCreateUser(t *testing.T) {
	assert := assert.New(t)

	ts := httptest.NewServer(NewHandler())
	defer ts.Close()

	resp, err := http.Post(ts.URL+"/users", "application/json", strings.NewReader(`{"first_name":"Joy", "last_name":"Kim", "email": "joy@mail.com"}`))
	assert.NoError(err)
	assert.Equal(http.StatusCreated, resp.StatusCode)

	user := new(User)
	err = json.NewDecoder(resp.Body).Decode(user)
	assert.NoError(err)
	assert.NotEqual(0, user.ID)

	id := user.ID
	resp, err = http.Get(ts.URL + "/usrs/" + strconv.Itoa(id))
	assert.NoError(err)
	assert.Equal(http.StatusOK, resp.StatusCode)

	user2 := new(User)
	err = json.NewDecoder(resp.Body).Decode(user2)
	assert.NoError(err)  //Decod하는데 첫 번째 U가 먼저 옴 (Format이 안맞음.)
	assert.Equal(user.ID, user2.ID)
	assert.Equal(user.FirstName, user2.FirstName)
}

 

- Post로 user 데이터를 만들어서 Created 것을 확인했고, 전송된 내용을 읽어 제대로 생성이 된 것인지 확인하는 코드 생성

- 서버가 보낸 resp.Body를 읽어 User를 Decode하여 문제가 있는지 없는지 확인.

- User의 ID가 0이 아니고, User.ID를 받아 실제 User의 정보가 올 수 있도록 http.Get을 사용하여 확인

- 값이 왔을 때 error가 없어야하고, 응답의 상태가 StatusOK가 와야함.

- 데이터는 UserInfo가 Json 형태로 와야해서, user2를 만들어 Json으로 데이터를 읽음.

- user2와 user1은 ID가 같기에 같은 유저라 user와 user2가 같아야함.

 

Error 확인

 

→ 내가 이 error가 난 이유는 resp, err = http.Get(ts.URL + "/usrs/" + strconv.Itoa(id)) 여기 "usrs"라는 오타가 존재함. (오타...)

 

// app.go

package myapp

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"time"

	"github.com/gorilla/mux"
)

// User Struct
type User struct {
	ID        int       `json:"id"`
	FirstName string    `json:"first_name"`
	LastName  string    `json:"last_name"`
	Email     string    `json:"email`
	CreateAt  time.Time `json:"created_at"`
}

var userMap map[int]*User
var lastID int

func indexHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Hello World")
}

func usersHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Get UserInfo by /users/{id}")
}

func getUserInfoHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id, err := strconv.Atoi(vars["id"])
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprint(w, err)
		return
	}

	user, ok := userMap[id]
	if !ok {
		w.WriteHeader(http.StatusOK)
		fmt.Fprint(w, "No User Id:", id)
		return
	}

	w.Header().Add("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	data, _ := json.Marshal(user)
	fmt.Fprint(w, string(data))
}

func createUserHandler(w http.ResponseWriter, r *http.Request) {
	user := new(User)
	err := json.NewDecoder(r.Body).Decode(user)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprint(w, err)
		return
	}
	// Create User
	lastID++
	user.ID = lastID
	user.CreateAt = time.Now()
	userMap[user.ID] = user

	w.Header().Add("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	data, _ := json.Marshal(user)
	fmt.Fprint(w, string(data))
}

// NewHandler make a new my app Handler
func NewHandler() http.Handler {
	userMap = make(map[int]*User)
	lastID = 0
	mux := mux.NewRouter()

	mux.HandleFunc("/", indexHandler)
	mux.HandleFunc("/users", usersHandler).Methods("GET")
	mux.HandleFunc("/users", createUserHandler).Methods("POST")
	mux.HandleFunc("/users/{id:[0-9]+}", getUserInfoHandler)
	return mux
}

 

- 이전에 getUserInfoHandler에 정보를 고정으로 기재해서 넣어놔서 해당 user.ID가 없다고 에러가 남.

- userMap을 만들어서 user가 create한 user정보를 등록해서 가지고 있다가 그 정보가 있으면 user정보를 리턴하고, 없으면 NoUserID를 리턴하는 코드 작성함.

- myapp.go에서 user 정보를 저장할 userMap을 생성함.

- NewHandler에 사용하기 전에 initialize 해줘야하기 때문에 Newhandler하기 전 initialize 먼저하게 만듦.

- Map에 user를 등록할 때는 user를 만들 때 등록할 것이라 Created User에서 유저 정보를 셋팅하고, UserMap에 user.ID값을 등록함.

- ID 값은 매번 변경되어야 하기때문에 마지막 ID를 가지고 있는 변수를 만들어 NewHandler에서 0으로 초기화 함.

- user가 만들어질 때마다 하나씩 증가 되게 코드를 하나 만들어 줌.

 

- getUserInfo는 실질적으로 request한 ID가 있으면 user 정보를 반환하게 하기 위해 mux에 vars를 만들어 request를 보내고, user가 보낸 정보가 들어오면, id를 뽑아와서 타입을 변경함.

- 에러가 있는 경우(변환 과정에 문제가 있으면) StatusBadRequest를 출력하고 에러가 무엇인지 알려줌.

 

- ID에 해당하는 User가 실제 Map에 있는지 확인하기 위해 userMap[id]에서 id에 해당하는 것이 있는지 확인.

- 만약 해당하는 ID가 없는 경우 헤더에서 HTTPStatusOK하고, User ID가 없음을 리턴함.

// app_test.go

package myapp

import (
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"strconv"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestIn(t *testing.T) {
	assert := assert.New(t)

	ts := httptest.NewServer(NewHandler())
	defer ts.Close()

	resp, err := http.Get(ts.URL)
	assert.NoError(err)
	assert.Equal(http.StatusOK, resp.StatusCode)

	data, _ := io.ReadAll(resp.Body)
	assert.Equal("Hello World", string(data))

}

func TestUsers(t *testing.T) {
	assert := assert.New(t)

	ts := httptest.NewServer(NewHandler())
	defer ts.Close()

	resp, err := http.Get(ts.URL + "/users")
	assert.NoError(err)
	assert.Equal(http.StatusOK, resp.StatusCode)
	data, _ := io.ReadAll(resp.Body)
	assert.Contains(string(data), "Get UserInfo")
}

func TestGetUserInfo(t *testing.T) {
	assert := assert.New(t)

	ts := httptest.NewServer(NewHandler())
	defer ts.Close()

	resp, err := http.Get(ts.URL + "/users/89")
	assert.NoError(err)
	assert.Equal(http.StatusOK, resp.StatusCode)
	data, _ := io.ReadAll(resp.Body)
	assert.Contains(string(data), "No User Id:89")

	resp, err = http.Get(ts.URL + "/users/56")
	assert.NoError(err)
	assert.Equal(http.StatusOK, resp.StatusCode)
	data, _ = io.ReadAll(resp.Body)
	assert.Contains(string(data), "No User Id:56")
}

func TestCreateUser(t *testing.T) {
	assert := assert.New(t)

	ts := httptest.NewServer(NewHandler())
	defer ts.Close()

	resp, err := http.Post(ts.URL+"/users", "application/json", strings.NewReader(`{
        "first_name": "Joy",
        "last_name": "Kim",
        "email": "joy@mail.com"
    }`))
	assert.NoError(err)
	assert.Equal(http.StatusCreated, resp.StatusCode)

	user := new(User)
	err = json.NewDecoder(resp.Body).Decode(user)
	assert.NoError(err)
	assert.NotEqual(0, user.ID)

	id := user.ID
	resp, err = http.Get(ts.URL + "/users/" + strconv.Itoa(id))
	assert.NoError(err)
	assert.Equal(http.StatusOK, resp.StatusCode)

	user2 := new(User)
	err = json.NewDecoder(resp.Body).Decode(user2)
	assert.NoError(err)
	assert.Equal(user.ID, user2.ID)
	assert.Equal(user.FirstName, user2.FirstName)
}

 

해당 코드로 테스트를 진행하면 모두 PASS가 되는 것을 확인할 수 있음.

 

 

p.s 아... 강의 다시 듣고 다시 들어서 복습해야 할 듯합니다...