본문 바로가기

GO lang/web

[GO] Todo list - refactoring(구조변경)

지난 게시글에서 만든 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