3104号室

本のレビューや音楽のことや日々思っていることを書くところ。つまり雑記。

Rustを学ぶ 3日目 UnitTestについて学ぶ

今回はRust における単体テスト(Unit Testing)について学びます。

コードを書く

RustでUnitTestを書く場合は、一般的にはコード内にrust #[cfg(test)]属性(attribute) を記述したtestsモジュールを用意し、その中に#[test]属性を記述したテスト関数を書きます。

以下の例ではFizzBuzz問題に対して、単体テストを書いてみました。

fn main() {
    for n in 1..=100 {
        println!("{}",fizzbuzz(n))
    }
}

fn fizzbuzz(n: u8) -> String{
    if n%3 == 0 && n%5 == 0 {
        return "FizzBuzz".to_string()
    }

    if n%3 == 0 {
        return "Fizz".to_string()
    }

    if n%5 == 0 {
        return "Buzz".to_string()
    }

    return n.to_string()
}

#[cfg(test)]
mod tests {
    use super::*;

    mod convert_multiples_of_three_to_fizz{
        use super::*;

        #[test]
        fn convert_tree_to_fizz(){
            assert_eq!(fizzbuzz(3), "Fizz");
        }

        #[test]
        fn convert_six_to_fizz(){
            assert_eq!(fizzbuzz(6), "Fizz");
        }
    }

    mod convert_multiples_of_five_to_buzz{
        use super::*;

        #[test]
        fn convert_five_to_buzz(){
            assert_eq!(fizzbuzz(5), "Buzz");
        }
    }

    mod convert_multiples_of_fifteen_to_fizz_buzz{
        use super::*;

        #[test]
        fn convert_fifteen_to_fizz_buzz(){
            assert_eq!(fizzbuzz(15), "FizzBuzz");
        }
    }
}

ほんとは関数名を日本語にしたかったのですが、マルチバイト文字はサポートされていないようなので断念しました。

また、テスト内容から仕様を読み取りやすくするために、モジュールを用いて階層構造を作っています。

実行する

プロジェクト内でcargo test と実行するとテストが実行されます。

