Language/Golang

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

HeeWorld 2025. 1. 7. 15:58

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

Golang 마스코트 Gopher(고퍼)

 

테스트 환경 구성

- 기존에 사용했던 디렉터리에서 새로운 디렉터리를 만들고 새 파일을 만들어 아래와 같이 수정함.

현재 내 환경 구성 Web > myapp > app.go

//main.go

package main

import (
	"Users/<path>/Web/myapp"
	"net/http"
)

func main() {
	http.ListenAndServe(":3000", myapp.NewHttpHandler())
}

 

//app.go (New File)

package myapp

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

type User struct {
	FirstName string    `json:"First_name"`
	LastName  string    `json:"Last_name"`
	Email     string    `json:"email"`
	CreatedAt time.Time `json:"Created_at"`
}

type fooHandler struct{}

// ServeHTTP implements http.Handler.
func (f *fooHandler) ServeHTTP(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, "Bad Requset: ", err)
		return
	}
	user.CreatedAt = time.Now()

	data, _ := json.Marshal(user)
	w.Header().Add("content-type", "application/json") // JSON 포맷임을 표시
	w.WriteHeader(http.StatusCreated)
	fmt.Fprint(w, string(data))
}

func barHandler(w http.ResponseWriter, r *http.Request) {
	name := r.URL.Query().Get("name")
	if name == "" {
		name = "World"
	}
	fmt.Fprintf(w, "Hello %s!", name)
}

func NewHttpHandler() http.Handler {
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Hello World")
	})

	mux.HandleFunc("/bar", barHandler)

	mux.Handle("/foo", &fooHandler{})
	return mux
}

 

- 위와 같이 코드를 수정하고 다시 Debugging하고 웹에서 localhost:3000, /bar, /foo 호출하여 확인.

localhost:3000 웹 호출
localhost:3000/bar 호출
localhost:3000/foo

 

 

테스팅 파일 생성

- myapp 디렉터리에 app_test.go 파일을 생성

  (go는 컨벤션이 <이름>_test 라고 붙이면, 테스트 코드처럼 작동함)


