diff --git a/kadai1/.gitkeep b/kadai1/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/kadai2/su-san/README.md b/kadai2/su-san/README.md new file mode 100644 index 0000000..6164df5 --- /dev/null +++ b/kadai2/su-san/README.md @@ -0,0 +1,53 @@ +# 課題2 + +## io.Readerとio.Writerについて調べてみよう +- 標準パッケージでどのように使われているか + - 今回使った標準imageパッケージでも使われている。 +https://github.com/golang/go/blob/75da700d0ae307ebfd4a3493b53e8f361c16f481/src/image/gif/reader.go#L218-L225 + + +- io.Readerとio.Writerがあることで + どういう利点があるのか具体例を挙げて考えてみる +https://github.com/golang/go/blob/75da700d0ae307ebfd4a3493b53e8f361c16f481/src/image/gif/reader.go#L218-L225 + +上記のように入力をio.Readerとすることでファイルだけでなく他の入力方法(カメラからのリアルタイムデータ、通信経由のデータなど)も同じメソッドで扱うことが +できる。このコードのように型判定はそのメソッドで行う。 + + +## テストを書いてみよう + +- 1回目の宿題のテストを作ってみて下さい + - [x] テストのしやすさを考えてリファクタリングしてみる + - 拡張子変換メソッドから拡張子が正しいかの判定とファイルの削除処理を外した + - [x] テストのカバレッジを取ってみる + - `go test -cover` とhtml出力やってみた + - [x] テーブル駆動テストを行う + - サブテスト化まで完了。すごくわかりやすい! + - [x] テストヘルパーを作ってみる + - 取ってつけたがまだメリットがよくわかっていない。別のメソッドを呼んでテストする際にエラー箇所がわかりやすくなる? + +## 疑問点 +- テストヘルパーのメリット +- ファイルが絡むテストのモック + +## カバレッジのとり方 + +`go test -cover` + +出力例 + +``` +PASS +coverage: 93.8% of statements +ok github.com/gopherdojo/dojo7/kadai2/su-san 0.127s +``` + +カバレッジの内容をhtmlで確認する方法 + +```bash +go test -coverprofile=cover.out +go tool cover -html=cover.out -o cover.html +``` + + + diff --git a/kadai2/su-san/cmd/imgconv/main.go b/kadai2/su-san/cmd/imgconv/main.go new file mode 100644 index 0000000..9413fcf --- /dev/null +++ b/kadai2/su-san/cmd/imgconv/main.go @@ -0,0 +1,112 @@ +package main + +import ( + "flag" + "fmt" + "os" + "path/filepath" + + "github.com/gopherdojo/dojo7/kadai2/su-san" +) + +func main() { + + inputExt := flag.String("i", "jpg", " extension to be converted ") + outputExt := flag.String("o", "png", " extension after conversion") + + // Usageメッセージ + flag.Usage = func() { + fmt.Fprintln(os.Stderr, "usage : cmd [-i] [-o] target_dir") + flag.PrintDefaults() + } + + flag.Parse() + args := flag.Args() + + if flag.NArg() == 0 { + flag.Usage() + return + } + + targetDir := args[0] + + // 変換対象のフォーマットと変換フォーマットが同じなら何もせず終了 + if *inputExt == *outputExt { + return + } + + convExts := image.NewConvExts(*inputExt, *outputExt) + if !convExts.SupportedFormats() { + fmt.Fprintf(os.Stderr, "unsupported format! please specify these format [png jpg jpeg gif]\n") + return + } + + var targetPaths []string + err := filepath.Walk(targetDir, func(path string, info os.FileInfo, err error) error { + if filepath.Ext(path) == ("." + *inputExt) { + targetPaths = append(targetPaths, path) + } + return nil + }) + + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + for _, p := range targetPaths { + + //var p Path = Path(p) + // 対象の拡張子出ない場合はスルーする + path := Path(p) + if !(path.IsConvesionTargetExt(convExts)) { + continue + } + + // 対象の拡張子でない場合はスルーする + if image.SupportedFormat(filepath.Ext(p)) == false { + fmt.Fprintln(os.Stderr, err) + continue + } + + f, err := os.Open(p) + // 読み込みエラーの場合はエラーを出してスルーする + if err != nil { + fmt.Fprintln(os.Stderr, err) + continue + } + + err = image.FmtConv(f, convExts) + if err != nil { + fmt.Fprintln(os.Stderr, err, " error filepath:", p) + os.Exit(1) + } + + // 成功ならば変換元を削除する + if err := os.Remove(p); err != nil { + fmt.Fprintln(os.Stderr, err, " error filepath:", p) + os.Exit(1) + } + + err = f.Close() + if err != nil { + fmt.Fprintln(os.Stderr, err, " error filepath:", p) + os.Exit(1) + } + } +} + +type Path string + +func (p *Path) IsConvesionTargetExt(c image.ConvExts) bool { + ext := filepath.Ext(string(*p)) + if len(ext) == 0 { + return false + } + + if ext[0] == '.' { + ext = ext[1:] + } + + return ext == c.InExt +} diff --git a/kadai2/su-san/cover.html b/kadai2/su-san/cover.html new file mode 100644 index 0000000..cf7d9d7 --- /dev/null +++ b/kadai2/su-san/cover.html @@ -0,0 +1,190 @@ + + + + + + + + +
+ +
+ not tracked + + not covered + covered + +
+
+
+ + + +
+ + + diff --git a/kadai2/su-san/image.go b/kadai2/su-san/image.go new file mode 100644 index 0000000..772cb93 --- /dev/null +++ b/kadai2/su-san/image.go @@ -0,0 +1,88 @@ +// Package image は画像のフォーマットを変換するためのパッケージです。 +package image + +import ( + "image" + "image/gif" + "image/jpeg" + "image/png" + "os" + "path/filepath" +) + +var supportedFormats = map[string]bool{"jpg": true, "jpeg": true, "png": true, "gif": true} + +func SupportedFormat(ext string) bool { + // ドット始まりの拡張子ならドットを削除する + if len(ext) > 0 && ext[0] == '.' { + ext = ext[1:] + } + _, ok := supportedFormats[ext] + return ok +} + +// ConvExts は変換対象のフォーマットと変換先のフォーマットを表す構造体です +type ConvExts struct { + InExt, OutExt string +} + +// SupportedFormats は指定フォーマットが対応しているか確認するメソッドです +func (c *ConvExts) SupportedFormats() bool { + return SupportedFormat(c.InExt) && SupportedFormat(c.OutExt) +} + +// NewConvExts は変換対象のフォーマットと変換先のフォーマットを表す構造体です +func NewConvExts(in, out string) ConvExts { + if in == "" { + in = "jpg" + } + + if out == "" { + out = "png" + } + return ConvExts{InExt: in, OutExt: out} +} + +// FmtConv は指定されたフォーマットからフォーマットへ変換する関数です +func FmtConv(f *os.File, exts ConvExts) (err error) { + + var img image.Image + var decodeErr error + + switch exts.InExt { + case "jpeg", "jpg": + img, decodeErr = jpeg.Decode(f) + case "png": + img, decodeErr = png.Decode(f) + case "gif": + img, decodeErr = gif.Decode(f) + } + + if decodeErr != nil { + return decodeErr + } + + path := f.Name() + pathWithoutExt := path[:len(path)-len(filepath.Ext(path))+1] + outputFile, err := os.Create(pathWithoutExt + exts.OutExt) + if err != nil { + return err + } + defer func() { + cerr := outputFile.Close() + if err == nil { + err = cerr + } + }() + + switch exts.OutExt { + case "jpeg", "jpg": + err = jpeg.Encode(outputFile, img, nil) + case "png": + err = png.Encode(outputFile, img) + case "gif": + err = gif.Encode(outputFile, img, nil) + } + + return +} diff --git a/kadai2/su-san/image_test.go b/kadai2/su-san/image_test.go new file mode 100644 index 0000000..d4ca3c6 --- /dev/null +++ b/kadai2/su-san/image_test.go @@ -0,0 +1,133 @@ +package image + +import ( + "os" + "testing" +) + +// +func TestSupportedFormat(t *testing.T){ + cases := []struct{name string; input string; expected bool}{ + {name: "jpg", input: "jpg", expected: true}, + {name: "jpeg", input: "jpeg", expected: true}, + {name: "png", input: "png", expected: true}, + {name: "gif", input: "gif", expected: true}, + {name: "GIF", input: "GIF", expected: false}, + {name: "no-ext", input: "", expected: false}, + {name: "dotpng", input: ".png", expected: true}, + {name: "mp4", input: "mp4", expected: false}, + } + + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T){ + t.Parallel() + if actual := SupportedFormat(c.input); c.expected != actual{ + t.Errorf( + "want supportedFormat(%s) = %v, got %v", + c.input, c.expected, actual) + } + }) + } +} + +func TestConvExts_SupportedFormats(t *testing.T){ + cases := []struct{name string; input ConvExts; expected bool}{ + {name: "jpg-jpg", input: ConvExts{"jpg", "jpg"}, expected: true}, + {name: "png-jpeg", input: ConvExts{"png", "jpeg"}, expected: true}, + {name: "jpg-no-ext", input: ConvExts{"jpg", ""}, expected: false}, + {name: "gif-png", input: ConvExts{"gif", "png"}, expected: true}, + {name: "gif-mp4", input: ConvExts{"gif", "mp4"}, expected: false}, + + } + + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T){ + t.Parallel() + if actual := c.input.SupportedFormats(); c.expected != actual{ + t.Errorf( + "want SupportedFormats(%s) = %v, got %v", + c.input, c.expected, actual) + } + }) + } +} + +func TestNewConvExts(t *testing.T) { + type inputs struct { + input, output string + } + + cases := []struct{name string; input inputs; expected ConvExts + }{ + {name: "jpg-png", input: inputs{"jpg", "png"}, expected: ConvExts{"jpg", "png"}}, + {name: "no-ext-jpeg", input: inputs{"", "jpeg"}, expected: ConvExts{"jpg", "jpeg"}}, + {name: "jpg-no-ext", input: inputs{"jpg", ""}, expected: ConvExts{"jpg", "png"}}, + {name: "no-ext-no-ext", input: inputs{"", ""}, expected: ConvExts{"jpg", "png"}}, + {name: "gif-mp4", input: inputs{"gif", "mp4"}, expected: ConvExts{"gif", "mp4"}}, + + } + + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T){ + t.Parallel() + if actual := NewConvExts(c.input.input, c.input.output); c.expected != actual{ + t.Errorf( + "want NewConvExts(%s) = %v, got %v", + c.input, c.expected, actual) + } + }) + } +} + +func TestFmtConv(t *testing.T) { + t.Helper() + // 前回作成したファイルは削除される + os.Remove("testdata/test_img/test_img1.png") + + type inputs struct { + path string; + convExts ConvExts + } + cases := []struct{name string; input inputs; expected string + }{ + { + name: "jpeg-png", + input: inputs{path:"testdata/test_img_1.jpeg", convExts:ConvExts{"jpeg", "png"}}, + expected: "testdata/test_img_1.png", + }, + { + name: "png-gif", + input: inputs{path:"testdata/test_img_2.png", convExts:ConvExts{"png", "gif"}}, + expected: "testdata/test_img_2.gif", + }, + { + name: "gif-jpg", + input: inputs{path:"testdata/test_img_3.gif", convExts:ConvExts{"gif", "jpg"}}, + expected: "testdata/test_img_3.jpg", + }, + } + + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T){ + t.Parallel() + // ファイルがある場合は削除する + os.Remove(c.expected) + f, err := os.Open(c.input.path) + if err != nil { + t.Errorf("cannnot open file: %v", c.input.path) + } + if err = FmtConv(f, c.input.convExts); err != nil { + t.Errorf("cannnot convert file: %v", c.input.path) + } + if _, err := os.Stat(c.expected); err != nil{ + t.Errorf( + "convert failed %v", c.input.path) + } + }) + } + +} diff --git a/kadai2/su-san/testdata/.DS_Store b/kadai2/su-san/testdata/.DS_Store new file mode 100644 index 0000000..fc04a35 Binary files /dev/null and b/kadai2/su-san/testdata/.DS_Store differ diff --git a/kadai2/su-san/testdata/test_img_1.jpeg b/kadai2/su-san/testdata/test_img_1.jpeg new file mode 100644 index 0000000..6324306 Binary files /dev/null and b/kadai2/su-san/testdata/test_img_1.jpeg differ diff --git a/kadai2/su-san/testdata/test_img_1.png b/kadai2/su-san/testdata/test_img_1.png new file mode 100644 index 0000000..188fae2 Binary files /dev/null and b/kadai2/su-san/testdata/test_img_1.png differ diff --git a/kadai2/su-san/testdata/test_img_2.gif b/kadai2/su-san/testdata/test_img_2.gif new file mode 100644 index 0000000..35cb75a Binary files /dev/null and b/kadai2/su-san/testdata/test_img_2.gif differ diff --git a/kadai2/su-san/testdata/test_img_2.png b/kadai2/su-san/testdata/test_img_2.png new file mode 100644 index 0000000..bc88cbd Binary files /dev/null and b/kadai2/su-san/testdata/test_img_2.png differ diff --git a/kadai2/su-san/testdata/test_img_3.gif b/kadai2/su-san/testdata/test_img_3.gif new file mode 100644 index 0000000..73b29eb Binary files /dev/null and b/kadai2/su-san/testdata/test_img_3.gif differ diff --git a/kadai2/su-san/testdata/test_img_3.jpg b/kadai2/su-san/testdata/test_img_3.jpg new file mode 100644 index 0000000..d4bc56e Binary files /dev/null and b/kadai2/su-san/testdata/test_img_3.jpg differ diff --git a/kadai2/su-san/testdata/test_img_4.jpg b/kadai2/su-san/testdata/test_img_4.jpg new file mode 100644 index 0000000..d4bc56e Binary files /dev/null and b/kadai2/su-san/testdata/test_img_4.jpg differ