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

hokita / 課題2 #17

Open
wants to merge 9 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
52 changes: 52 additions & 0 deletions kadai2/hokita/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# 課題2
## io.Readerとio.Writer
### io.Readerとio.Writerについて調べてみよう
- io.Readerとio.Writerについて調べてみよう
- 標準パッケージでどのように使われているか
- io.Readerとio.Writerがあることでどういう利点があるのか具体例を挙げて考えてみる

### 解答
#### 標準パッケージでどのように使われているか
- strings
- 文字列操作
- bufio
- バッファリングしながら読み書きする
- bytes
- バイトスライスを操作する

#### io.Readerとio.Writerがあることでどういう利点があるのか具体例を挙げて考えてみる
IOが統一されているため、標準入出力、ファイル、ネットワーク通信どのような場合でも、入出力処理をする側、それを使う側がお互いの処理内容を知らなくてすみ、付け替えも可能。

例えばファイルを読み込んで処理Aをするアプリがあり、今回新しくHTTPレスポンスからA処理をする機能を追加したい場合、io.Readerを返すHTTPレスポンス読込処理を用意すれば、処理A自体を変更する必要はない。また処理Aのテストは入力を意識する必要もない。

## テストを書いてみよう
### 1回目の課題のテストを作ってみて下さい
- テストのしやすさを考えてリファクタリングしてみる
- テストのカバレッジを取ってみる
- テーブル駆動テストを行う
- テストヘルパーを作ってみる

### 対応
#### テストのしやすさを考えてリファクタリングしてみる
- `io.Readerとio.Writer`の課題を通して`(c *Converter) Execute(in io.Reader, out io.Writer)`を作成した。

#### テストのカバレッジを取ってみる
```shell
$ go test -coverprofile=profile github.com/gopherdojo/dojo8/kadai2/hokita/imgconv/
ok github.com/gopherdojo/dojo8/kadai2/hokita/imgconv 0.438s coverage: 79.5% o
f statements

$ go test -coverprofile=profile github.com/gopherdojo/dojo8/kadai2/hokita/imgconv/imgconv
ok github.com/gopherdojo/dojo8/kadai2/hokita/imgconv/imgconv 0.476s coverage:
95.7% of statements
```

#### テーブル駆動テストを行う
- `map[string]struct`で作ってみた。

#### テストヘルパーを作ってみる
- convertで作成されたファイル確認、削除処理をする`checkAndDeleteFile`を作成

## わからなかったこと、むずかしかったこと
- モックをしたいがためにinterfaceを無理やり作ることはあるのだろうか。
- だからと言って抽象化してモックしないとunitテストではなくE2Eテストになってしまいそう。
79 changes: 79 additions & 0 deletions kadai2/hokita/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package main

import (
"errors"
"os"
"path/filepath"

"github.com/gopherdojo/dojo8/kadai2/hokita/imgconv/imgconv"
)

func convert(dir, from, to string) error {
if err := validation(dir, from, to); err != nil {
return err
}

err := filepath.Walk(dir,
func(path string, info os.FileInfo, err error) (rerr error) {
if err != nil {
return err
}

fromImage, err := imgconv.NewImage("." + from)
if err != nil {
return err
}

toImage, err := imgconv.NewImage("." + to)
if err != nil {
return err
}

// ignore unrelated file
if !fromImage.Has(filepath.Ext(path)) {
return nil
}

file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()

out, err := os.Create(switchExt(path, "."+to))
if err != nil {
return err
}
defer func() {
if err := out.Close(); err != nil {
rerr = err
}
}()

converter := imgconv.NewConverter(toImage.GetEncoder())
return converter.Execute(file, out)
})
if err != nil {
return err
}

return nil

}

func switchExt(path, to string) string {
ext := filepath.Ext(path)
return path[:len(path)-len(ext)] + to
}

func validation(dir, from, to string) error {
if dir == "" {
return errors.New("please specify a directory")
}

if f, err := os.Stat(dir); os.IsNotExist(err) || !f.IsDir() {
return errors.New("cannot find directory")
}

return nil
}
3 changes: 3 additions & 0 deletions kadai2/hokita/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/gopherdojo/dojo8/kadai2/hokita/imgconv

go 1.14
10 changes: 10 additions & 0 deletions kadai2/hokita/imgconv/encoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package imgconv

import (
"image"
"io"
)

type Encoder interface {
execute(w io.Writer, Image image.Image) error
}
18 changes: 18 additions & 0 deletions kadai2/hokita/imgconv/helper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package imgconv

import (
"os"
"testing"
)