* 테스트 패키지 설치하기 (https://github.com/smartystreets/goconvey)

- 테스크 코드 테스트 하기 매우 좋은 패키지로 열심히 시도해도 계속 에러가 나서 GitHub을 읽어보니...

    "Currently this means Go 1.16 and Go 1.17 are supported." 버전을 탑니다 ㅎ 전 1.23이거든요... 없이 합시다

- 그래도 설치하면 go test 명령어를 통해 확인할 수 있으니 설치해서 확인하는 것을 추천!

 

처음에 Github대로 바로 install 하면 맥북 기준으로 아래와 같이 패키지 제공하지 않는다고 나오니, get → install 하기

# go install github.com/smartystreets/goconvey
no required module provides package github.com/smartystreets/goconvey; to add it:
	go get github.com/smartystreets/goconvey

- 기존 app.go에서 mux.HandleFun중  "/" 최상위 경로 Func을 아래와 같이 분리.

   (맨 위에 func으로 옮김)

//app.go

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

...

func NewHttpHandler() http.Handler {
	mux := http.NewServeMux()
	mux.HandleFunc("/", indexHandler)

	mux.HandleFunc("/bar", barHandler)

	mux.Handle("/foo", &fooHandler{})
	return mux
}

 

- 테스트 코드를 작성하면, Tucker님은 goconvey로 결과 값을 받지만 나는 받을 수가 없음

   대신 goconvey를 설치하면 터미널에서 go test 명령어를 통해 결과 값을 받을 수 있음!

   test 코드가 있는 디렉터리로 경로 이동 후 "go test" 실행

package myapp

import (
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestIndexPathHandler(t *testing.T) {
	res := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/", nil)

	indexHandler(res, req)

	if res.Code != http.StatusOK {
		t.Fatal("Failed!!!", res.Code)
	}
}

PASS를 확인할 수 있음.

 

- 실패코드는 Status를 BedRequest로 변경해서 확인해보기

    400이 와야하는데 200(StatusOK)이 와서 실패함.

	if res.Code != http.StatusBadRequest {
		t.Fatal("Failed!!!", res.Code)
	}
    
    // Status가 BedRequest일 때 OK

 

FAIL을 확인할 수 있음.

 

* assert 패키지 

- https://github.com/stretchr/testify/tree/master/assert 를 사용

- 새로운 터미널 열어서 go get github.com/stretchr/testify/assert 실행 (assert 패키지만 가져옴)

- import 부분에 "github.com/stretchr/testify/assert" 패키지 추가

import (
	"net/http"
	"net/http/httptest"
	"testing"

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

 

 

✓ assert를 사용하여 테스트 코드 만들기

package myapp

import (
	"net/http"
	"net/http/httptest"
	"testing"

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

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

	res := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/", nil)

	indexHandler(res, req)

	assert.Equal(http.StatusOK, res.Code)
}

결과 PASS를 확인할 수 있음.

 

✓ 새로운 테스트 코드

   내가 사용하는 버전에서는 ioutil이 없는 것 같고, 유튜브 댓글 통해 io로 사용하면 된다고 확인함 (버전 업글시 바뀐듯)

package myapp

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

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

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

	res := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/", nil)

	indexHandler(res, req)

	assert.Equal(http.StatusOK, res.Code)
	data, _ := io.ReadAll(res.Body)
	assert.Equal("Hello World", string(data))
}

 

결과는 PASS가 나오는 것을 확인

 

✓ BarHandler 테스트 코드 추가

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

	res := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/bar", nil)

	barHandler(res, req)

	assert.Equal(http.StatusOK, res.Code)
	data, _ := io.ReadAll(res.Body)
	assert.Equal("Hello World", string(data))
}

 

- 저장 후에 go test를 돌리면, Fail이 발생하고 내용을 보면 실제 "Hello world!"가 왔다는 것을 확인할 수 있음.

FAIL과 Error 내용을 확인할 수 있음 (Hello World가 아닌 뒤에 !가 있어서 실패)

 

assert.Equal("Hello World!", string(data))

 

- assert.Equal의 Hello World 뒤에 !를 붙이고 저장해서 다시 go test를 돌리면 PASS되는 것을 확인할 수 있음.

PASS를 확인할 수 있음

 

✓ BarHandler에서 req 경로를 "/bar" → "/" 로 변경 후 실행해도 PASS가 됨.

   "/"일 때는 "Hello World"가 와야하는데 "Hello World!"가 온 것.

   mux를 제대로 사용하지 않고 barHandler를 직접 호출해서 타겟이 제대로 적용이 되지 않은 것.

   mux 코드를 추가하여 mux.ServeHTTP로 res,req를 호출해야 mux가 타겟에 맞춰 분배해서 호출함.

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

	res := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/", nil)

	mux := NewHttpHandler()
	mux.ServeHTTP(res, req)

	assert.Equal(http.StatusOK, res.Code)
	data, _ := io.ReadAll(res.Body)
	assert.Equal("Hello World!", string(data))
}

 

- 테스트 결과를 보면 "Hello World!"가 와야하는데 "Hello World"가 와서 FAIL 되었다는 것을 확인할 수 있음.

   Index경로로 핸들러가 호출된 것을 알 수 있음. (mux로 바꿔 "/" 가 동작한다는 것)

FAIL과 Error를 확인할 수 있음.

 

✓ BarHandler에 "/bar"로 타겟을 바꿔주고, IndexHandler에 동일하게 mux 코드를 추가하고 테스트 실행하면 OK되는 것을 확인할 수 있음.

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

	res := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/", nil)

	mux := NewHttpHandler()
	mux.ServeHTTP(res, req)

	assert.Equal(http.StatusOK, res.Code)
	data, _ := io.ReadAll(res.Body)
	assert.Equal("Hello World", string(data))
}

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

	res := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/bar", nil)

	mux := NewHttpHandler()
	mux.ServeHTTP(res, req)

	assert.Equal(http.StatusOK, res.Code)
	data, _ := io.ReadAll(res.Body)
	assert.Equal("Hello World!", string(data))
}

 

✓ 타겟에 name을 넣어 코드를 copy/paste 하기

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

	res := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/bar?name=hee", nil)

	mux := NewHttpHandler()
	mux.ServeHTTP(res, req)

	assert.Equal(http.StatusOK, res.Code)
	data, _ := io.ReadAll(res.Body)
	assert.Equal("Hello hee!", string(data))
}

 

PASS 된 것을 확인할 수 있음