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

[課題2] io.Writer io.Reader調べ。 テスト作成 #21

Open
wants to merge 3 commits into
base: kadai1-asuke-yasukuni
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
Empty file added kadai2/.gitkeep
Empty file.
1 change: 1 addition & 0 deletions kadai2/asuke-yasukuni/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea
54 changes: 54 additions & 0 deletions kadai2/asuke-yasukuni/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
## dojo7 [課題2] io.Writer io.Reader調べ。 テスト作成

**io.Readerとio.Writerについて調べる**
- 標準パッケージでどのように使われているか
- バイトを扱う読み込み、書き込み、処理にはほぼ共通的に使われている模様。
- io.Readerとio.Writerがあることでどういう利点があるのか具体例を挙げて考えてみる
- 標準パッケージで入出力のインターフェースを定義してくれてるので、機能を作る側も使う側も統一されたインターフェースを扱えるという点で利点がありそう。インターフェースが予め用意されている+共通で使われているおかげでテストも実装しやすい。
- randパッケージのrand.Intのテスト見てて思ったが、自分で何かの機能を作るときも io.Readerとかio.Writer型を引数に持たせたりすると使う側が振る舞いをある程度制御できてよさそう(小並感)
```go
type countingReader struct {
r io.Reader
n int
}

func (r *countingReader) Read(p []byte) (n int, err error) {
n, err = r.r.Read(p)
r.n += n
return n, err
}

// Test that Int reads only the necessary number of bytes from the reader for
// max at each bit length
func TestIntReads(t *testing.T) {
for i := 0; i < 32; i++ {
max := int64(1 << uint64(i))
t.Run(fmt.Sprintf("max=%d", max), func(t *testing.T) {
reader := &countingReader{r: rand.Reader}

_, err := rand.Int(reader, big.NewInt(max))
if err != nil {
t.Fatalf("Can't generate random value: %d, %v", max, err)
}
expected := (i + 7) / 8
if reader.n != expected {
t.Errorf("Int(reader, %d) should read %d bytes, but it read: %d", max, expected, reader.n)
}
})
}
}
```

**課題1のテストを書く**
- テストのしやすさを考えてリファクタリングしてみる
- インターフェース使ってmockを作ってみました
- https://github.com/gopherdojo/dojo7/pull/21/files#diff-5f0f05a4693bd5628f7d44efae0b1425R8-R12
- https://github.com/gopherdojo/dojo7/pull/21/files#diff-5f0f05a4693bd5628f7d44efae0b1425R48
- テストのカバレッジを取ってみる
- 以下のファイルに出力しました
- https://github.com/gopherdojo/dojo7/pull/21/files#diff-31cdc2027b482a00d09e119a29101b68R1
- https://github.com/gopherdojo/dojo7/pull/21/files#diff-85087c52fb8411bf5f8a97be6c6938adR1
- テーブル駆動テストを行う
- 全体的にテーブル駆動テストにしました
- テストヘルパーを作ってみる
- https://github.com/gopherdojo/dojo7/pull/21/files#diff-5f0f05a4693bd5628f7d44efae0b1425R57-R65
138 changes: 138 additions & 0 deletions kadai2/asuke-yasukuni/cover.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style>
body {
background: black;
color: rgb(80, 80, 80);
}
body, pre, #legend span {
font-family: Menlo, monospace;
font-weight: bold;
}
#topbar {
background: black;
position: fixed;
top: 0; left: 0; right: 0;
height: 42px;
border-bottom: 1px solid rgb(80, 80, 80);
}
#content {
margin-top: 50px;
}
#nav, #legend {
float: left;
margin-left: 10px;
}
#legend {
margin-top: 12px;
}
#nav {
margin-top: 10px;
}
#legend span {
margin: 0 5px;
}
.cov0 { color: rgb(192, 0, 0) }
.cov1 { color: rgb(128, 128, 128) }
.cov2 { color: rgb(116, 140, 131) }
.cov3 { color: rgb(104, 152, 134) }
.cov4 { color: rgb(92, 164, 137) }
.cov5 { color: rgb(80, 176, 140) }
.cov6 { color: rgb(68, 188, 143) }
.cov7 { color: rgb(56, 200, 146) }
.cov8 { color: rgb(44, 212, 149) }
.cov9 { color: rgb(32, 224, 152) }
.cov10 { color: rgb(20, 236, 155) }