func checkAndDeleteFile(t *testing.T, file string) {
t.Helper()

if _, err := os.Stat(file); err != nil {
t.Errorf(`"%v" was not found`, file)
}

if err := os.Remove(file); err != nil {
t.Fatal(err)
}
}
24 changes: 24 additions & 0 deletions kadai2/hokita/imgconv/image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package imgconv

import (
"errors"
)

type Image interface {
GetEncoder() Encoder
Has(ext string) bool
}

func NewImage(ext string) (Image, error) {
var jpeg JPEG
var png PNG

switch {
case jpeg.Has(ext):
return &jpeg, nil
case png.Has(ext):
return &png, nil
}

return nil, errors.New("selected extension is not supported")
}
46 changes: 46 additions & 0 deletions kadai2/hokita/imgconv/image_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package imgconv

import (
"reflect"
"testing"
)

func TestNewImage(t *testing.T) {
tests := map[string]struct {
ext string
want Image
wantErrMsg string
}{
"jpg": {
ext: ".jpg",
want: &JPEG{},
},
"png": {
ext: ".png",
want: &PNG{},
},
"txt": {
ext: ".txt",
wantErrMsg: "selected extension is not supported",
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
got, err := NewImage(test.ext)
if !reflect.DeepEqual(got, test.want) {
t.Errorf(
`ext="%v" want="%v" got="%v"`,
test.ext, test.want, got,
)
}

if err != nil && err.Error() != test.wantErrMsg {
t.Errorf(
`ext="%v" wantErrMsg="%v" errMsg="%v"`,
test.ext, test.wantErrMsg, err.Error(),
)
}
})
}
}
23 changes: 23 additions & 0 deletions kadai2/hokita/imgconv/imgconv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package imgconv

import (
"image"
"io"
)

type Converter struct {
encoder Encoder
}

func NewConverter(encoder Encoder) *Converter {
return &Converter{encoder}
}

func (c *Converter) Execute(in io.Reader, out io.Writer) error {
img, _, err := image.Decode(in)
if err != nil {
return err
}

return c.encoder.execute(out, img)
}
30 changes: 30 additions & 0 deletions kadai2/hokita/imgconv/imgconv_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package imgconv

import (
"bytes"
"image"
"io"
"os"
"testing"
)

type mockEncoder struct{}

func (e *mockEncoder) execute(w io.Writer, Image image.Image) error {
return nil
}

func TestConverter_Execute(t *testing.T) {
file, err := os.Open("../testdata/test2/gopher.jpg")
if err != nil {
t.Fatal(err)
}
stdout := new(bytes.Buffer)

converter := NewConverter(&mockEncoder{})

err = converter.Execute(file, stdout)
if err != nil {
t.Errorf("failed to call Execute(): %s", err)
}
}
31 changes: 31 additions & 0 deletions kadai2/hokita/imgconv/jpeg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package imgconv

import (
"image"
"image/jpeg"
"io"
)

type JPEG struct{}

func (*JPEG) GetEncoder() Encoder {
return &JPEGEncoder{}
}

func (*JPEG) Has(ext string) bool {
var jpegExt = map[string]bool{
".jpg": true,
".jpeg": true,
".JPG": true,
".JPEG": true,
}

return jpegExt[ext]
}

type JPEGEncoder struct{}

func (*JPEGEncoder) execute(w io.Writer, Image image.Image) error {
err := jpeg.Encode(w, Image, &jpeg.Options{Quality: jpeg.DefaultQuality})
return err
}
55 changes: 55 additions & 0 deletions kadai2/hokita/imgconv/jpeg_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package imgconv

import (
"image"
"os"
"reflect"
"testing"
)

func TestJPEG_GetEncoder(t *testing.T) {
test := struct {
want Encoder
}{
want: &JPEGEncoder{},
}

var jpeg JPEG
got := jpeg.GetEncoder()
if !reflect.DeepEqual(got, test.want) {
t.Errorf(`want="%v" got="%v"`, test.want, got)
}
}

func TestJpegEncoder_execute(t *testing.T) {
inFile := "../testdata/test3/gopher.png"
outFile := "../testdata/test3/gopher.jpg"

file, err := os.Open(inFile)
if err != nil {
t.Fatal(err)
}

out, err := os.Create(outFile)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := out.Close(); err != nil {
t.Fatal(err)
}
}()

img, _, err := image.Decode(file)
if err != nil {
t.Fatal(err)
}

var encoder JPEGEncoder
err = encoder.execute(out, img)
if err != nil {
t.Errorf("failed to call execute(): %v", err)
}

checkAndDeleteFile(t, outFile)
}
Loading