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

Kadai4 misonog #57

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
16 changes: 16 additions & 0 deletions kadai4/misonog/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
/omikuji-server

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/
9 changes: 9 additions & 0 deletions kadai4/misonog/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
BINARY_NAME=omikuji-server

all: test build

build:
go build -o $(BINARY_NAME)

test:
go test -v ./...
43 changes: 43 additions & 0 deletions kadai4/misonog/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# おみくじ API

## 仕様

- JSON 形式でおみくじの結果を返す
- 正月(1/1-1/3)だけ大吉にする
- ハンドラのテストを書いてみる

## 利用方法

### setup

```shell
$ make # テスト & ビルド
```

### サーバーの起動

```shell
$ ./omikuji-server
```

### おみくじを引く

```shell
$ curl "http://127.0.0.1:8080/"
> {"result":"小吉"}
$ curl "http://127.0.0.1:8080/?date=2021-01-01"
> {"result":"大吉"}
```

## ディレクトリ構造

```shell
.
├── Makefile
├── README.md
├── go.mod
├── main.go
├── omikuji-server
├── omikuji.go
└── omikuji_test.go
```
3 changes: 3 additions & 0 deletions kadai4/misonog/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/misonog/gopherdojo-studyroom/kadai4/misonog

go 1.16
15 changes: 15 additions & 0 deletions kadai4/misonog/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package main

import (
"log"
"net/http"
)

func main() {
http.HandleFunc("/", handler)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これをmain関数の外に置いておくことでhttpのroutingも含めてテストできるようになります


err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal(err)
}
}
73 changes: 73 additions & 0 deletions kadai4/misonog/omikuji.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package main

import (
"encoding/json"
"log"
"math/rand"
"net/http"
"time"
)

const (
timeForm = "2006-1-2"
daikichi = "大吉"
)

var omikujiResults = []string{"吉", "小吉", "凶"}
Comment on lines +13 to +16
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

daikichi以外の値も定数に定義して、daikichiも含めてこのsliceに入れてしまう方が可変性、可読性(一覧性)ともに高いと思います。


type omikujiResult struct {
Result string `json:"result"`
}

type omikuji struct {
result string
date time.Time
}
Comment on lines +22 to +25
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

この2つのデータをstructとしてまとめて置く必要はありますか?


func handler(w http.ResponseWriter, r *http.Request) {
var o omikuji

dateValue := r.FormValue("date")
if dateValue != "" {
date, err := time.Parse(timeForm, dateValue)
if err != nil {
log.Fatal(err)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

この場合、httpのerrorとして返した方が良いと思います。特に、ユーザーが異常な値を入れたら発生する想定内なエラーだと思うので

}
o = omikuji{date: date}
} else {
o = omikuji{}
}

o.Draw()
data := &omikujiResult{Result: o.result}

w.Header().Set("Content-Type", "application/json; charset=utf-8")
if err := json.NewEncoder(w).Encode(data); err != nil {
log.Fatal(err)
}
}

func (o *omikuji) Draw() {
if isShogatsu(o.date) {
o.result = daikichi
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここでreturnしてしまえば、elseはいらないですね

} else {
rand.Seed(time.Now().UnixNano())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rand.Seedみたいな共通の初期化系の処理は、init functionで最初に実行させてしまうという方法があります

https://golang.org/doc/effective_go#init

i := rand.Intn(len(omikujiResults))
o.result = omikujiResults[i]
}
}

func isShogatsu(t time.Time) bool {
// 0001/1/1は正月と判定できない
if t.IsZero() {
return false
}

_, month, date := t.Date()
if month == time.January {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここがfalseの時点で return false すれば、ifをネストしなくてよくなりますね

if date == 1 || date == 2 || date == 3 {
return true
}
}
return false
}
70 changes: 70 additions & 0 deletions kadai4/misonog/omikuji_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package main

import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
)

func TestHandler(t *testing.T) {
cases := []struct {
name string
date string
fixedResult bool
expected string
}{
{name: "no date", date: ""},
{name: "12/31", date: "2020-12-31"},
{name: "shogatsu", date: "2021-01-01", fixedResult: true, expected: "大吉"},
{name: "1/4", date: "2021-01-04"},
}

for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/?date="+c.date, nil)
handler(w, r)
rw := w.Result()
defer rw.Body.Close()
if rw.StatusCode != http.StatusOK {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StatusCodeも期待値に入れると、異常系のテストもできます Bad Requestのときとかも含め

t.Fatal("unexpected status code")
}

var res omikujiResult
dec := json.NewDecoder(rw.Body)
if err := dec.Decode(&res); err != nil {
t.Fatal(err)
}
if c.fixedResult && c.expected != res.Result {
t.Errorf("want omikujiResult.Result = %v, got %v",
res.Result, c.expected)
}
})
}
}

func TestIsShogatsu(t *testing.T) {
cases := []struct {
name string
input time.Time
expected bool
}{
{name: "nil", expected: false},
{name: "shogatsu 1/1", input: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), expected: true},
{name: "shogatsu 1/2", input: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), expected: true},
{name: "not shogatsu", input: time.Date(2021, 1, 4, 0, 0, 0, 0, time.UTC), expected: false},
}

for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
if actual := isShogatsu(c.input); actual != c.expected {
t.Errorf("want isShogatsu(%v) = %v, got %v",
c.input, c.expected, actual)
}
})
}
}