</style>
</head>
<body>
<div id="topbar">
<div id="nav">
<select id="files">

<option value="file0">dojo7/kadai1/asuke-yasukuni/walk/encoder.go (87.5%)</option>

</select>
</div>
<div id="legend">
<span>not tracked</span>

<span class="cov0">not covered</span>
<span class="cov8">covered</span>

</div>
</div>
<div id="content">

<pre class="file" id="file0" style="display: none">// Recursive image encoder command implementation.
package walk

import (
"fmt"
"os"
"path/filepath"
)

type File interface {
Encode(path, toExt string) error
}

type Walk struct {
File File
}

// Recursively search the directory and perform encoding.
func (w *Walk) Encoder(src *string, fromExt, toExt string) (encodeFiles []string, err error) <span class="cov8" title="1">{
err = filepath.Walk(*src, func(path string, info os.FileInfo, err error) error </span><span class="cov8" title="1">{
if filepath.Ext(path) != "."+fromExt </span><span class="cov8" title="1">{
return nil
}</span>

// Use to output.
<span class="cov8" title="1">encodeFiles = append(encodeFiles, fmt.Sprintf("%s%s -&gt; %s", "[replace file]", path, toExt))

if err := w.File.Encode(path, toExt); err != nil </span><span class="cov0" title="0">{
return err
}</span>

<span class="cov8" title="1">return nil</span>
})

<span class="cov8" title="1">return</span>
}
</pre>

</div>
</body>
<script>
(function() {
var files = document.getElementById('files');
var visible;
files.addEventListener('change', onChange, false);
function select(part) {
if (visible)
visible.style.display = 'none';
visible = document.getElementById(part);
if (!visible)
return;
files.value = part;
visible.style.display = 'block';
location.hash = part;
}
function onChange() {
select(files.value);
window.scrollTo(0, 0);
}
if (location.hash != "") {
select(location.hash.substr(1));
}
if (!visible) {
select("file0");
}
})();
</script>
</html>
8 changes: 8 additions & 0 deletions kadai2/asuke-yasukuni/cover.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
mode: set
dojo7/kadai1/asuke-yasukuni/walk/encoder.go:19.94,20.81 1 1
dojo7/kadai1/asuke-yasukuni/walk/encoder.go:35.2,35.8 1 1
dojo7/kadai1/asuke-yasukuni/walk/encoder.go:20.81,21.40 1 1
dojo7/kadai1/asuke-yasukuni/walk/encoder.go:26.3,28.52 2 1
dojo7/kadai1/asuke-yasukuni/walk/encoder.go:32.3,32.13 1 1
dojo7/kadai1/asuke-yasukuni/walk/encoder.go:21.40,23.4 1 1
dojo7/kadai1/asuke-yasukuni/walk/encoder.go:28.52,30.4 1 0
3 changes: 3 additions & 0 deletions kadai2/asuke-yasukuni/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/gopherdojo/dojo7/asuke-yasukuni

go 1.12
Binary file added kadai2/asuke-yasukuni/imgreplacer
Binary file not shown.
38 changes: 38 additions & 0 deletions kadai2/asuke-yasukuni/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package main

import (
"flag"
"log"

"github.com/gopherdojo/dojo7/asuke-yasukuni/replacer"
"github.com/gopherdojo/dojo7/asuke-yasukuni/validation"
"github.com/gopherdojo/dojo7/asuke-yasukuni/walk"
)

var src = flag.String("src", "", "ファイルパス書いて")
var from = flag.String("from", "jpg", "変換したい画像の拡張子 jpg or png")
var to = flag.String("to", "png", "変換後の拡張子 jpg or png")