$ cargo test
   Compiling hello v0.1.0 (file:///Users/.../workspace/fizzbuzz)               
    Finished dev [unoptimized + debuginfo] target(s) in 1.30s
     Running target/debug/deps/hello-a6b7f01e913b3a47

running 4 tests
test tests::convert_multiples_of_fifteen_to_fizz_buzz::convert_fifteen_to_fizz_buzz ... ok
test tests::convert_multiples_of_five_to_buzz::convert_five_to_buzz ... ok
test tests::convert_multiples_of_three_to_fizz::convert_six_to_fizz ... ok
test tests::convert_multiples_of_three_to_fizz::convert_tree_to_fizz ... ok

test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

今回は以上です。

参考文献

doc.rust-lang.org

『プログラミングRust』, Jim Blandy, Jason Orendorff 著, 中田秀基 訳, 2章 Rustツアー

プログラミングRust

プログラミングRust

  • 作者: Jim Blandy,Jason Orendorff,中田秀基
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2018/08/10
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る

Rustを学ぶ 2日目 インストールから"Hello, world!"まで

rustupを利用してRustをインストールする

今回は以下の環境でインストールを行いました。

  • macOS High Sierra バージョン10.13.6

rustupが配布されている以下にアクセスします。

rustup.rs

以下のコマンドを実行するように書かれているので、素直ターミナルを開き、実行します。

curl https://sh.rustup.rs -sSf | sh

すると実行中に以下のように聞かれます。

Current installation options:

   default host triple: x86_64-apple-darwin
     default toolchain: stable
  modify PATH variable: yes

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation
>

デフォルトで良いでしょう。そのままEnter.

以下の内容が表示され、インストールが完了します。

Rust is installed now. Great!

バージョンの確認

ターミナルを一度再起動して、以下のコマンドを順に実行します。

$ cargo --version
cargo 1.29.0 (524a578d7 2018-08-05)
$ rustc --version
rustc 1.29.0 (aa3ca1994 2018-09-11)
$ rustdoc --version
rustdoc 1.29.0 (aa3ca1994 2018-09-11)

今回バージョンを確認したコマンドについて以下に示します。

cargo

cargoはRustの汎用ツールです。プロジェクトを作成したり、プログラムをビルド&実行したり、コードが依存している外部ライブラリの管理をします。

rustc

Rustのコンパイラです。通常はcargoがコンパイラを起動します。

rustdoc

rustdocはRustのドキュメンテーションツール。

プロジェクトを作成する

任意のディレクトリで以下を実行します。

$ cargo new --bin hello

Hello, world!

「さて、"Hello world!"を記述するプログラムを書こうか。」と言いたいところだが、すでにCargoがプログラムを書いてしまっています。

作成したプロジェクトのフォルダの中にあるsrc/main.rcファイルを開くとそれが確認できる。

fn main() {
    println!("Hello, world!");
}

では実行してみましょう。

作成したプロジェクトのフォルダ内でcargo run と打ち込むことで実行できます。

実行すると以下のように"Hello, world!"が表示されます。

yamadamasashinoMacBook-Pro:src masashi$ cargo run
   Compiling hello v0.1.0 (file:///Users/masashi/workspace/hello)               
    Finished dev [unoptimized + debuginfo] target(s) in 10.92s
     Running `/Users/masashi/workspace/hello/target/debug/hello`
Hello, world!

今回は以上です。

文献

『プログラミングRust』, Jim Blandy, Jason Orendorff 著, 中田秀基 訳, 2章 Rustツアー

プログラミングRust

プログラミングRust

  • 作者: Jim Blandy,Jason Orendorff,中田秀基
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2018/08/10
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る

Rustを学ぶ 1日目 Rustとはどんな言語なのか

システム向けプログラミング C/C++には以下の問題がある

  • 安全なコードを書くことが難しい。メモリ管理が特に難しい。
  • マルチスレッドのコードを書くことが難しい。

そこでRustの登場

Rustはシステム向けプログラミング言語で以下の特徴がある。

  • C/C++と同等の性能をもつ。
  • 安全に書くことが容易である。
  • 並列プログラミングが容易にできる。
  • C、C++と同様に開発者がメモリの使い方を細かく制御できる。よって開発者がコードのコストを見積もることができる。

Rustがメモリの安全性と信頼できる並列性を実現するために導入した機構

  • 所有権(ownership)
  • 移動(move)
  • 借用(borrow)

所有権(ownership)

個々の値の生存期間が明確にする。

移動(move)

値をある所有者から他の所有者に引き渡す。

借用(borrow)

所有権に影響を与えることなく一時的に値を利用することを可能にする。

Rustでは並列コードであってもデータ競合がおきないことが保証される

排他ロックなどの同期機構の使い方がもし間違っていれば、コンパイル時に検出される。

文献

『プログラミングRust』, Jim Blandy, Jason Orendorff 著, 中田秀基 訳, 1章 なぜRustなのか?

プログラミングRust

プログラミングRust

  • 作者: Jim Blandy,Jason Orendorff,中田秀基
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2018/08/10
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る

第2回 2D/3Dゲームエンジン Godot Engine 入門 「2Dキャラクタを動かしてみる」

f:id:masashi-yamada0110:20180916192530p:plain

前回はGodotを使って"Hello World!"という文字列を画面に表示させてみました。

jet-city.hatenablog.com

今回は一気にステップアップしてキャラクタを動かしてみましょう。

作るもの

オリジナルのキャラクタをカーソルキーを使って動かしてみます。

動いている間はキャラクタの画像をアニメーションさせます。

f:id:masashi-yamada0110:20180916153058g:plain

準備

使用する画像を以下に置いておきました。

character.zip - Google ドライブ

character.zipを展開すると以下の3つの画像ファイルが入っています。

展開して任意のフォルダに保存して置いてください。

f:id:masashi-yamada0110:20180916154256p:plain green_man.png

f:id:masashi-yamada0110:20180916154334p:plain green_man_01.png

f:id:masashi-yamada0110:20180916154350p:plain green_man_02.png

プロジェクト作成

プロジェクトの作成方法は前回と変わりません。 任意の名前(下の画像の場合は"move")をつけて「作成と編集」ボタンを押しましょう。

f:id:masashi-yamada0110:20180916160259p:plain

プロジェクトに画像を追加する

画面の左側に「ファイルシステム」というタブがあるので、その下のフォルダアイコンと「res://」という文字が書かれている箇所(下記画像参照)をクリックしてください。

その後、先ほどダウンロードして保存した3つの画像ファイルを下の画像の箇所にドラッグ&ドロップしてください。

f:id:masashi-yamada0110:20180916160923p:plain

ドラッグ&ドロップするとドロップした箇所に、下記のように画像が追加されます。

f:id:masashi-yamada0110:20180916161542p:plain

ノード作成と設定

画面右側の「Scene」タブのすぐ下にある「+」をクリックしてノードを追加します。

f:id:masashi-yamada0110:20180909232406p:plain

「Create New Node」というダイアログが開くので、 ツリーの中から「Area2D」というノードを選択し、「作成」ボタンをクリックします。

f:id:masashi-yamada0110:20180916162025p:plain

そうすると、「Scene」タブ内に「Area2D」というノードが追加されます。

これがプレイヤーを表すノードとなるので、「Area2D」と書かれた箇所を2回クリックして、名前を「Player」に変更しておきましょう。

f:id:masashi-yamada0110:20180916162734p:plain

「Player」ノードを選択した状態で、もういちど「+」ボタンをクリックします。

先ほどと同様の手順で、今度は「AnimatedSprite」ノードを追加します。

すると「Player」ノードの下に「AnimatedSprite」ノードが作成されます。

「AnimatedSprite」を選択した状態で、画面右下の「インスペクター」タブから「Frames」プロパティの「」と書かれた項目をクリックします。

すると「New SpriteFrames」というメニュー項目が表示されるのでクリックしてください。

f:id:masashi-yamada0110:20180916163723p:plain

その後、先ほどまで「」と表示されていた箇所に「」と表示されるので、その項目をクリックします。

f:id:masashi-yamada0110:20180916164219p:plain

クリックすると画面下部に「アニメーション」エリアが現れるので、 画面左側の「res://」から「green_man.png」→「green_man_01.png」→「green_man.png」→「green_man_02.png」の順番になるように画像をドラッグ&ドロップしてください。

f:id:masashi-yamada0110:20180916164643p:plain

このままでは表示される画像の大きさが小さいので、「AnimatedSprite」ノードを洗濯した状態で、 画面右下の「インスペクター」からTransform->Scaleプロパティを選択し「x」、「y」の両方に「2」を入力して表示サイズを2倍にします。

f:id:masashi-yamada0110:20180916190905p:plain

これで必要なノードの作成と設定は終了です。

続いてスクリプトを記述してみましょう。

スクリプトを記述してキャラクタを動かす

画面右上の「Scene」タブから「Player」ノードを選択し、f:id:masashi-yamada0110:20180916165358p:plainアイコンをクリックしてください。

すると以下のようなダイアログが表示されるので、何も変更を加えずに「作成」ボタンをクリックします。

f:id:masashi-yamada0110:20180916165856p:plain

画面中央に下図のようなエリアが表示されます。このエリアでスクリプトを編集します。

f:id:masashi-yamada0110:20180916170328p:plain

スクリプトはGDScriptと言うGodot Engine独自のスクリプトを使用しています。

スクリプトの仕様の詳細に関しては公式のドキュメントを参照してください。

python と似た構文なので、pythonを使った経験があれば難なく使えると思います。

今回は入門ということなので、網羅的な説明よりも、必要な内容をその都度説明していこうとおもいます。

まず、最初に以下のようなスクリプトが入力されます。

extends Area2D

# class member variables go here, for example:
# var a = 2
# var b = "textvar"

func _ready():
    # Called when the node is added to the scene for the first time.
    # Initialization here
    pass

#func _process(delta):
#  # Called every frame. Delta is time since last frame.
#  # Update game logic here.
#  pass

まず、最初の行のpython extends Area2Dですが、これは今編集中の"Player"クラスが"Area2D"クラスを継承していることを表します。

ここで他の言語を学習した経験のある人はクラスの宣言がないことを不思議に思うかもしれません。

じつは"GDScript"では「ファイルはクラス」と考えます。

1つのファイルが必ず1つのクラスを表すので、他の言語でみられるpython class Player:のような宣言が必要ありません。

メンバ変数を宣言する場合は、コメントの例にあるようにインデントなしで以下のように宣言します。

var a

今回はスクリーンの大きさをメンバ変数に持ちたいので以下のように宣言します。

var screen size

次にpython func _ready():についてですが、 この行はクラスの生成時に最初に呼び出されるメソッドで、基本的にはクラスの初期化処理を記述します。

func _ready():
    #ここに初期化処理を書く

以下のように記述して先ほど宣言したscreensize変数にスクリーンのサイズを代入しましょう。 また、キャラクタの初期位置(position)を画面の真ん中にしたいので、その処理を記述します。

func _read():
    screensize = get_viewport().size # スクリーンサイズを代入
    position.x = screensize.x / 2
    position.y = screensize.y / 2

その下にpython #func _process(delta): というコメントアウトされた行がありますが、先頭の#を削除してコメントアウトを外してください。

このpython _process(delta)はフレーム毎に呼ばれるメソッドです。クラスのフレーム毎の振る舞いはここに記述します。

func _process(delta):
    # ここにフレーム毎の振る舞いを記述する

キーボードの入力にたいして、キャラクタを動かすのでpython _process(delta)メソッドには 以下のコードを記述します。各コードの意味はコード内にコメントで記述しておきましたので参考にしてください。

func _process(delta):
    var velocity = Vector2() # Playerが動くベクトルを宣言
    if Input.is_action_pressed("ui_right"): # 右カーソルが押されていたら
        velocity.x += 1                               # ベクトルのx要素に1足す
    if Input.is_action_pressed("ui_left"):   # 左カーソルが押されていたら
        velocity.x -= 1                                # ベクトルのx要素から1引く
    if Input.is_action_pressed("ui_down"): #上カーソルが押されていたら
        velocity.y += 1                                # ベクトルのy要素に1足す
    if Input.is_action_pressed("ui_up"):     # 上カーソルが押されていたら
        velocity.y -= 1                                 #  ベクトルのy要素から1引く
 
    if velocity.length() > 0: # もしベクトルの長さが0より大きいつまりキーが押されている時
        velocity = velocity.normalized() * 100 # 実際に動かすのは100ドットなので100掛ける
        $AnimatedSprite.play()  # アニメーションをスタート
    else: 
        $AnimatedSprite.stop() # それ以外ではアニメーションを停止
    
     # position を変更することでPlayerの座標を移動する
    position += velocity * delta # ベクトル * delta

    # 動ける範囲を画面内に限定する
    position.x = clamp(position.x, 0, screensize.x) 
    position.y = clamp(position.y, 0, screensize.y)

最終的なコードは以下です。

extends Area2D

var screensize

func _ready():
    screensize = get_viewport().size
    position.x = screensize.x / 2
    position.y = screensize.y / 2

func _process(delta):
    var velocity = Vector2()
    if Input.is_action_pressed("ui_right"):
        velocity.x += 1
    if Input.is_action_pressed("ui_left"):
        velocity.x -= 1
    if Input.is_action_pressed("ui_down"):
        velocity.y += 1
    if Input.is_action_pressed("ui_up"):
        velocity.y -= 1
    if velocity.length() > 0:
        velocity = velocity.normalized() * 100
        $AnimatedSprite.play()
    else:
        $AnimatedSprite.stop()
        
    position += velocity * delta
    position.x = clamp(position.x, 0, screensize.x)
    position.y = clamp(position.y, 0, screensize.y)

動かしてみる

実際に動かしてみましょう。画面右上のPlayボタンをクリックしてください。

f:id:masashi-yamada0110:20180909235858p:plain

いくつか確認のダイアログが開くと思いますが、特に何も変更せず「OK」または「保存」ボタンをクリックして先に進めてください。

ウインドウが開くのでカーソルキーを押すとキャラクターが動くことを確認できます。

f:id:masashi-yamada0110:20180916153058g:plain

参考文献

Go言語で価値のあるUnitTestの書き方

f:id:masashi-yamada0110:20180921223019p:plain

目次

はじめに

僕はGo言語が好きで、よく使っています。

もちろん作ったものに対しては単体テストをきちんと書くし、そこそこ良いコードをかけている

…と思っていた頃が僕にもありました。

しかし、そんな幻想から一瞬で目が覚める出来事がありました。

それは・・・!

f:id:masashi-yamada0110:20180913205636p:plain

そう、泣く子も黙るTDDの第一人者@t_wadaさんです。

最近、幸運にもt-wadaさんの「テスト駆動開発」の講義を受ける機会がありました。

そのあと勉強のためにその講義の内容を自分なりに昇華し、Go言語でテストを書いてみたりしたのですが 思わず今まで書いたテストコードを火にくべて懺悔したくなるくらい、 「わかりやすく」かつ「保守しやすい」コードがGo言語でもかけたので紹介します。

テスト対象のプログラム

FizzBuzz問題を扱いテストを書いてみたいと思います。

FizzBuzz問題とは

FizzBuzz問題は以下のごく簡単な問題をさします。

1から100までの数をプリントするプログラムを書け。ただし3の倍数のときは数の代わりに「Fizz」と、5の倍数のときは「Buzz」とプリントし、3と5両方の倍数の場合には「FizzBuzz」とプリントすること。

Fizz-Buzz問題とは - はてなキーワード

本体コード

FizzBuzzの数字から文字列に変換する部分を 構造体にするとこんな感じになりますね。

package fizzbuzz
import (
  "strconv"
)

type FizzBuzz struct {}

func (f *FizzBuzz)Convert(value int) string{
  if value % 3 == 0 && value % 5 == 0 {
    return "FizzBuzz"
  } 

  if value % 3 == 0 {
    return "Fizz"
  }

  if value % 5 == 0 {
    return "Buzz"
  }

  return strconv.Itoa(value)
}

このConvert関数に対してテストを書いていきます。

今までどんなテストを書いていたか

今まで書いていたテストコードは以下のようなものでした。

package fizzbuzz
import (
  "testing"
)

func TestFizzBuzz(t *testing.T){
  fizzbuzz := &FizzBuzz{}

  // 数字1を文字列1に変換する
  expect := "1"
  actual := fizzbuzz.Convert(1)
  if expect != actual {
    t.Errorf("expect: %v / actual: %v", expect, actual)
  }

  // 数字3を文字列Fizzに変換する
  expect = "Fizz"
  actual = fizzbuzz.Convert(3)
  if expect != actual {
    t.Errorf("expect %v / actual %v", expect, actual)
  }

  // 数字10を文字列Buzzに変換する
  expect = "Buzz"
  actual = fizzbuzz.Convert(10)
  if expect != actual {
    t.Errorf("expect %v / actual %v", expect, actual)
  }

  // 数字15を文字列FizzBuzzに変換する
  expect = "FizzBuzz"
  actual = fizzbuzz.Convert(15)
  if expect != actual {
    t.Errorf("expect %v / actual %v", expect, actual)
  }

}

「テストはベタに書いた方が良い」とか「テストは重複しても気にしなくても良い」だとかよくわからない思い込みがあってこんなコードをよくかいていました。

これを実行すると下記のような結果が得られます。

$ go test ./fizzbuzz -v
=== RUN   TestFizzBuzz
--- PASS: TestFizzBuzz (0.00s)
PASS
ok      _/Users/masashi/workspace/fizzbuzz/fizzbuzz 0.007s

失敗した場合はこんな感じに出力されます。

$ go test ./fizzbuzz -v
=== RUN   TestFizzBuzz
--- FAIL: TestFizzBuzz (0.00s)
    fizzbuzz_test.go:34: expect FizzBuzz / actual Fizz
FAIL
FAIL    _/Users/masashi/workspace/fizzbuzz/fizzbuzz 0.010s

今読み返すと、テスト結果から失敗した内容が読み取れず、「テストがちゃんと仕事してないなー。」と思うのですが、 つい最近までこれが普通だと思ってたんです。慣れって怖いものです。

取り入れた考え方

以下のような考え方を取り入れてテストを書き換えてみました。

テスト関数は機能ごとに分けて、わかりやすい関数名にする(できれば日本語で)

まず今まで僕が書いていたコードの一番悪い点は一つの関数で全てのテストを行ってしまっていることです。

これではコードを読む人は長い関数を全て目を通さなければテストが正しく動いているかわかりません。

しかもテストが失敗した時は行番号しか表示されないので、 いちいちソースコードを開いて失敗した原因を調べなければ失敗した昨日がわかりません。

なので関数は機能単位で分け、関数は"テスト対象の機能は何か”がすぐにわかる名前にします。

Go言語では関数名に日本語が使えるので特に制約がなければ日本語にしちゃいましょう。

例えば以下のような関数を用意します。

func Test_3の倍数はFizzと変換する(t *testing.T){
//ここにテストを書く
}

するとテストが失敗した場合は以下の出力になるので、どこで失敗したのかすぐにわかります。

--- FAIL: Test_3の倍数はFizzと変換する (0.00s)
    fizzbuzz_test.go:16: 失敗

テストを構造化する

テストの内容は構造化できることが多く、しかもその構造をテストコードに取り入れることで、 目的が明確なテストが書けるようになります。

例えばFizzBuzz構造体のConvert関数のテスト仕様は以下のツリー構造で表せます。

- 3の倍数はFizzと変換する 
  - 3をFizzに変換する 
  - 6をFizzに変換する 
- 5の倍数はBuzzと変換する
  - 5をBuzzに変換する
  - 10をBuzzに変換する
- 3と5両方の倍数はFizzBuzzと変換する
  - 15をFizzBuzzに変換する
  - 30をFizzBuzzに変換する
- そのほかの数字は文字列に変換する
  - 1を"1"に変換する
  - 2を"2"に変換する

この通りにテストコードが構造化されていると、仕様と対応して見やすいものになるはずです。

これにはGo言語のtestingパッケージのRun()関数を使いましょう。

https://golang.org/pkg/testing/#T.Run

func Test_3の倍数はFizzと変換する(t *testing.T){
    t.Run("3をFizzに変換する", func(t *testing.T){
      expect := "Fizz"
      actual := fizzbuzz.Convert(3)
      if expect != actual {
        t.Errorf("expect %v / actual %v", expect, actual)
      }
    })
}

上記のようにコードを記述すると、出力は以下のようになります。

$ go test ./fizzbuzz -v
=== RUN   Test_3の倍数はFizzと変換する
=== RUN   Test_3の倍数はFizzと変換する/3をFizzに変換する
--- PASS: Test_3の倍数はFizzと変換する (0.00s)
    --- PASS: Test_3の倍数はFizzと変換する/3をFizzに変換する (0.00s)

これで出力結果が仕様に対応し、より意味を汲み取りやすいものとなりました。

テストコードの重複をなくす

テストコードに重複があると、コードが長くなりコードを読む人に負担をかけます。

テストコードも本体コードと同様に重複をなくすようにします。

このときに使えるのがGo言語で推奨されているTable Driven Testです。

本家Wikiは以下にTeble Driven Testについて書かれています。 github.com

日本語だと以下がわかりやすいと思います。 qiita.com

Table Driven テストを用いると コードの重複を減らし、スマートにテストを書くことができますので是非マスターしましょう。

Go言語でテストを書く時は必須のスキルです。

書き換えたテストコードと実行結果

最終的なコードはこのようになります。

package fizzbuzz

import (
    "testing"
)

var fizzbuzz = &FizzBuzz{}

func Compare(t *testing.T, expect, actual interface{}) {
    if expect != actual {
        t.Errorf("値が異なります。\n Expect:%s \n Actual:%s", expect, actual)
    }
}

func Test_3の倍数はFizzと変換する(t *testing.T) {
    testCases := []struct {
        name   string
        input  int
        expect string
    }{
        {"3をFizzに変換する", 3, "Fizz"},
        {"6をFizzに変換する", 6, "Fizz"},
    }
    for _, tc := range testCases {
        t.Run(tc.name, func(t *testing.T) {
            Compare(t, tc.expect, fizzbuzz.Convert(tc.input))
        })
    }
}

func Test_5の倍数はBuzzと変換する(t *testing.T) {
    testCases := []struct {
        name   string
        input  int
        expect string
    }{
        {"5をBuzzに変換する", 5, "Buzz"},
        {"10をBuzzに変換する", 10, "Buzz"},
    }
    for _, tc := range testCases {
        t.Run(tc.name, func(t *testing.T) {
            Compare(t, tc.expect, fizzbuzz.Convert(tc.input))
        })
    }
}

func Test_3と5両方の倍数はFizzBuzzと変換する(t *testing.T) {
    testCases := []struct {
        name   string
        input  int
        expect string
    }{
        {"15をFizzBuzzに変換する", 15, "FizzBuzz"},
        {"30をFizzBuzzに変換する", 30, "FizzBuzz"},
    }
    for _, tc := range testCases {
        t.Run(tc.name, func(t *testing.T) {
            Compare(t, tc.expect, fizzbuzz.Convert(tc.input))
        })
    }
}

func Test_そのほかの数字は文字列に変換する(t *testing.T) {
    testCases := []struct {
        name   string
        input  int
        expect string
    }{
        {`1を"1"に変換する`, 1, "1"},
        {`2を"2"に変換する`, 2, "2"},
    }
    for _, tc := range testCases {
        t.Run(tc.name, func(t *testing.T) {
            Compare(t, tc.expect, fizzbuzz.Convert(tc.input))
        })
    }
}

実行すると以下のような実行結果が得られます。

$ go test ./fizzbuzz -v
=== RUN   Test_3の倍数はFizzと変換する
=== RUN   Test_3の倍数はFizzと変換する/3をFizzに変換する
=== RUN   Test_3の倍数はFizzと変換する/6をFizzに変換する
--- PASS: Test_3の倍数はFizzと変換する (0.00s)
    --- PASS: Test_3の倍数はFizzと変換する/3をFizzに変換する (0.00s)
    --- PASS: Test_3の倍数はFizzと変換する/6をFizzに変換する (0.00s)
=== RUN   Test_5の倍数はBuzzと変換する
=== RUN   Test_5の倍数はBuzzと変換する/5をBuzzに変換する
=== RUN   Test_5の倍数はBuzzと変換する/10をBuzzに変換する
--- PASS: Test_5の倍数はBuzzと変換する (0.00s)
    --- PASS: Test_5の倍数はBuzzと変換する/5をBuzzに変換する (0.00s)
    --- PASS: Test_5の倍数はBuzzと変換する/10をBuzzに変換する (0.00s)
=== RUN   Test_3と5両方の倍数はFizzBuzzと変換する
=== RUN   Test_3と5両方の倍数はFizzBuzzと変換する/15をFizzBuzzに変換する
=== RUN   Test_3と5両方の倍数はFizzBuzzと変換する/10をFizzBuzzに変換する
--- PASS: Test_3と5両方の倍数はFizzBuzzと変換する (0.00s)
    --- PASS: Test_3と5両方の倍数はFizzBuzzと変換する/15をFizzBuzzに変換する (0.00s)
    --- PASS: Test_3と5両方の倍数はFizzBuzzと変換する/10をFizzBuzzに変換する (0.00s)
=== RUN   Test_そのほかの数字は文字列に変換する
=== RUN   Test_そのほかの数字は文字列に変換する/1を"1"に変換する
=== RUN   Test_そのほかの数字は文字列に変換する/2を"2"に変換する
--- PASS: Test_そのほかの数字は文字列に変換する (0.00s)
    --- PASS: Test_そのほかの数字は文字列に変換する/1を"1"に変換する (0.00s)
    --- PASS: Test_そのほかの数字は文字列に変換する/2を"2"に変換する (0.00s)
PASS
ok      _/Users/masashi/workspace/fizzbuzz/fizzbuzz 0.008s

テストが出力する結果から仕様がきちんと読み取れます。

もしテストが失敗した場合、その箇所は以下のように表示されます。

--- FAIL: Test_3と5両方の倍数はFizzBuzzと変換する (0.00s)
    --- FAIL: Test_3と5両方の倍数はFizzBuzzと変換する/15をFizzBuzzに変換する (0.00s)
        fizzbuzz_test.go:11: 値が異なります。
             Expect:FizzBuzz 
             Actual:Fizz

どこで失敗したのか一目瞭然ですね。

このようにテスト関数をわかりやすい名前(可能であれば日本語)でテストを構造化することで、テストが仕様を明確に示すようになり、テストが失敗した時もすぐに発生箇所を見つけられるようになります。

これはチームで開発する場合や長く保守するソフトウェアを開発するときに効果を発揮すること間違いなしです。

是非試してみてください。

テスト駆動開発

テスト駆動開発

第1回 2D/3Dゲームエンジン Godot Engine 入門 「環境構築からHello Worldまで」

Godot Engineとは

Godot Engineは様々なプラットフォームで動作する2D/3Dのゲームに対応したゲームエンジンです。

開発もWindows、Mac、Linuxで行うことができ、エディタが軽量で快適に開発できるので、僕が今一番注目しているゲームエンジンです。

環境構築

以下のサイトにアクセスします。

Godot Engine - Free and open source 2D and 3D game engine

上部のナビゲーションから「Download」をクリックします。

f:id:masashi-yamada0110:20180909223212p:plain

使用しているOSのタブを選択し、「DOWNLOADS」の下にある「32-BIT + 64-BIT」ボタンをクリックするとGodotで開発するために必要なファイルがダウンロードできます。

f:id:masashi-yamada0110:20180909223649p:plain

Hello World を表示してみる

この章はGodot Engineが公開している以下のドキュメントを参考に作成されています。

先ほどダウンロードした実行ファイルを実行すると以下のようなウインドウが開きます。

f:id:masashi-yamada0110:20180909224048p:plain

最初の「確認」ダイアログは「キャンセル」ボタンを押して閉じてしまいましょう。

その後、ウインドウ右側の「新規プロジェクト」ボタンを押します。

f:id:masashi-yamada0110:20180909224713p:plain

「新規プロジェクトを作成」ダイアログが開くので、以下の手順に従って操作します。

f:id:masashi-yamada0110:20180910212831p:plain

① 「参照」ボタンを押して作業スペースにするフォルダを選択します。

② プロジェクト名を入力する。今回は「HelloWorld」と入力します。

③ 「フォルダを作成」ボタンを押す。すると①で指定したフォルダの下に「HelloWorld」フォルダができます。

④ 「作成して編集」ボタンを押します。

そうすると、以下のようなウインドウが開きます。

f:id:masashi-yamada0110:20180909230556p:plain

次に編集中のSceneNodeを追加します。

このSceneというのは、その名の通り場面を表していてGodotではこのSceneを編集することによりゲームを組み立てていきます。

Sceneはルートとなる一つのNodeを持ちます。

NodeSceneを構成する要素で「ラベル」や「ボタン」などの様々な種類があり、 ツリー状の構造をしています。

図にすると以下のようになります。

f:id:masashi-yamada0110:20180909233604p:plain

「Scene」に「Node」を追加するにはエディタ右上にある「Scene」タブの下の「+」をクリックします。

f:id:masashi-yamada0110:20180909232406p:plain

ウインドウ右下の「インスペクター」というタブに「Label」のプロパティがテーブル形式で表示されています。

「Text」の一つ右にあるセルをクリックし、値を「Hello World!」に変更してください。

f:id:masashi-yamada0110:20180909234512p:plain

変更を終えたら、中央のLabelが表示されている箇所に表示されているラベルが見切れないように下図の丸いマークをドラッグ&ドロップして表示領域を広げます。

f:id:masashi-yamada0110:20180909235813p:plain

ウインドウ右上に数のようなアイコンが並んでいると思うので、一番左端の「Play」ボタンを押してください。

f:id:masashi-yamada0110:20180909235858p:plain

すると以下のダイアログが表示され、「Sceneを保存するかどうか」聞かれるので「保存」ボタンを押します。

f:id:masashi-yamada0110:20180909235956p:plain

以下のダイアログが開き、Sceneの保存場所を尋ねられますが、今回はそのまま何も変更せずに「保存」ボタンを押してください。

f:id:masashi-yamada0110:20180910000053p:plain

すると今回作成したプロジェクトが実行され、左上に「Hello World!」と書かれたウインドウが表示されます。

f:id:masashi-yamada0110:20180910000621p:plain

今回は以上です。

『集中力はいらない』森博嗣 著 を読みました

この本のタイトルを見て「集中力はいらない。なんてそんなバカな。仕事だって短時間で終わらせるためには“集中”が必要だし、何かを暗記する時だって“集中”は必要じゃないか。だからむしろ“集中”は生きていくうえで重要な能力のひとつじゃないか。」と怒りながら本を手にしました。というのは嘘で、実際のところは「“集中”って結構大事だよなー。でも“いらない”ってのは何でなんだろうなー。ちょっと気になるなー。」ぐらいの気持ちで読みはじめました。

最初はそんな感じて読み始めたのだけど、結構気づきの多い面白い本だったので紹介したいとおもいます。

本の中では“集中”を認めつつも、それがあまりにも重要視されすぎていないか?という問いかけをしています。

そもそも、僕は、「集中力」を全否定するつもりは毛頭ない。それどころか、集中力は大事だと思っている。ただ、説明が難しいのだが、全面的にそれを押し通すのはいかがか、という問題を提起したい。集中力は、みんなが持っている印象ほど素晴らしいものではない、少しずれているのでは、と気づいてもらいたいのだ。

『集中力はいらない』,まえがき, 集中力を疑う

そもそも「集中しなさい」とは「機械のように働きなさい」ということだったと指摘しています。

今後、機械化がさらに進み、AIが人間に代わって多くの仕事をこなすようになる。仕事がなくなると危惧する声も多いが、仕事なんてなくなれば良いのではないか、と僕は考えている。機械に任せられるなら、任せれば良い。人間は今よりも自由になる。自由になったら、無駄な道草をして楽しめば良い。 これまで、社会が人間に「集中しなさい」と要求したのは、結局は、機械のように働きなさいという意味だったのだから、そろそろその要求自体が意味を失っている時代に差し掛かっているということである。

『集中力はいらない』,第1章 集中しない力, 「集中とは機械のように働く」こと

時代が変わり、機械のように働く必要がなくなったというのはそのとおりですね。

これを読んでて、僕は仕事でたまに出会う困った人たちのことを思い出します。

僕はソフトウェア開発を仕事にしているので、仕事の効率化のための”自動化”がよく話題に上がります。

それこそ機械的な作業はすべて機械にやらせましょうという活動です。

こういう活動をつづけていると、たまに自動化にたいしてあからさまに嫌な顔をする人、拒否反応を示す人と出会うことがあります。

自動化をしようとすると重箱の隅をつつくような問題点(ほとんど起こらないケースで、しかもあまり問題にならないようなもの)を指摘したりして自動化を食い止めようとする人たちです。

いままではこういう人の行動原理はホント謎だったのですが、筆者の言う「集中すること」が仕事だと思っている人にとっては自動化とは「自分の仕事が奪われる」危機的状況だったのでしょう。

うーん。そうやって「集中する」仕事にこだわりらなければならないのって、あまり幸せなことではないよなーと思います。

では「集中する」ことの価値が低下しているなかで、重要になってくるのが、発想です。

発想には集中だけではなく思考の発散も合わさって引き起こされる。ということを著者の経験から以下のように書いています。

事前にそればかり考えていた期間がある、といっても、これは、ずっと一点に集中しているわけではない。最初のうちはたしかに焦点が絞られ、集中している思考といえるものの、問題が解けない(つまりその一本道では前に進めなくなる)ため、別の道はないか、ほかに手はないのか、なにか使えそうなものはないか、同じような傾向がどこかにないか、とだんだん思考が発散していく。そういった「きょろきょろ辺りを見回す」思考を長時間続けたあと、突然、なにも考えない空白の場に置かれたときに、発想は生まれる。格好良い言葉にすれば、「無の境地」のようなものか。あれもこれもと、頭の中が騒がしくなったあと、急に静寂が訪れたとき、ぽっかりと浮かび上がるのである。

『集中はいらない』,第1章 集中しない力, 発想は集中からは生まれない

これは経験ある人も多いのではないでしょうか。

何か煮詰まったときに気分転換でお散歩にでかけたら、ふとしたきっかけで解決策が思いついたりした経験が僕にもあります。

このような「分散思考」を持ったときに発想が生まれやすいというのは、今まで意識してはいなかったけど言われてみれば確かにそうだと納得しました。

この本ではなかなか言語化しにくい「分散思考」の必要性を筆者の経験をもとに書いていて、僕はとても興味深く読むことができました。

研究などをしていた経験のある人は、筆者の意見に合意できる点も沢山あるんじゃないかなぁ。

また「“発想”するとはどうすれば良いのかわからなくて、そういう場面になるといつも困ってしまう。」という人も読んだらコツのようなものが見えてくるのではないかと思います。

気になるかたは、ぜひ読んでみてください。

集中力はいらない (SB新書)

集中力はいらない (SB新書)