지난번에 이어서myapp/app_test.go 파일에 테스트 코드를 추가적으로 만들어줍니다.
func TestFooHandler_WithoutJson(t *testing.T) { // 1
assert := assert.New(t)
res := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/foo", nil)
mux := NewHttpHandler()
mux.ServeHTTP(res, req)
assert.Equal(http.StatusOK, res.Code)
}
먼저 실패하는 코드를 만들어 줍니다.
1 : GET으로 /foo에 호출하는데 input없이 진행합니다.
그러면 response가 올텐데 StatusOK가 나와야 한다고 하고, 실제 결과를 넣어줍니다.
그 후 어떻게 될지 goconvey를 실행시켜 확인해봅시다.
실행 시 goconvey가 백그라운드에서 검사를 해줍니다.
기다리다보면 FAIL이 뜨는데 이것을 자세히 보면
원하는건 200을 원했는데 400번이(bad)나왔습니다.
그 이유는 fooHandler
의
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 Request: ", err)
return
}
user.CreatedAt = time.Now()
data, _ := json.Marshal(user)
w.Header().Add("content-type", "application/json")
w.WriteHeader(http.StatusCreated)
fmt.Fprint(w, string(data))
}
를 보게 되면 Body가 없을 경우에 Decode가 실패하게 되는데 그 때 error가 나고, fmt.Fprint(w, "Bad Request: ", err)를 반환하기 때문입니다. 그래서 StatusOK가 아니라 StatusBadRequest가 와야하는 것이죠.
func TestFooHandler_WithoutJson(t *testing.T) { // 1
assert := assert.New(t)
res := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/foo", nil)
mux := NewHttpHandler()
mux.ServeHTTP(res, req)
assert.Equal(http.StatusBadRequest, res.Code)
}
이 때 저장하면 goconvey가 돌게 되고, PASS했음을 알 수 있습니다.
이제 실제 JSON을 넣어서 테스트해봅시다!
func TestFooHandler_WithoutJson(t *testing.T) { // 1
assert := assert.New(t)
res := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/foo", nil)
mux := NewHttpHandler()
mux.ServeHTTP(res, req)
assert.Equal(http.StatusBadRequest, res.Code)
}
func TestFooHandler_WithJson(t *testing.T) {
assert := assert.New(t)
res := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/foo",
strings.NewReader(`{"first_name":"changbeom", "last_name":"song", "email":"changbeom@naver.com"}`)) // 1
mux := NewHttpHandler()
mux.ServeHTTP(res, req)
assert.Equal(http.StatusCreated, res.Code) // 2
user := new(User) // 3
err := json.NewDecoder(res.Body).Decode(user) // 4
assert.Nil(err) // 5
assert.Equal("changbeom", user.FirstName) // 6
assert.Equal("song", user.LastName) // 7
}
1 : JSON format인데, strings.NewReader()를 통해서 JSON format으로 작성한 string이 io.Reader로 바뀌어서 request보내 줄 수 있게 됩니다.
2 : 그렇게 되었을 때 response가 StatusCreated로 와야 합니다.
3 : 실제적으로 data가 제대로 왔는지 user변수를 만들어 줍니다.
4 : 그 후 response된 result를 user struct로 decode 해줍니다.
5 : 실패할 경우 error가 나오는데 그 error를 받아서 nil인지 아닌지 확인해주고,
6, 7 : FirstName과 LastName이 맞는지 확인합니다.
그 후 저장하여 PASS 인지 확인해줍니다.
소스는 app_test.go만 수정되었습니다.
myapp/app_test.go
package myapp
import (
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
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, _ := ioutil.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, _ := ioutil.ReadAll(res.Body)
assert.Equal("Hello World!", string(data))
}
func TestBarPathHandler_WithName(t *testing.T) {
assert := assert.New(t)
res := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/bar?name=changbeom", nil)
mux := NewHttpHandler()
mux.ServeHTTP(res, req)
assert.Equal(http.StatusOK, res.Code)
data, _ := ioutil.ReadAll(res.Body)
assert.Equal("Hello changbeom!", string(data))
}
func TestFooHandler_WithoutJson(t *testing.T) {
assert := assert.New(t)
res := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/foo", nil)
mux := NewHttpHandler()
mux.ServeHTTP(res, req)
assert.Equal(http.StatusBadRequest, res.Code)
}
func TestFooHandler_WithJson(t *testing.T) {
assert := assert.New(t)
res := httptest.NewRecorder()
req := httptest.NewRequest("POST", "/foo",
strings.NewReader(`{"first_name":"changbeom", "last_name":"song", "email":"changbeom@naver.com"}`))
mux := NewHttpHandler()
mux.ServeHTTP(res, req)
assert.Equal(http.StatusCreated, res.Code)
user := new(User)
err := json.NewDecoder(res.Body).Decode(user)
assert.Nil(err)
assert.Equal("changbeom", user.FirstName)
assert.Equal("song", user.LastName)
}
이제 FileUploadserver를 만들어 볼 것인데, public폴더를 만들어 준 뒤, main.go 파일을 작성합니다.
main.go
package main
import "net/http"
func main() {
http.Handle("/", http.FileServer(http.Dir("public")))
http.ListenAndServe(":3000", nil)
}
가장 고전적인 파일 웹서버를 만드는건데 해당 경로에 파일들을 access할 수 있는 서버들을 열어 주는 것입니다.
public폴더에 index.html이라는 파일을 만들어 줍니다.
<html>
<head>
<title>Go 로 만드는 웹 4</title>
</head>
<body>
<p><h1>파일을 전송해보자.</h1></p>
<form action="/uploads" method="POST" accept-charset="utf-8" enctype="multipart/form-data">
<p><input type="file" id="upload_file" name="upload_file"/></p>
<p><input type="submit" name="upload"/></p>
</form>
</body>
</html>
이후 저장 후에 서버를 실행 하면 위와 같은 화면이 뜨게 됩니다.
이 때 아무 파일이나 선택 후 submit을 클릭하면
404Page가 뜨는 것을 확인 할 수 있습니다.
uploadHandler를 만들지 않아서 생긴 일이기 때문에 만들어줍시다!
먼저 main.go파일을 수정해줍니다.
func uploadsHandler(w http.ResponseWriter, r *http.Request) { // 2
uploadFile, header, err := r.FormFile("upload_file")
if err != nil { // 3
w.WriteHeader(http.StatusBadRequest)
fmt.Fprint(w, err)
return
}
dirname := "./uploads" // 4
os.MkdirAll(dirname, 0777) // 5
filepath := fmt.Sprintf("%s/%s", dirname, header.Filename) // 6
file, err := os.Create(filepath) // 7
defer file.Close() // 8
if err != nil { // 9
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(w, err)
return
}
io.Copy(file, uploadFile) // 10
w.WriteHeader(http.StatusOK) // 11
fmt.Fprint(w, filepath) // 12
}
func main() {
http.HandleFunc("/uploads", uploadsHandler) // 1
http.Handle("/", http.FileServer(http.Dir("public")))
http.ListenAndServe(":3000", nil)
}
1 : uploadHander 등록.
2 : uploadHander 함수 생성.
전송된 파일은 request에 실려서 와서 그것을 읽어야 하는데 r.FormFile()이 inputFormFile형태로 날라온 값을 읽겠다는 의미입니다. 이 함수의 return값이 multipart.File, multipart.FileHeader, error가 나오고 인자값은 key값을 받는데 upload_file로 해줍니다.
3 : 에러가 있을 경우 처리.
4 : 'upload'된 파일을 저장해줄 폴더를 지정 -> 없으면 폴더를 새로 만들어 주어야 합니다.
5 : 디렉토리를 만들어주고 그 디렉토리의 권한을 777을 주어서 read, write, excute할 수 있게 합니다.
6 : filepath를 적어 줍니다.
7 : 이제 file을 만들어 주어야 하는데 filepath에 해당하는 file을 만들어 줍니다..
8 : file을 만들면 file의 Handle을 사용하는데 이 Handle이 OS자원이기 때문에 반납을 해주어야 합니다.
9 : 만약 file을 만들고, 에러가 생길 때의 처리.
10 : 파일을 제대로 upload했을 때 uploadFile변수에 있는 것을 file변수에 복사 해야하는데 그 때 사용하는 코드 입니다.
11 : 잘 되었기 때문에 OK 코드를 보내고,
12 : 어디에 업로드가 되었는지 filepath를 출력 시켜 줍니다.
이 후 실행을 하여 업로드가 잘 되는지 확인해봅시다.
이렇게 해서 File 전송하는 것을 마쳤고, TestCode를 만들어 봅시다.
main_test.go
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestUploadTest(t *testing.T) {
assert := assert.New(t) // 1
path := "C:/Users/tucker/Downloads/ex_image.png" // 2
file, _ := os.Open(path) // 3
defer file.Close() // 4
buf := &bytes.Buffer{} // 5
writer := multipart.NewWriter(buf) // 6
multi, err := writer.CreateFormFile("upload_file", filepath.Base(path)) // 7
assert.NoError(err) // 8
io.Copy(multi, file) // 9
writer.Close() // 10
res := httptest.NewRecorder()
req := httptest.NewRequest("POST", "/uploads", buf)
req.Header.Set("Content-type", writer.FormDataContentType()) // 11
uploadsHandler(res, req)
assert.Equal(http.StatusOK, res.Code)
}
1 : assert사용.
2 : file 경로 작성 합니다.
3 : 해당 file을 열어주는데 현재 파일 위치를 확인했기 때문에 error는 무시 해줍니다.
4 : 마찬가지로 닫아 줍니다.
5 : NewWriter에 io.writer로 넣어주기 위한 buf변수 입니다.
6 : 웹으로 파일을 전송할 때 MIME 포맷을 사용하는데, 이것을 하기위해 multipart.NewWriter()를 사용 합니다.
이 때 나오는 인스턴스가 wirter입니다.
7 : 그리고 이 writer에 CreateFormFile()을 사용하여 File을 만들어주는데,
fieldname을 upload_file, filename을 ex_image.png가 되는데 filepath.Base를 하게 되면 경로에서 filename만 잘라내줍니다.
이 함수에는 io.writer, error가 return됩니다.
8 : error가 있는지 확인하고,
9 : file을 읽었고, form파일을 만들었으니 데이터를 집어넣어주어야 한다. 아까 했던것처럼 카피 해줍니다.
10 : 그리고 writer를 닫아줍니다.
11 : 테스트 코드들을 만들고, 이 data가 어떤 data인지 알려주어야 server가 읽을 수 있기 때문에 conetent타입이 formdata임을 알려줍니다.
그 후 테스트를 실행하면 PASS 됐음을 알 수 있다.
그런데 좀 전에 받은 파일과 지금 받은 파일이 다른지 확인해보기 위해 지워 봅시다!
main_test.go
코드를 수정해봅시다!
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestUploadTest(t *testing.T) {
assert := assert.New(t)
path := "C:/Users/tucker/Downloads/ex_image.png"
file, _ := os.Open(path)
defer file.Close()
os.RemoveAll("./uploads") // 추가
buf := &bytes.Buffer{}
writer := multipart.NewWriter(buf)
multi, err := writer.CreateFormFile("upload_file", filepath.Base(path))
assert.NoError(err)
io.Copy(multi, file)
writer.Close()
res := httptest.NewRecorder()
req := httptest.NewRequest("POST", "/uploads", buf)
req.Header.Set("Content-type", writer.FormDataContentType())
uploadsHandler(res, req)
assert.Equal(http.StatusOK, res.Code)
}
저장하면 goconvey에서 PASS가 뜨게 되고, 수동으로 'upload'폴더에 있는 ex_image.png를 삭제 후 goconvey로 테스트를 해보면 PASS가 뜨고 다시 생성 되었음을 확인할 수 있습니다.
이제 업로드 되는 것은 확인했는데 실제로 이 파일이 같은지 확인해 봅시다!
main_test.go
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestUploadTest(t *testing.T) {
assert := assert.New(t)
path := "C:/Users/tucker/Downloads/ex_image.png"
file, _ := os.Open(path)
defer file.Close()
os.RemoveAll("./uploads")
buf := &bytes.Buffer{}
writer := multipart.NewWriter(buf)
multi, err := writer.CreateFormFile("upload_file", filepath.Base(path))
assert.NoError(err)
io.Copy(multi, file)
writer.Close()
res := httptest.NewRecorder()
req := httptest.NewRequest("POST", "/uploads", buf)
req.Header.Set("Content-type", writer.FormDataContentType())
uploadsHandler(res, req)
assert.Equal(http.StatusOK, res.Code)
uploadFilePath := "./uploads/" + filepath.Base(path) // 1
_, err = os.Stat(uploadFilePath) // 2
assert.NoError(err) // 3
uploadFile, _ := os.Open(uploadFilePath) // 4
originFile, _ := os.Open(path) // 5
defer uploadFile.Close() // 6
defer originFile.Close() // 7
uploadData := []byte{} // 8
originData := []byte{} // 9
uploadFile.Read(uploadData) // 10
originFile.Read(originData) // 11
assert.Equal(originData, uploadData) // 12
}
1 : 업로드 되는 경로를 넣어줍니다.
2 : 그 안에 파일이 잘 들어있는지 확인해줍니다.
os.Stat를 사용하면 그 file의 info를 가져다줍니다.
3 : 마찬가지로 에러가 없어야 하고,
4, 5 : 통과했으면 파일이 있다는 의미이므로, 업로드된 파일과 기존 파일과 확인해 보아야 합니다.
6, 7 : 두 파일을 닫아줍니다.
8, 9 : Read함수에 사용 될 byte array.
10, 11 : 위의 byte array를 사용하여 두 데이터를 읽어 옵니다.
12 : 그 후 이 두개의 데이터가 같은지 확인합니다.
저장 후 기다리면 PASS 된 것을 확인 할 수 있습니다.
풀소스
main.go
package main
import (
"fmt"
"net/http"
"os"
"io"
)
func uploadsHandler(w http.ResponseWriter, r *http.Request) {
uploadFile, header, err := r.FormFile("upload_file")
if err != nil {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprint(w, err)
return
}
defer uploadFile.Close()
dirname := "./uploads"
os.MkdirAll(dirname, 0777)
filepath := fmt.Sprintf("%s/%s", dirname, header.Filename)
file, err := os.Create(filepath)
defer file.Close()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(w, err)
return
}
io.Copy(file, uploadFile)
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, filepath)
}
func main() {
http.HandleFunc("/uploads", uploadsHandler)
http.Handle("/", http.FileServer(http.Dir("public")))
http.ListenAndServe(":3000", nil)
}
main_test.go
package main
import (
"bytes"
"io"
"mime/multipart"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestUploadTest(t *testing.T) {
assert := assert.New(t)
path := "C:/Users/ckdqj/Downloads/ex_image.png"
file, _ := os.Open(path)
defer file.Close()
os.RemoveAll("./uploads")
buf := &bytes.Buffer{}
writer := multipart.NewWriter(buf)
multi, err := writer.CreateFormFile("upload_file", filepath.Base(path))
assert.NoError(err)
io.Copy(multi, file)
writer.Close()
res := httptest.NewRecorder()
req := httptest.NewRequest("POST", "/uploads", buf)
req.Header.Set("Content-type", writer.FormDataContentType())
uploadsHandler(res, req)
assert.Equal(http.StatusOK, res.Code)
uploadFilePath := "./uploads/" + filepath.Base(path)
_, err = os.Stat(uploadFilePath)
assert.NoError(err)
uploadFile, _ := os.Open(uploadFilePath)
originFile, _ := os.Open(path)
defer uploadFile.Close()
defer originFile.Close()
uploadData := []byte{}
originData := []byte{}
uploadFile.Read(uploadData)
originFile.Read(originData)
assert.Equal(originData, uploadData)
}
public/index.html
<html>
<head>
<title>Go 로 만드는 웹 4</title>
</head>
<body>
<p><h1>파일을 전송해보자.</h1></p>
<form action="/uploads" method="POST" accept-charset="utf-8" enctype="multipart/form-data">
<p><input type="file" id="upload_file" name="upload_file"/></p>
<p><input type="submit" name="upload"/></p>
</form>
</body>
</html>
출처 : Turcker의 Golang
'프로그래밍(Web) > Golang' 카테고리의 다른 글
[바미] Go - RESTful API(POST) (0) | 2020.12.17 |
---|---|
[바미] Go - RESTful API(GET) (0) | 2020.12.16 |
[바미] Go - test환경 만들기 (0) | 2020.12.16 |
[바미] Go - 간단한 JSON Transfer 만들기. (0) | 2020.12.16 |
[바미] Go - 간단한 Web Handler 만들기 (0) | 2020.12.16 |