Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: example package에 mocking 예제 추가 #6

Merged
merged 1 commit into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .mockery.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
with-expecter: true
outpkg: mocks
packages:
github.com/cloud-club/Aviator-service/pkg:
# place your package-specific config here
config:
dir: "mocks/"
interfaces:
# select the interfaces you want mocked
ServerInterface:
# Modify package-level config for this specific interface (if applicable)
# Default
# filename: "mock_{{.InterfaceName}}.go"
# dir: "mocks/{{.PackagePath}}"
# mockname: "Mock{{.InterfaceName}}"
# outpkg: "{{.PackageName}}"
config:
github.com/cloud-club/Aviator-service/example:
# place your package-specific config here
config:
dir: "mocks/"
interfaces:
# select the interfaces you want mocked
ExampleServerInterface:
# Modify package-level config for this specific interface (if applicable)
# Default
# filename: "mock_{{.InterfaceName}}.go"
# dir: "mocks/{{.PackagePath}}"
# mockname: "Mock{{.InterfaceName}}"
# outpkg: "{{.PackageName}}"
config:
56 changes: 56 additions & 0 deletions example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Test 예시

이 폴더는 Test를 작성하는 예시를 보여주기 위한 폴더입니다. 참고해서 돌려보시고 본인 프로젝트에 맞게 수정하시면 됩니다. 저도 아직도 Mocking/Testing 이해가 어려워서 잘못된 부분이 있을 수도 있으니까 편하게 커멘트/수정 부탁드립니다.

## 테스트 라이브러리

