Go Lang 웹 프레임워크 기초 - Gin 체험하기 (6) 메인 페이지 기사 리스트 구조만들기
해당 튜토리얼은 'Building Go Web Applications and Microservices Using Gin'의 내용을 번역하며 개인적으로 정리한 것입니다.
이번 시간에는 기사 리스트의 뼈대를 만들고 테스트해보도록 하겠습니다.
1. 뷰 템플릿 작성
메인 페이지에 기사(Article) 리스트가 표시될 예정이므로 새로 템플릿을 만들 필요는 없습니다. 하지만 현재의 내용을 기사 리스트로 대체하려면 index.html 템플릿에 변경이 필요합니다.
이 수정사항을 적용하기 위해서 기사 리스트가 payload라는 변수의 템플릿에 전달된다고 해봅시다. 이러한 가정하에서 다음의 snippet은 모든 기사의 리스트들을 표시해야 합니다.
{{range .payload }}
<!--ID를 기준으로 기사에 대한 링크를 만듭니다-->
<a href="/article/view/{{.ID}}">
<!--기사의 제목을 표시-->
<h2>{{.Title}}</h2>
</a>
<!--기사의 컨텐츠를 표시-->
<p>{{.Content}}</p>
{{end}}
이 snippet은 payload 변수의 모든 항목을 반복하고, 각 기사의 제목과 컨텐츠를 표시합니다. 위의 snippet도 각 기사로 연결됩니다. 아직 개별 기사들을 표시하기 위한 라우트 핸들러를 정의하지 않았기 때문에 이 링크는 예상대로 작동되지 않을 것입니다.
업데이트 된 index.html 파일은 다음의 코드를 포함하여야 합니다.
<!--index.html-->
<!-- header.html 템플릿을 이 위치에 포함시킵니다. -->
{{ template "header.html" .}}
<!--Loop over the `payload` variable, which is the list of articles-->
{{range .payload }}
<!-- ID를 기준으로 기사에 대한 링크를 만듭니다. -->
<a href="/article/view/{{.ID}}">
<!-- 기사의 제목을 표시 -->
<h2>{{.Title}}</h2>
</a>
<!-- 기사의 컨텐츠를 표시 -->
<p>{{.Content}}</p>
{{end}}
<!-- footer.html 템플릿을 이 위치에 포함시킵니다 -->
{{ template "footer.html" .}}
2. 유닛 테스트를 포함한 라우터 핸들러 요구사항 지정
index 경로에 대한 핸들러를 만들기 전에 이 라우터 핸들러의 예상 동작을 정의하는 테스트를 먼저 만들어보도록 합시다. 이 테스트는 다음의 조건들을 확인해볼 것입니다
- 핸들러는 HTTP status 200을 응답합니다
- 반환된 HTML에는 'Home page' 텍스트가 포함된 제목 태그를 포함합니다.
테스트를 위한 코드는 handlers.article_test.go 파일의 TestShowIndexPageUnauthentified 함수에 저장됩니다. 우리는 이 함수가 사용하는 헬퍼 기능을 common_test.go 파일에 두도록 할 것입니다.
handler.article_test.go의 내용은 다음과 같습니다
//------------------- handlers.article_test.go -----------------//
package main
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
// ----- 홈페이지에 대한 GET 요청이 인증되지 않은 사용자에 대해 HTTP 200 코드가 있는 홈페이지를 반환하는지 테스트합니다 ----- //
func TestShowIndexPageUnauthenticated(t *testing.T) {
r := getRouter(true)
r.GET("/", showIndexPage)
// ---- 위의 라우터로 보낼 요청을 생성합니다 ----- //
req, _ := http.NewRequest("GET", "/", nil)
testHTTPResponse(t, r, req, func(w *httptest.ResponseRecorder) bool {
// ----- HTTP status 코드가 200인지 확인합니다 ----- //
statusOK := w.Code == http.StatusOK
// ------ 페이지의 제목이 'Home Page' 인지 테스트합니다 ------- //
// --- HTML 페이지를 파싱하고 처리할 수 있는 라이브러리를 사용하여 훨씬 디테일한 테스트를 수행합니다 --- //
p, err := ioutil.ReadAll(w.Body)
pageOK := err == nil && strings.Index(string(p), "<title>Home Page</title>") > 0
return statusOK && pageOK
})
}
common_test.go 의 내용은 다음과 같습니다.
package main
import (
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/gin-gonic/gin"
)
var tmpArticleList []article
// ------- 이 함수는 테스트 함수를 실행하기 전, 설정에 사용합니다 ----- //
func TestMain(m *testing.M) {
// ----- Gin을 테스트 모드로 설정합니다 ----- //
gin.SetMode(gin.TestMode)
// ------ 다른 테스트를 실행합니다 ------ //
os.Exit(m.Run())
}
// ---- 테스트 중 라우터를 만드는 헬퍼 함수 ---- //
func getRouter(withTemplates bool) *gin.Engine {
r := gin.Default()
if withTemplates {
r.LoadHTMLGlob("templates/*")
}
return r
}
// ----- 요청을 처리하고 응답을 테스트하는 헬퍼 함수 ------- //
func testHTTPResponse(t *testing.T, r *gin.Engine, req *http.Request, f func(w *httptest.ResponseRecorder) bool) {
// ---- 응답 레코더 생성 ---- //
w := httptest.NewRecorder()
// ------ 서비스를 생성하고 위의 요청을 처리합니다 ----- //
r.ServeHTTP(w, req)
if !f(w) {
t.Fail()
}
}
// ---- 이 함수는 테스트를 위해 메인 리스트를 임시 리스트에 저장하는데 사용됩니다 ----- //
func saveLists() {
tmpArticleList = articleList
}
// ------ 이 함수는 임시 리스트에서 메인 리스트를 복구하는데 사용됩니다 ------ //
func restoreLists() {
articleList = tmpArticleList
}
우리는 이 테스트를 구현하기 위해 몇가지의 헬프 함수를 작성했습니다. 이 함수들은 유사한 기능을 테스트하기 위해 추가 테스트를 작성할 때, 상용 코드를 줄이는 데 도움을 줄 것입니다.
이 TestMain 함수는 Gin이 테스트 모드를 사용하도록 설정하고, 나머지 테스트 함수를 호출합니다. getRouter 함수는 메인 어플리케이션과 유사한 방법으로 라우터를 만들고 반환합니다. saveLists() 함수는 원본 기사 목록을 임시 변수에 저장합니다. 이 임시 변수는 restoreLists() 함수에 의해 유닛 테스트가 실행된 후 기사 리스트를 초기 상태로 복원하는데 사용됩니다.
최종적으로, testHTTPResponse 함수는 전달된 함수를 실행하여 테스트의 성공 여부를 나타내는 boolean true 값을 반환하는지 확인합니다. 이 함수는 HTTP 요청의 응답을 테스트하는 데 필요한 코드가 중복이 되지 않도록 하는데 도움을 줄 것입니다.
HTTP 코드와 반환된 HTML을 확인하려면 다음의 몇가지를 수행합니다.
- 새 라우터를 생성합니다.
- 기본 App이 사용하는 것과 동일한 핸들러를 사용하도록 경로를 정의합니다. (ShowIndexPage)
- 이 라우터에 접근할 수 있는 새 요청을 생성합니다.
- HTTP 코드와 HTML을 테스트하기 위해 응답을 처리하는 함수를 만듭니다.
- testHTTPResponse() 함수를 호출하여 이 테스트를 완료합니다
3. 라우터 핸들러 만들기
우리는 handler.article.go 파일에 기사와 관련된 기능에 대한 모든 라우터 핸들러를 생성할 것입니다. 인덱스 페이지 핸들러 showIndexPage 는 다음의 작업을 수행하도록 합니다.
3-1. 문서 목록을 가져옵니다.
이 작업은 이전에 정의된 getAllArticles 함수를 사용하여 수행될 수 있습니다.
articles := getAllArticles()
3-2. 기사 리스트를 전달하는 index.html 템플릿을 렌더링합니다.
이 작업은 아래의 코드를 사용합니다.
c.HTML(
// ----- HTTP Status를 200(OK)로 설정합니다 ----- //
http.StatusOK,
// ------ index.html 템플릿을 사용합니다 ------ //
"index.html",
// ---- 페이지에서 사용하는 데이터를 전달합니다 ---- //
gin.H{
"title": "Home Page",
"payload": articles,
},
)
이전 단계의 버전과의 유일한 차이점은 payload라는 변수가 템플릿에 접근할 기사 리스트를 전달한다는 것입니다.
handlers.article.go 는 다음의 코드를 포함하여야 합니다.
// handlers.article.go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func showIndexPage(c *gin.Context) {
articles := getAllArticles()
// ---- Context의 HTML 메소드를 호출하여 템플릿을 렌더링합니다 ---- //
c.HTML(
// ----- HTTP Status를 200(OK)로 설정합니다 ------ //
http.StatusOK,
// ----- index.html 템플릿을 사용합니다 ------ //
"index.html",
// ---- 페이지에서 사용하는 데이터를 전달합니다 ----- //
gin.H{
"title": "Home Page",
"payload": articles,
},
)
}
이제 어플리케이션을 실행하고 브라우저에서 https://localhost:8080 에 접속하면 다음과 같은 화면을 볼 수 있습니다.
다음 시간에는 메인 페이지가 아닌 기사 하나하나의 링크에 맞는 경로 설정을 해보도록 할 예정입니다. 이번 포스팅에서는 많은 내용의 코딩이 필요하므로 실수하지 않도록 꼼꼼하게 체크해주는 것도 필요합니다!
'IT > Develop' 카테고리의 다른 글
[BOJ 1712번 손익분기점] Go Lang 문제풀이 (0) | 2021.09.03 |
---|---|
AWS 따라하기 - Promotion Credits 신청하기! (0) | 2021.09.01 |
Go Lang 웹 프레임워크 기초 - Gin 체험하기 (5) 라우터 초기화, 기사 목록 모델 테스트 (0) | 2021.08.21 |
Go Lang 웹 프레임워크 기초 - Gin 체험하기 (4) 템플릿 연결하기 (0) | 2021.08.20 |
Go Lang 웹 프레임워크 기초 - Gin 체험하기 (3) 기본 템플릿 작성 (0) | 2021.08.20 |