지난 게시글에서 만든 todo list 를 refactoring 하였습니다.
refactoring 을 쉽게 하기 위해서 테스트 코드를 먼저 작성하여 현재의 기능을 검증하고 코드를 수정하면 refactoring 으로 인한 오류를 쉽게 검증할 수 있습니다.
기본구조는 아래와 같다.
1. 테스트 코드를 먼저 만들어서 기존 기능을 검증하겠습니다
./myapp/app_test.go
package myapp
import (
// 모델은 검증코드 이후, refactoring 에서 추가한 부분입니다
"GO/tuckersGo/goWeb/web16-todo_refactoring/model"
"encoding/json"
"log"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
)
func TestTodos(t *testing.T) {
assert := assert.New(t)
ts := httptest.NewServer(MakeNewHandler())
defer ts.Close()
// add testing - first data
resp, err := http.PostForm(ts.URL+"/todos", url.Values{"name": {"Test todo"}})
assert.NoError(err)
assert.Equal(http.StatusCreated, resp.StatusCode)
// 추가한 정보를 서버에서 다시 가져와서 원본과 비교함
todo := new(model.Todo)
err = json.NewDecoder(resp.Body).Decode(&todo)
assert.NoError(err)
assert.Equal(todo.Name, "Test todo")
id1 := todo.ID
log.Println("app_test.go / add result >", *todo, id1)
// add testing - second data
resp, err = http.PostForm(ts.URL+"/todos", url.Values{"name": {"Test todo2"}})
assert.NoError(err)
assert.Equal(http.StatusCreated, resp.StatusCode)
// todo = new(Todo)
err = json.NewDecoder(resp.Body).Decode(&todo)
assert.NoError(err)
assert.Equal(todo.Name, "Test todo2")
id2 := todo.ID
log.Println("app_test.go / add result >", *todo, id2)
// get testing - getting whole data
resp, err = http.Get(ts.URL + "/todos")
assert.NoError(err)
assert.Equal(http.StatusOK, resp.StatusCode)
todos := []*model.Todo{}
err = json.NewDecoder(resp.Body).Decode(&todos)
assert.NoError(err)
assert.Equal(len(todos), 2)
for i := 0; i < len(todos); i++ {
log.Println("after getting whole data >", *todos[i])
}
// complete testing
resp, err = http.Get(ts.URL + "/complete-todo/" + strconv.Itoa(id2) + "?complete=true")
assert.NoError(err)
assert.Equal(http.StatusOK, resp.StatusCode)
resp, err = http.Get(ts.URL + "/todos")
assert.NoError(err)
assert.Equal(http.StatusOK, resp.StatusCode)
// todos := []*Todo{}
err = json.NewDecoder(resp.Body).Decode(&todos)
assert.NoError(err)
assert.Equal(len(todos), 2)
for i := 0; i < len(todos); i++ {
log.Println("after complete for >", *todos[i])
}
// DELETE testing
req, _ := http.NewRequest("DELETE", ts.URL+"/todos/"+strconv.Itoa(id1), nil) // data는 필요없어서 nil 처리
resp, err = http.DefaultClient.Do(req)
assert.NoError(err)
assert.Equal(http.StatusOK, resp.StatusCode)
log.Println("delete id1: ", id1)
resp, err = http.Get(ts.URL + "/todos")
assert.NoError(err)
assert.Equal(http.StatusOK, resp.StatusCode)
// todos := []*Todo{}
err = json.NewDecoder(resp.Body).Decode(&todos)
assert.NoError(err)
assert.Equal(len(todos), 1)
for i := 0; i < len(todos); i++ {
log.Println("after delete for >", *todos[i])
}
}
./model/model.go - app.go 에서 Todo 구조체를 model 로 분리하고 관련된 기능을 함수로 구현함.
package model
import (
"log"
"time"
)
type Todo struct {
ID int `json:"id"`
Name string `json:"name"`
Completed bool `json:"completed"`
CreatedAt time.Time `json:"created_at"`
}
var todoMap map[int]*Todo
func init() {
todoMap = make(map[int]*Todo)
}
// todoMap 이 private 변수이므로 함수로 처리함
func GetTodos() []*Todo {
list := []*Todo{}
for _, v := range todoMap {
list = append(list, v)
}
return list
}
func AddTodo(name string) *Todo {
id := len(todoMap) + 1
todo := &Todo{id, name, false, time.Now()}
todoMap[id] = todo
log.Println("add Todo success")
return todo
}
func RemoveTodo(id int) bool {
if _, ok := todoMap[id]; ok {
delete(todoMap, id)
return true
}
return false
}
func CompleteTodo(id int, complete bool) bool {
if todo, ok := todoMap[id]; ok {
todo.Completed = complete
return true
}
return false
}
./myapp/app.go - Todo 구조체와 관련된 부분을 모두 model.go 로 이관함
package myapp
import (
"GO/tuckersGo/goWeb/web16-todo_refactoring/model"
"net/http"
"strconv"
"github.com/gorilla/mux"
"github.com/unrolled/render"
)
var rd *render.Render
func indexHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/todo.html", http.StatusTemporaryRedirect)
}
func getTodoListHandler(w http.ResponseWriter, r *http.Request) {
list := model.GetTodos()
//client 로 해당 정보 전송
rd.JSON(w, http.StatusOK, list)
}
func addTodoHandler(w http.ResponseWriter, r *http.Request) {
name := r.FormValue("name")
todo := model.AddTodo(name)
//client 로 해당 정보 전송
rd.JSON(w, http.StatusCreated, todo)
}
type Success struct {
Success bool `json:"success"`
}
func removeTodoHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, _ := strconv.Atoi(vars["id"])
ok := model.RemoveTodo(id)
if ok {
//client 로 처리결과 전송
rd.JSON(w, http.StatusOK, Success{Success: true})
} else {
//client 로 처리결과 전송
rd.JSON(w, http.StatusOK, Success{Success: false})
}
}
func completeTodoHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, _ := strconv.Atoi(vars["id"])
complete := r.FormValue("complete") == "true"
ok := model.CompleteTodo(id, complete)
if ok {
//client 로 처리결과 전송
rd.JSON(w, http.StatusOK, Success{Success: true})
} else {
//client 로 처리결과 전송
rd.JSON(w, http.StatusOK, Success{Success: false})
}
}
func MakeNewHandler() http.Handler {
rd = render.New()
mux := mux.NewRouter()
mux.HandleFunc("/", indexHandler)
mux.HandleFunc("/todos", getTodoListHandler).Methods("GET")
mux.HandleFunc("/todos", addTodoHandler).Methods("POST")
mux.HandleFunc("/todos/{id:[0-9]+}", removeTodoHandler).Methods("DELETE")
mux.HandleFunc("/complete-todo/{id:[0-9]+}", completeTodoHandler).Methods("GET")
return mux
}
[참고자료]
'GO lang > web' 카테고리의 다른 글
[GO] Todo list - interface 구현2 (0) | 2021.11.18 |
---|---|
[GO] Todo list - interface 구현1 (0) | 2021.11.18 |
[GO] Todo list - map 자료구조 (0) | 2021.11.17 |
[GO] Web - Restful API, TDD (0) | 2021.11.04 |
[GO] Web - 테스트 코드 작성방법(TDD) (0) | 2021.11.04 |