diff --git a/kadai2/tsuchinaga/.gitignore b/kadai2/tsuchinaga/.gitignore new file mode 100644 index 0000000..e6e6285 --- /dev/null +++ b/kadai2/tsuchinaga/.gitignore @@ -0,0 +1,6 @@ +## for IntelliJ +.idea +*.iml + +testdata/**/*.converted.* +cover.out diff --git a/kadai2/tsuchinaga/README.md b/kadai2/tsuchinaga/README.md new file mode 100644 index 0000000..f814726 --- /dev/null +++ b/kadai2/tsuchinaga/README.md @@ -0,0 +1,100 @@ +# 課題2 tsuchinaga + +## 課題 +スライドより転載 + +* io.Readerとio.Writerについて調べてみよう + * 標準パッケージでどのように使われているか + * io.Readerとio.Writerがあることで + どういう利点があるのか具体例を挙げて考えてみる +* 1回目の課題のテストを作ってみて下さい + * テストのしやすさを考えてリファクタリングしてみる + * テストのカバレッジを取ってみる + * テーブル駆動テストを行う + * テストヘルパーを作ってみる + + +### io.Readerとio.Writerについて調べてみよう + +`io.Reader` と `io.Writer` を実装している代表的なのはやはり `os.File` だと思う。 + +`os.File` には `os.Stdin` 、 `os.Stdout` 、 `os.Stderr` がある。 +これらはそれぞれ、標準入力、標準出力、標準エラーにあたり、誰もが入出力先にしたことがあると思う。 + +例えば、「出力先を標準出力からログファイルにかえたいなー」というときに、 +`io.Writer` がなければ新たに出力用の処理を用意して上げる必要があったり、 +標準ライブラリが `io.Writer` を出力先に指定していなければ、標準ライブラリと同じことをするのに自前で用意する必要があったりする。 +さらに、「標準出力とログファイルの両方に出したいなー」みたいなときに、それぞれを同じように扱えるようにしていないと処理を作ることも難しい。 + +読み込みの代表例として、 `bufio.Scanner` をあげる。 + +競技プログラミングで大きな入力を受け取るときや、サイズの大きいファイルを読み込むときにお世話になる。 +※ 他にも1単語ずつじゃなくて、1行ずつ読むときとかにも重宝する + +この `Scanner` を生成する関数である `NewScanner` は下記のようになっている。 +```go +// NewScanner returns a new Scanner to read from r. +// The split function defaults to ScanLines. +func NewScanner(r io.Reader) *Scanner { + return &Scanner{ + r: r, + split: ScanLines, + maxTokenSize: MaxScanTokenSize, + } +} +``` +引数が `io.Reader` となっている。 +これは標準入力だろうが、ファイルだろうが、HTTPリクエストのBodyだろうが、なんでもかんでも同じように扱える。 + +同じような処理をいっぱい作らなくていい以外に、モックを作りやすいというメリットもある。(interfaceのメリットかも) + +テストコードを書いていると、ここなんか適当なもので置き換えたいなーというのは頻繁にある。 +そんな時に構造体に依存していたりすると置き換えることができず、密結合が起きてしまい、テストしづらくなる。 +そこをinterfaceに依存するようにしていると、置き換えることが容易になる。 + + +### 1回目の課題のテストを作ってみて下さい + +リファクタリングの域をこえてるきもするけど、いつもやってるようにTODO出してテストを作っていく +課題1の時よりも責務を意識してTODOを分ける + +* [x] ディレクトリが指定でき、その配下のディレクトリとファイルを再帰的に探索し、変換前として指定されたフォーマットの画像ファイルを変換後として指定されたフォーマットの画像ファイルに変換する + * [x] コマンドのパラメータを指定する + * [x] ベースのディレクトリを指定する + * [x] 必須 + * [x] 変換前のファイルフォーマットを指定する + * [x] 任意 + * [x] デフォルトjpeg + * [x] 許容されるのはjpeg/png + * [x] 変換後のファイルフォーマットを指定する + * [x] 任意 + * [x] デフォルトpng + * [x] 許容されるのはjpeg/png + * [x] 変換前と同じフォーマットの指定は不可 + * [x] 指定されたディレクトリの配下のディレクトリとファイルの一覧を取得する + * [x] 配下にディレクトリがあればさらに読み込めるようにする + * [x] 指定されたパスがディレクトリでなければ中止 + * [x] ディレクトリかを判断する + * [x] ファイルの画像フォーマットを取得する + * [x] ファイルが画像なら画像フォーマットを取得する + * [x] ファイルが画像でなければフォーマットは取得できないので中止 + * [x] ファイルフォーマットを変換する + + +#### カバレッジ +```bash +$ go tool cover -func=cover.out +github.com/gopherdojo/dojo8/kadai2/tsuchinaga/imgconv/imgconv.go:27: NewIMGConverter 100.0% +github.com/gopherdojo/dojo8/kadai2/tsuchinaga/imgconv/imgconv.go:42: Do 93.3% +github.com/gopherdojo/dojo8/kadai2/tsuchinaga/imgconv/imgconv.go:74: NewConverter 100.0% +github.com/gopherdojo/dojo8/kadai2/tsuchinaga/imgconv/imgconv.go:90: IsDir 100.0% +github.com/gopherdojo/dojo8/kadai2/tsuchinaga/imgconv/imgconv.go:99: GetIMGType 90.0% +github.com/gopherdojo/dojo8/kadai2/tsuchinaga/imgconv/imgconv.go:118: DirFileList 100.0% +github.com/gopherdojo/dojo8/kadai2/tsuchinaga/imgconv/imgconv.go:137: Convert 69.2% +github.com/gopherdojo/dojo8/kadai2/tsuchinaga/validation/validation.go:4: NewValidator 100.0% +github.com/gopherdojo/dojo8/kadai2/tsuchinaga/validation/validation.go:19: IsValidDir 100.0% +github.com/gopherdojo/dojo8/kadai2/tsuchinaga/validation/validation.go:26: IsValidFileType 100.0% +github.com/gopherdojo/dojo8/kadai2/tsuchinaga/validation/validation.go:31: IsValidSrc 100.0% +github.com/gopherdojo/dojo8/kadai2/tsuchinaga/validation/validation.go:36: IsValidDest 100.0% +total: (statements) 86.1% +``` diff --git a/kadai2/tsuchinaga/go.mod b/kadai2/tsuchinaga/go.mod new file mode 100644 index 0000000..f5adc6d --- /dev/null +++ b/kadai2/tsuchinaga/go.mod @@ -0,0 +1,5 @@ +module github.com/gopherdojo/dojo8/kadai2/tsuchinaga + +go 1.14 + +require golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 diff --git a/kadai2/tsuchinaga/go.sum b/kadai2/tsuchinaga/go.sum new file mode 100644 index 0000000..3ab73ea --- /dev/null +++ b/kadai2/tsuchinaga/go.sum @@ -0,0 +1,2 @@ +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/kadai2/tsuchinaga/imgconv/imgconv.go b/kadai2/tsuchinaga/imgconv/imgconv.go new file mode 100644 index 0000000..fc98492 --- /dev/null +++ b/kadai2/tsuchinaga/imgconv/imgconv.go @@ -0,0 +1,181 @@ +package imgconv + +import ( + "fmt" + "image" + "image/jpeg" + "image/png" + "io/ioutil" + "os" + "path" + + "golang.org/x/xerrors" +) + +var ( + ReadDirError = xerrors.New("read dir error") + FileStatError = xerrors.New("file stat error") + OpenFileError = xerrors.New("open file error") + CloseFileError = xerrors.New("close file error") + NotImageError = xerrors.New("not image error") + ReadImageError = xerrors.New("read image error") + CreateDestinationFileError = xerrors.New("create destination file error") + EncodeImageError = xerrors.New("encode image error") +) + +// NewIMGConverter - 新しい画像変換の生成 +func NewIMGConverter() IMGConverter { + return &imgConverter{converter: NewConverter()} // converterを外からもらってもいいけど、使い方は決まってるので決め打ちで +} + +// IMGConverter - 画像変換のユースケースのインターフェース +type IMGConverter interface { + Do(path, src, dest string) chan error +} + +// imgConverter - 画像変換のユースケース +type imgConverter struct { + converter Converter +} + +// Do - 指定されたディレクトリから再帰的にたどりながらファイルを変換する +func (c *imgConverter) Do(path, src, dest string) chan error { + ch := make(chan error) + + go func() { + defer close(ch) + + dirs, files, err := c.converter.DirFileList(path) + if err != nil { + ch <- err + return + } + + // 非同期で再帰的にディレクトリをたどる + for _, dir := range dirs { + dch := c.Do(dir, src, dest) + for err := range dch { + ch <- err + } + } + + // ファイルを変換する + for _, file := range files { + if err := c.converter.Convert(file, src, dest); err != nil { + ch <- err + } + } + }() + + return ch +} + +// NewConverter - 新しいConverterを生成する +func NewConverter() Converter { + return &converter{} +} + +// converter - 変換処理とそれに付随する処理をもつサービスのインターフェース +type Converter interface { + IsDir(path string) (bool, error) + GetIMGType(path string) (_ string, err error) + DirFileList(filePath string) ([]string, []string, error) + Convert(filePath string, src string, dest string) (err error) +} + +// converter - 変換処理とそれに付随する処理をもつサービス +type converter struct{} + +// IsDir - pathがディレクトリかどうか +func (c converter) IsDir(path string) (bool, error) { + fi, err := os.Stat(path) + if err != nil { + return false, xerrors.Errorf("%+v: %w", err, FileStatError) + } + return fi.IsDir(), nil +} + +// GetIMGType - 引数のファイルの画像フォーマットを取得する +func (c converter) GetIMGType(path string) (_ string, err error) { + f, err := os.Open(path) + if err != nil { // 開けない + return "", OpenFileError + } + defer func() { + if closeErr := f.Close(); closeErr != nil { + err = xerrors.Errorf("%+v: %w", closeErr, CloseFileError) + } + }() + + _, format, err := image.DecodeConfig(f) + if err != nil { // 画像じゃない + return "", NotImageError + } + return format, nil +} + +// DirFileList - 引数直下のディレクトリとファイルの配列を返す +func (c converter) DirFileList(filePath string) ([]string, []string, error) { + infos, err := ioutil.ReadDir(filePath) + if err != nil { + return nil, nil, xerrors.Errorf("%+v(filePath: %s): %w", err, filePath, ReadDirError) + } + + dirs, files := make([]string, 0), make([]string, 0) + for _, info := range infos { + jp := path.Join(filePath, info.Name()) + if info.IsDir() { + dirs = append(dirs, jp) + } else { + files = append(files, jp) + } + } + return dirs, files, nil +} + +// Convert - 画像変換 +func (c converter) Convert(filePath string, src string, dest string) (err error) { + if ft, err := c.GetIMGType(filePath); err != nil { + return err + } else if ft != src { + return nil + } + + f, err := os.Open(filePath) + if err != nil { // 開けない + return xerrors.Errorf("%+v: %w", err, OpenFileError) + } + defer func() { + if closeErr := f.Close(); closeErr != nil { + err = xerrors.Errorf("%+v: %w", closeErr, CloseFileError) + } + }() + + img, _, err := image.Decode(f) + if err != nil { + return xerrors.Errorf("%+v: %w", err, ReadImageError) + } + + newFilePath := fmt.Sprintf("%s.%s.%s", filePath, "converted", dest) + o, err := os.Create(newFilePath) + if err != nil { + return xerrors.Errorf("%+v: %w", err, CreateDestinationFileError) + } + defer func() { + if closeErr := o.Close(); closeErr != nil { + err = xerrors.Errorf("%+v: %w", closeErr, CloseFileError) + } + }() + + switch dest { + case "jpeg": + if err = jpeg.Encode(o, img, nil); err != nil { + return xerrors.Errorf("%+v: %w", err, EncodeImageError) + } + case "png": + if err = png.Encode(o, img); err != nil { + return xerrors.Errorf("%+v: %w", err, EncodeImageError) + } + } + return nil +} diff --git a/kadai2/tsuchinaga/imgconv/imgconv_test.go b/kadai2/tsuchinaga/imgconv/imgconv_test.go new file mode 100644 index 0000000..6e05bbc --- /dev/null +++ b/kadai2/tsuchinaga/imgconv/imgconv_test.go @@ -0,0 +1,272 @@ +package imgconv + +import ( + "errors" + "os" + "path/filepath" + "reflect" + "testing" +) + +func init() { + wd, err := os.Getwd() + if err != nil { + panic(err) + } + if "imgconv" == filepath.Base(wd) { + if err := os.Chdir(".."); err != nil { + panic(err) + } + } +} + +func TestConverter_IsDir(t *testing.T) { + t.Parallel() + testTable := []struct { + desc string + arg string + expect1 bool + expect2 error + }{ + {desc: "ディレクトリを指定したらtrue", arg: "./testdata", expect1: true}, + {desc: "ファイルを指定したらfalse", arg: "./testdata/Example.jpg", expect1: false}, + {desc: "存在しないパスを指定したらエラー", arg: "./NotFound.jpg", expect2: FileStatError}, + {desc: "空文字を指定したらエラー", arg: "", expect2: FileStatError}, + // {desc: "不良セクタを指定したらfalse", arg1: "", expect1: false}, // やり方わからない + } + + for _, test := range testTable { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + c := &converter{} + actual1, actual2 := c.IsDir(test.arg) + if test.expect1 != actual1 || !errors.Is(actual2, test.expect2) { + t.Errorf("%s 失敗\n期待: %v, %v\n実際: %v, %v\n", t.Name(), test.expect1, test.expect2, actual1, actual2) + } + }) + } +} + +func TestConverter_GetIMGType(t *testing.T) { + t.Parallel() + testTable := []struct { + desc string + arg string + expect1 string + expect2 error + }{ + {desc: "jpegファイルを指定したらjpegが返される", arg: "./testdata/Example.jpg", expect1: "jpeg"}, + {desc: "pngファイルを指定したらpngが返される", arg: "./testdata/Example.png", expect1: "png"}, + {desc: "テキストファイルを指定したらエラー", arg: "./testdata/Example.txt", expect2: NotImageError}, + {desc: "画像ではないバイナリファイルを指定したらエラー", arg: "./testdata/Example.xlsx", expect2: NotImageError}, + {desc: "存在しないパスを指定したらエラー", arg: "./foo/bar", expect2: OpenFileError}, + } + + for _, test := range testTable { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + c := &converter{} + actual1, actual2 := c.GetIMGType(test.arg) + if test.expect1 != actual1 || !errors.Is(actual2, test.expect2) { + t.Errorf("%s 失敗\n期待: %v, %v\n実際: %v, %v\n", t.Name(), test.expect1, test.expect2, actual1, actual2) + } + }) + } +} + +func TestConverter_DirFileList(t *testing.T) { + t.Parallel() + testTable := []struct { + desc string + arg string + expect1 []string + expect2 []string + expect3 error + }{ + {desc: "存在しないパスを指定したらエラー", arg: "", expect3: ReadDirError}, + {desc: "存在するパスを指定したら直下にあるディレクトリとファイルが返される", + arg: "./testdata", + expect1: []string{"testdata/subdir1", "testdata/subdir2"}, + expect2: []string{"testdata/Example.jpg", "testdata/Example.png", "testdata/Example.txt", "testdata/Example.xlsx"}}, + } + + for _, test := range testTable { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + c := &converter{} + actual1, actual2, actual3 := c.DirFileList(test.arg) + if !reflect.DeepEqual(test.expect1, actual1) || !reflect.DeepEqual(test.expect2, actual2) || !errors.Is(actual3, test.expect3) { + t.Errorf("%s 失敗\n期待: %+v, %+v, %+v\n実際: %+v, %+v, %+v\n", t.Name(), + test.expect1, test.expect2, test.expect3, actual1, actual2, actual3) + } + }) + } +} + +func TestConverter_Convert(t *testing.T) { + t.Parallel() + testTable := []struct { + desc string + arg1 string + arg2 string + arg3 string + expect error + }{ + {desc: "存在しないパスを指定したらエラー", arg1: "foo/bar", expect: OpenFileError}, + {desc: "画像じゃないファイルを指定したらエラー", arg1: "testdata/Example.txt", expect: NotImageError}, + {desc: "srcに指定した画像形式と違う画像形式なら何もせず終了", arg1: "testdata/Example.jpg", arg2: "png", expect: nil}, + // {desc: "Decodeできない画像ならエラー", arg1: "", expect: ReadImageError}, // 壊れた画像とか作ればいける? + // {desc: "空っぽの変換後のファイルを生成出来なかったらエラー", arg1: "", expect: CreateDestinationFileError}, // 吐き出し先にロックされたファイルを作ればいける? + // {desc: "jpgへの画像の変換に失敗したらエラー", arg1: "", expect: EncodeImageError}, // どうすれば失敗させられるのか分からない + // {desc: "pngへの画像の変換に失敗したらエラー", arg1: "", expect: EncodeImageError}, // どうすれば失敗させられるのか分からない + {desc: "正常に終了すればエラーなく終了", arg1: "testdata/subdir1/Example.jpg", arg2: "jpeg", arg3: "png", expect: nil}, + } + + for _, test := range testTable { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + c := &converter{} + actual := c.Convert(test.arg1, test.arg2, test.arg3) + if !errors.Is(actual, test.expect) { + t.Errorf("%s 失敗\n期待: %+v\n実際: %+v\n", t.Name(), test.expect, actual) + } + }) + } +} + +func TestNewConverter(t *testing.T) { + expect := &converter{} + actual := NewConverter() + if !reflect.DeepEqual(expect, actual) { + t.Errorf("%s 失敗\n期待: %+v\n実際: %+v\n", t.Name(), expect, actual) + } +} + +func TestNewIMGConverter(t *testing.T) { + expect := &imgConverter{converter: NewConverter()} + actual := NewIMGConverter() + if !reflect.DeepEqual(expect, actual) { + t.Errorf("%s 失敗\n期待: %+v\n実際: %+v\n", t.Name(), expect, actual) + } +} + +type testConverter struct { + dirFileListReturn map[int]struct { + ret1 []string + ret2 []string + ret3 error + } + dirFileListCount int + convertReturn map[int]error + convertCount int +} + +func (t testConverter) IsDir(string) (bool, error) { + panic("implement me") +} + +func (t testConverter) GetIMGType(string) (_ string, err error) { + panic("implement me") +} + +func (t *testConverter) DirFileList(string) ([]string, []string, error) { + rs := t.dirFileListReturn[t.dirFileListCount] + t.dirFileListCount++ + return rs.ret1, rs.ret2, rs.ret3 +} + +func (t *testConverter) Convert(string, string, string) (err error) { + r := t.convertReturn[t.convertCount] + t.convertCount++ + return r +} + +func TestImgConverter_Do(t *testing.T) { + t.Parallel() + testTable := []struct { + desc string + dirFileListReturn map[int]struct { + ret1 []string + ret2 []string + ret3 error + } + convertReturn map[int]error + expectDirFileListCount int + expectConvertCount int + expectErrorCount int + }{ + {desc: "dirが読み取れなければエラーを1回返して終わり", + dirFileListReturn: map[int]struct { + ret1 []string + ret2 []string + ret3 error + }{0: {ret3: ReadDirError}}, + expectDirFileListCount: 1, expectConvertCount: 0, expectErrorCount: 1, + }, + {desc: "dirが3段あれば3回実行する", + dirFileListReturn: map[int]struct { + ret1 []string + ret2 []string + ret3 error + }{0: {ret1: []string{"subdir1"}, ret2: []string{}}, + 1: {ret1: []string{"subdir2"}, ret2: []string{}}, + 2: {ret1: []string{}, ret2: []string{}}}, + expectDirFileListCount: 3, expectConvertCount: 0, expectErrorCount: 0, + }, + {desc: "fileの数だけconvertが叩かれる", + dirFileListReturn: map[int]struct { + ret1 []string + ret2 []string + ret3 error + }{0: {ret1: []string{}, ret2: []string{"file1", "file2", "file3", "file4"}}}, + expectDirFileListCount: 1, expectConvertCount: 4, expectErrorCount: 0, + }, + {desc: "fileの変換に失敗したらエラーが返される", + dirFileListReturn: map[int]struct { + ret1 []string + ret2 []string + ret3 error + }{0: {ret1: []string{}, ret2: []string{"file1", "file2", "file3", "file4"}}}, + convertReturn: map[int]error{0: ReadImageError, 2: ReadImageError}, + expectDirFileListCount: 1, expectConvertCount: 4, expectErrorCount: 2, + }, + {desc: "エラーなくdirもfileもなければ何もせずに抜ける", + dirFileListReturn: map[int]struct { + ret1 []string + ret2 []string + ret3 error + }{0: {ret1: []string{}, ret2: []string{}}}, + expectDirFileListCount: 1, expectConvertCount: 0, expectErrorCount: 0, + }, + } + + for _, test := range testTable { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + testConverter := &testConverter{ + dirFileListReturn: test.dirFileListReturn, + convertReturn: test.convertReturn, + } + imgConverter := &imgConverter{converter: testConverter} + + var errCount int + ch := imgConverter.Do("", "", "") + for range ch { + errCount++ + } + + if test.expectDirFileListCount != testConverter.dirFileListCount || + test.expectConvertCount != testConverter.convertCount || + test.expectErrorCount != errCount { + t.Errorf("%s 失敗\n期待: %d, %d, %d\n実際: %d, %d, %d\n", t.Name(), + test.expectDirFileListCount, test.expectConvertCount, test.expectErrorCount, + testConverter.dirFileListCount, testConverter.convertCount, errCount) + } + }) + } +} diff --git a/kadai2/tsuchinaga/main.go b/kadai2/tsuchinaga/main.go new file mode 100644 index 0000000..a055e27 --- /dev/null +++ b/kadai2/tsuchinaga/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "flag" + "log" + "os" + + "github.com/gopherdojo/dojo8/kadai2/tsuchinaga/imgconv" + + "github.com/gopherdojo/dojo8/kadai2/tsuchinaga/validation" +) + +var ( + stderr Logger = log.New(os.Stderr, "", log.LstdFlags|log.Lmicroseconds) + validator = validation.NewValidator() + imgConverter = imgconv.NewIMGConverter() +) + +func main() { + // 入力値受け取り + var dir, src, dest string // ベースディレクトリ、変換元画像形式、変換後画像形式 + flag.StringVar(&dir, "dir", "", "変換する画像のあるディレクトリ") + flag.StringVar(&src, "src", "jpeg", "optional 変換元の画像形式 jpeg|png") + flag.StringVar(&dest, "dest", "png", "optional 変換後の画像形式 jpeg|png") + flag.Parse() + + // validatorで入力値チェック + switch { + case !validator.IsValidDir(dir): + stderr.Println("dirの指定は必須です") + os.Exit(2) + case !validator.IsValidSrc(src): + stderr.Println("srcに有効な画像形式を指定してください") + os.Exit(2) + case !validator.IsValidDest(dest, src): + stderr.Println("destに有効な画像形式を指定してください。また、srcとdestは一致しないよう指定してください") + os.Exit(2) + } + + // converterで変換の実行 + for err := range imgConverter.Do(dir, src, dest) { + switch err { + case imgconv.NotImageError: // 画像じゃないファイルも存在しうるので無視する + default: + stderr.Println(err) + } + + } +} + +// Logger - ログ出力のインターフェース +type Logger interface { + Println(v ...interface{}) +} diff --git a/kadai2/tsuchinaga/testdata/Example.jpg b/kadai2/tsuchinaga/testdata/Example.jpg new file mode 100644 index 0000000..cacc09a Binary files /dev/null and b/kadai2/tsuchinaga/testdata/Example.jpg differ diff --git a/kadai2/tsuchinaga/testdata/Example.png b/kadai2/tsuchinaga/testdata/Example.png new file mode 100644 index 0000000..aa61359 Binary files /dev/null and b/kadai2/tsuchinaga/testdata/Example.png differ diff --git a/kadai2/tsuchinaga/testdata/Example.txt b/kadai2/tsuchinaga/testdata/Example.txt new file mode 100644 index 0000000..12a719a --- /dev/null +++ b/kadai2/tsuchinaga/testdata/Example.txt @@ -0,0 +1 @@ +Example diff --git a/kadai2/tsuchinaga/testdata/Example.xlsx b/kadai2/tsuchinaga/testdata/Example.xlsx new file mode 100644 index 0000000..776136a Binary files /dev/null and b/kadai2/tsuchinaga/testdata/Example.xlsx differ diff --git a/kadai2/tsuchinaga/testdata/subdir1/Example.jpg b/kadai2/tsuchinaga/testdata/subdir1/Example.jpg new file mode 100644 index 0000000..cacc09a Binary files /dev/null and b/kadai2/tsuchinaga/testdata/subdir1/Example.jpg differ diff --git a/kadai2/tsuchinaga/testdata/subdir1/Example.png b/kadai2/tsuchinaga/testdata/subdir1/Example.png new file mode 100644 index 0000000..aa61359 Binary files /dev/null and b/kadai2/tsuchinaga/testdata/subdir1/Example.png differ diff --git a/kadai2/tsuchinaga/testdata/subdir1/subdir1_1/Example.jpg b/kadai2/tsuchinaga/testdata/subdir1/subdir1_1/Example.jpg new file mode 100644 index 0000000..cacc09a Binary files /dev/null and b/kadai2/tsuchinaga/testdata/subdir1/subdir1_1/Example.jpg differ diff --git a/kadai2/tsuchinaga/testdata/subdir1/subdir1_1/subdir1_1_1/Example.png b/kadai2/tsuchinaga/testdata/subdir1/subdir1_1/subdir1_1_1/Example.png new file mode 100644 index 0000000..aa61359 Binary files /dev/null and b/kadai2/tsuchinaga/testdata/subdir1/subdir1_1/subdir1_1_1/Example.png differ diff --git a/kadai2/tsuchinaga/testdata/subdir2/subdir2_1/Example.jpg b/kadai2/tsuchinaga/testdata/subdir2/subdir2_1/Example.jpg new file mode 100644 index 0000000..cacc09a Binary files /dev/null and b/kadai2/tsuchinaga/testdata/subdir2/subdir2_1/Example.jpg differ diff --git a/kadai2/tsuchinaga/testdata/subdir2/subdir2_1/Example.png b/kadai2/tsuchinaga/testdata/subdir2/subdir2_1/Example.png new file mode 100644 index 0000000..aa61359 Binary files /dev/null and b/kadai2/tsuchinaga/testdata/subdir2/subdir2_1/Example.png differ diff --git a/kadai2/tsuchinaga/validation/validation.go b/kadai2/tsuchinaga/validation/validation.go new file mode 100644 index 0000000..6409f2c --- /dev/null +++ b/kadai2/tsuchinaga/validation/validation.go @@ -0,0 +1,38 @@ +package validation + +// NewValidator - 新しくValidatorを生成する +func NewValidator() Validator { + return &validator{} +} + +// Validator - validationをまとめている構造体のインターフェース +type Validator interface { + IsValidDir(dir string) bool + IsValidSrc(src string) bool + IsValidDest(dest, src string) bool +} + +// validator - validationをまとめている構造体 +type validator struct{} + +// IsValidDir - 許可されたdirかのチェック +func (v validator) IsValidDir(dir string) bool { + return dir != "" +} + +var validFileTypes = map[string]bool{"jpeg": true, "png": true} + +// IsValidFileType - 指定されたファイルタイプが利用可能かを返す +func (v validator) IsValidFileType(fileType string) bool { + return validFileTypes[fileType] +} + +// IsValidSrc - 許可されたsrcかのチェック +func (v validator) IsValidSrc(src string) bool { + return v.IsValidFileType(src) +} + +// IsValidDest - 許可されたdestかのチェック +func (v validator) IsValidDest(dest, src string) bool { + return v.IsValidFileType(dest) && dest != src +} diff --git a/kadai2/tsuchinaga/validation/validation_test.go b/kadai2/tsuchinaga/validation/validation_test.go new file mode 100644 index 0000000..2abd219 --- /dev/null +++ b/kadai2/tsuchinaga/validation/validation_test.go @@ -0,0 +1,116 @@ +package validation + +import ( + "reflect" + "testing" +) + +func TestNewValidator(t *testing.T) { + t.Parallel() + expect := &validator{} + actual := NewValidator() + if !reflect.DeepEqual(expect, actual) { + t.Errorf("%s 失敗\n期待: %+v\n実際: %+v\n", t.Name(), expect, actual) + } +} + +func TestValidator_IsValidDir(t *testing.T) { + t.Parallel() + testTable := []struct { + desc string + arg string + expect bool + }{ + {desc: "dirは必須なので空文字ならfalse", arg: "", expect: false}, + {desc: "ここではディレクトリかどうかまでは見ないので、空文字でなければtrue", arg: "foo", expect: true}, + } + + for _, test := range testTable { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + v := &validator{} + actual := v.IsValidDir(test.arg) + if test.expect != actual { + t.Errorf("%s 失敗\n期待: %v\n実際: %v\n", t.Name(), test.expect, actual) + } + }) + } +} + +func TestValidator_IsValidFileType(t *testing.T) { + t.Parallel() + testTable := []struct { + desc string + arg string + expect bool + }{ + {desc: "jpegがtrueで返される", arg: "jpeg", expect: true}, + {desc: "pngがtrueで返される", arg: "png", expect: true}, + {desc: "jpgがfalseで返される", arg: "jpg", expect: false}, + {desc: "gifがfalseで返される", arg: "gif", expect: false}, + {desc: "空文字がfalseで返される", arg: "", expect: false}, + } + + for _, test := range testTable { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + v := &validator{} + actual := v.IsValidFileType(test.arg) + if test.expect != actual { + t.Errorf("%s 失敗\n期待: %v\n実際: %v\n", t.Name(), test.expect, actual) + } + }) + } +} + +func TestValidator_IsValidSrc(t *testing.T) { + t.Parallel() + testTable := []struct { + desc string + arg string + expect bool + }{ + {desc: "許可されたファイルタイプならtrue", arg: "jpeg", expect: true}, + {desc: "許可されていないファイルタイプならfalse", arg: "gif", expect: false}, + } + + for _, test := range testTable { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + v := &validator{} + actual := v.IsValidSrc(test.arg) + if test.expect != actual { + t.Errorf("%s 失敗\n期待: %v\n実際: %v\n", t.Name(), test.expect, actual) + } + }) + } +} + +func TestValidator_IsValidDest(t *testing.T) { + t.Parallel() + testTable := []struct { + desc string + arg1 string + arg2 string + expect bool + }{ + {desc: "許可されたファイルタイプならtrue", arg1: "jpeg", arg2: "", expect: true}, + {desc: "許可されていないファイルタイプならfalse", arg1: "gif", arg2: "", expect: false}, + {desc: "許可されているファイルタイプでもsrcとdestが一致しているとfalse", arg1: "png", arg2: "png", expect: false}, + } + + for _, test := range testTable { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + v := &validator{} + actual := v.IsValidDest(test.arg1, test.arg2) + if test.expect != actual { + t.Errorf("%s 失敗\n期待: %v\n実際: %v\n", t.Name(), test.expect, actual) + } + }) + } +}