func main() {
flag.Parse()

// do ext validation
if !validation.Ext(*from) || !validation.Ext(*to) {
log.Fatalf("\x1b[31mfrom:%s to:%s encode is unsupported\x1b[0m\n", *from, *to)
}

log.Printf("\x1b[33m%s\x1b[0m\n", "[replace start]")

walker := walk.Walk{File: &replacer.File{}}
files, err := walker.Encoder(src, *from, *to)
if err != nil {
log.Fatal(err)
}

// encoding result
for _, f := range files {
log.Print(f)
}

log.Printf("\x1b[33m%s\x1b[0m\n", "[replace end]")
}
69 changes: 69 additions & 0 deletions kadai2/asuke-yasukuni/replacer/encode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Replacer is a package that can convert to the specified image format (jpg, png) by generating File structure.
// Supported formats png,jpg
package replacer

import (
"fmt"
"image"
"image/jpeg"
"image/png"
"log"
"os"
"path/filepath"
)

// A structure that stores image files.
type File struct{}

// This method encodes an image file into jpg or png.
func (f *File) Encode(path, to string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer fileClose(file)

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

// create output file
outPath := path[:len(path)-len(filepath.Ext(path))] + "." + to
out, err := os.Create(outPath)
if err != nil {
return err
}
defer fileClose(out)

// select encoder
switch to {
case "jpg":
if err := jpeg.Encode(out, img, &jpeg.Options{Quality: 100}); err != nil {
return err
}
case "png":
if err := png.Encode(out, img); err != nil {
return err
}
default:
// delete fail file
if err := os.Remove(outPath); err != nil {
return err
}
return fmt.Errorf("%s is unsupported extension", to)
}

// delete original file
if err := os.Remove(path); err != nil {
return err
}

return nil
}

func fileClose(file *os.File) {
if err := file.Close(); err != nil {
log.Printf("\x1b[31m%s:%s\x1b[0m\n", "[encode error]", err)
}
}
30 changes: 30 additions & 0 deletions kadai2/asuke-yasukuni/replacer/encode_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package replacer

import (
"testing"
)

func TestEncode(t *testing.T) {

var testCase = []struct {
Name string
Src string
To string
Result bool
}{
{"jpg -> png encode", "../testdata/test-single.jpg", "png", true},
{"png -> jpg encode", "../testdata/test-single.png", "jpg", true},
{"jpg -> gif not support encode", "../testdata/test-single.jpg", "gif", false},
{"not found file encode", "../testdata/test-nofile.gif", "png", false},
}

f := File{}
for _, tc := range testCase {
t.Run(tc.Name, func(t *testing.T) {
err := f.Encode(tc.Src, tc.To)
if err != nil && tc.Result {
t.Error(err)
}
})
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added kadai2/asuke-yasukuni/testdata/test-single.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions kadai2/asuke-yasukuni/validation/extention.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Validation package for commands.
package validation

// Returns true for allowed formats, false otherwise.
func Ext(ext string) bool {
// Because there are few correspondence formats, we do not make map.
if ext == "jpg" || ext == "png" {
return true
}
return false
}
29 changes: 29 additions & 0 deletions kadai2/asuke-yasukuni/validation/extention_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package validation

import (
"testing"
)

func TestExtValidation(t *testing.T) {
testCases := []struct {
Name string
Ext string
Result bool
}{
{Name: "validate success", Ext: "png", Result: true},
{Name: "validate success", Ext: "jpg", Result: true},
{Name: "validate fail not support", Ext: "gif", Result: false},
{Name: "validate fail not support", Ext: "pdf", Result: false},
{Name: "validate fail number", Ext: "23456886", Result: false},
{Name: "validate fail symbol", Ext: ":;@[]_/23!-^~#/.,", Result: false},
{Name: "validate fail symbol number", Ext: ":;@po234[]_/23!-^~#/.,", Result: false},
}

for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
if Ext(tc.Ext) != tc.Result {
t.Fatalf("%s %s", "ext", tc.Ext)
}
})
}
}
Loading