- [Testify](https://github.com/stretchr/testify): assert, mock 용 라이브러리
- [mockery](https://vektra.github.io/mockery/latest/): mock 자동 생성 라이브러리

## example 구성

- `server.go`: `pkg`의 `Server`를 참고해 구현한 예시용 서버 및 CRUD 함수
- `request.go`: Request 구조체 값들을 하나의 string으로 만들어주는 함수(예시:`{"id": 1, "name": "test"}` -> `?id=1&name=test`)
- `main.go`: `server.go`의 서버를 실행하는 테스트용 `Main()` 함수. 실제로 테스트 해보려면 프로젝트 root 폴더의 `main.go` 에서 `example.Main()` 추가 후 실행.(현재 token/header 등이 설정되어 있지 않아서 404 리턴됨.)

### server_test.go 설명

- TestExampleSeverCreate: Mockery를 이용하여 `pkg`의 `Server`를 Mocking한 후 `Create()` 함수를 테스트하는 예시
- 총 두 가지의 테스트
- `Success - 성공`: 정상적인 request & response 테스트
- `Failed - 필수 파라미터 누락`: request 전송 시 필수 파라미터 누락 시 에러 테스트
- 각 Test code를 `t.Run()`으로 묶어서 테스트 실행 시 한 번에 실행되도록 함.
- `t.Run()`의 첫 번째 인자는 테스트 이름, 두 번째 인자는 테스트 함수
- `t.Run()`의 테스트 이름은 테스트 결과에 표시됨.
- error 값이 nil이 아닐 경우 `The error should be nil` 출력
- Response 값이 예상 값과 다를 경우 `The response should be equal` 출력

## `mocks`

- mock 자동 생성 라이브러리를 이용하여 생성한 mock 파일

## `.mockery.yml`

- mock 자동 생성 라이브러리 설정 파일
- example 패키지와 pkg ㅍ패키지의 모든 파일을 대상으로 mock 파일 생성

## 테스트 파일 구성법

- `brew install mockery`로 `mockery` 설치
- `.mockery.yml` 파일이 있는 root 폴더에서 명령어 실행

```bash
mockery
```

## 테스트 방법

```bash
go test -v ./...
```

### 테스트 커버리지 확인

```bash
go test -v ./... -cover
```
31 changes: 31 additions & 0 deletions example/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package example

import (
"fmt"
"log"

server "github.com/cloud-club/Aviator-service/types/server"
)

// main function
func Main() {
// Create MyServer
exampleServer := NewExampleServer()
myserver := NewMyServer(exampleServer)

myserver_response, myserver_err := myserver.Server.Create(
"https://ncloud.apigw.ntruss.com/vserver/v2/",
&server.CreateServerRequest{
ServerImageProductCode: "SW.VSVR.OS.LNX64.CNTOS.0703.B050",
VpcNo: "***04",
SubnetNo: "***43",
NetworkInterfaceOrder: 1,
},
)

if myserver_err != nil {
log.Fatalf("Failed to create server instance: %v", myserver_err)
}

fmt.Println(myserver_response)
}
32 changes: 32 additions & 0 deletions example/request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package example

import (
"fmt"
"net/url"
"reflect"
"strings"
)

func createRequestString(csr interface{}) string {
// Create string with each field name + value in request struct
v := url.Values{}
s := reflect.ValueOf(csr).Elem()

for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
name := strings.ToLower(s.Type().Field(i).Name)

switch f.Kind() {
case reflect.String, reflect.Int, reflect.Bool:
v.Add(name, fmt.Sprint(f.Interface()))
case reflect.Struct:
for j := 0; j < f.NumField(); j++ {
subName := fmt.Sprintf("%s.%d.%s", name, j+1, strings.ToLower(f.Type().Field(j).Name))
v.Add(subName, fmt.Sprint(f.Field(j).Interface()))
}
}
}

return "?" + v.Encode()

}
99 changes: 99 additions & 0 deletions example/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package example

import (
"errors"
"fmt"
"io"
"log"
"net/http"

server "github.com/cloud-club/Aviator-service/types/server"
)

type MyServer struct {
Server ExampleServerInterface
}

func NewMyServer(server ExampleServerInterface) *MyServer {
if server == nil {
panic("server is nil")
}
return &MyServer{Server: server}

}

func (ms *MyServer) CreateServerInstance(url string, request *server.CreateServerRequest) (*server.CreateServerResponse, error) {
if request == nil {
return nil, errors.New("request is nil")
}

response, err := ms.Server.Create(url, request)
fmt.Printf("MyServer.CreateServerInstance() called")
return response, err
}

type ExampleServer struct {
}

func NewExampleServer() ExampleServerInterface {
return &ExampleServer{}
}

type ExampleServerInterface interface {
Create(url string, request *server.CreateServerRequest) (*server.CreateServerResponse, error)
Get(url string) error
List(url string) error
Update(url string) error
Delete(url string) error
}

func (client *ExampleServer) Create(url string, request *server.CreateServerRequest) (*server.CreateServerResponse, error) {
// 테스트용으로 시각화하기 위해 짠 코드라서 실제로는 사용하지 않음!
// Header에 Content-Type & Token 값 추가했다고 가정
requestString := createRequestString(request)
response, err := http.Get(url + requestString)
if err != nil {
fmt.Print(err.Error())
return nil, err
}
defer response.Body.Close()

if response.StatusCode != http.StatusOK {
log.Fatal(response.Status)
return nil, err
}

responseByteData, err := io.ReadAll(response.Body)
if err != nil {
log.Fatal(err)
return nil, err
}

var csr server.CreateServerResponse
responseInterface, err := server.MapResponse(responseByteData, &csr)
if err != nil {
log.Fatal(err)
return nil, err
}

// interface{} 타입으로 변환된 responseInterface를 다시 CreateServerResponse 타입으로 변환
responseStruct := responseInterface.(*server.CreateServerResponse)

return responseStruct, err
}

func (client *ExampleServer) Get(url string) error {
return nil
}

func (client *ExampleServer) List(url string) error {
return nil
}

func (client *ExampleServer) Update(url string) error {
return nil
}

func (client *ExampleServer) Delete(url string) error {
return nil
}
94 changes: 94 additions & 0 deletions example/server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package example

import (
"testing"
"time"

"github.com/cloud-club/Aviator-service/mocks"
server "github.com/cloud-club/Aviator-service/types/server"
"github.com/stretchr/testify/assert"
)

func TestExampleServerCreate(t *testing.T) {
testCases := []struct {
testName string
url string
request *server.CreateServerRequest
response *server.CreateServerResponse
err error
}{
{
testName: "Success - 성공",
url: "https://ncloud.apigw.ntruss.com/vserver/v2/",
request: &server.CreateServerRequest{
ServerImageProductCode: "SW.VSVR.OS.LNX64.CNTOS.0703.B050",
VpcNo: "***04",
SubnetNo: "***43",
NetworkInterfaceOrder: 1,
},
response: &server.CreateServerResponse{
RequestId: "e7e7e7e7-7e7e-7e7e-7e7e-7e7e7e7e7e7e",
ReturnCode: 0,
ReturnMessage: "success",
TotalRows: 1,
ServerInstanceList: []server.ServerInstance{
{
ServerInstanceNo: "***4299",
ServerName: "test-***",
CpuCount: 2,
MemorySize: 4294967296,
PlatformType: server.CommonCode{Code: "LNX64", CodeName: "Linux 64 Bit"},
LoginKeyName: "test-***",
ServerInstanceStatus: server.CommonCode{Code: "INIT", CodeName: "Server initializing"},
ServerInstanceOperation: server.CommonCode{Code: "NULL", CodeName: "Server operation null"},
ServerInstanceStatusName: "init",
CreateDate: time.Time{},
Uptime: time.Time{},
ServerImageProductCode: "SW.VSVR.OS.LNX64.CNTOS.0703.B050",
ServerProductCode: "SVR.VSVR.STAND.C002.M004.NET.SSD.B050.G001",
IsProtectServerTermination: false,
ZoneCode: "KR-1",
RegionCode: "KR",
VpcNo: "***04",
SubnetNo: "***43",
NetworkInterfaceNoList: server.NetworkInterfaceNoList{},
ServerInstanceType: server.CommonCode{Code: "SVRSTAND", CodeName: "Server Standard"},
BaseBlockStorageDiskType: server.CommonCode{Code: "NET", CodeName: "Network Storage"},
BaseBlockStorageDiskDetailType: server.CommonCode{Code: "SSD", CodeName: "SSD"},
},
},
},
err: nil,
},
{
testName: "Failed - 필수 파라미터 누락",
url: "https://ncloud.apigw.ntruss.com/vserver/v2/",
request: &server.CreateServerRequest{
VpcNo: "***04",
SubnetNo: "***43",
},
response: &server.CreateServerResponse{
RequestId: "e7e7e7e7-7e7e-7e7e-7e7e-7e7e7e7e7e7e",
ReturnCode: 0,
ReturnMessage: "Failed",
},
err: nil,
},
}

for _, tc := range testCases {
t.Helper()
t.Run(tc.testName, func(t *testing.T) {
mockServer := &mocks.MockExampleServerInterface{}
mockServer.On("Create", tc.url, tc.request).
Return(tc.response, tc.err).
Once()

response, err := mockServer.Create("https://ncloud.apigw.ntruss.com/vserver/v2/", tc.request)

assert.Nil(t, err, "The error should be nil")
assert.Equal(t, tc.response, response, "The responses should be equal")
mockServer.AssertExpectations(t)
})
}
}
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ module github.com/cloud-club/Aviator-service
go 1.21.1

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/tools v0.15.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
17 changes: 17 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
Expand Down Expand Up @@ -33,3 +46,7 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading