今回の講義ではソフトウェアテストの話をします。 皆さんの中にこれまでプログラミングしてきたなかで テストを書いたことがあるよ?って人はどれぐらいますでしょうか?
では、ソフトウェアテストでは何をテストするかというと、 テストをする対象によっていろいろなテストの方法が存在しています。
- 単体テスト(ユニットテスト)
- 結合テスト(インテグレーションテスト)
- パフォーマンステスト
- ストレステスト
- 受け入れテスト
- シナリオテスト
- 回帰テスト(リグレッションテスト)
- ペネトレーションテスト
- ....
全てのテストが自動化されているわけではない。 人間が実際に操作をしてテストをする方法もある。(QAの人に聞いてみよう!) 今回の話は、みなさんがこれから一番書くであろうユニットテストの話をします。
テストをやる意義としては以下の様な事が挙げられます。
- 成果物の品質が保証される(仕様がもれなく・正しく実装されているかの目安となる)
- テストに書いてある挙動は、テストが成功していれば保証できる
- 完成度を確認する目安にもなる
- 変更に強くなる(実装者以外が読めて、しかも実行できる仕様書になる)
- 「テストのないコードはレガシーコードだ!」
- なぜならいじったら壊れるかもしれなくて、いじるためにすごいコストがかかるから
- 誰かが何かの変更をした時に、別の箇所が破壊(リグレッション)されてしまっていないかどうかを確認・検知することができる
- テストがあれば、自分がいじった後も、正しく動いてるかどうかを確かめることができます
- 「テストのないコードはレガシーコードだ!」
- インタフェースや仕様が整理・記述される
- 使う側の視点でテストを書くことで、使いやすい・正しいインタフェースを定義することができる
- テストでは必ずコードを実行しているはずなので、テストを読むとコードの使い方がわかります
- テストケースの説明が入ってるとより読めるテストになる
- 何度も同じような動作確認をすなくて済む
- 変更→サーバー再起動→ブラウザでボタンぽちぽち→はー動いた、みたいな
- ユニットテストだけでなく、機能テストや結合テストが自動化されていると良い
- 安心できる
- ちゃんと動いてるよね・・・?→カチャカチャターン...All Test Successful!→ヒャッフー!
- なんとなく不安・・とか、わからないことは、テストコードを書いて証明(prove)しよう!
- 仕様が一番わかっている(仕様を考えた)人がテストを書ける
- アンチパターン:
- You:「あいつが書いたモジュール、ユーザIDからメールアドレスを取得するメソッドにテストがない・・・だと・・・・・。俺が書くしかないな。」
- 上司:「この間リリースされた君たちのモジュールのせいで、バグが出ましたよ^^」 You:「え、ちゃんとテスト書いたのに・・?」
- 上司:「メールアドレス未登録時に返ってくる『電話番号(!)』が間違っていて大変なことなことなっているんだけどどうしてくれるのかな^^」 You:「」
- 間違ったテストコード
- テストから書くことで、正しい、かつ網羅されたテストを記述することができる
- アンチパターン:
- 仕様やインタフェースが何度も変更されたり、複雑にならずにすむ
- アンチパターン:
- サーバからイイネ・コメント数を取得するjsモジュールと、それを使ってイイネ・コメント数を表示するUI部品を作るぞ!
- よし、サーバからJSONRPCでデータ取ってくるgetIineCommentCount()できた! 今度はUI部品を作るぞ!
- う、使ってみたけど、イイネ・コメント数が更新されたことどうやって判断するの・・。 よし、getIineCommentCount(callback)にしよう!
- う、更新取得するたびにcallbackされるし、いくつ増えたかわかんね・・。 よし、newIineCountとnewCommentCountをcallbackに渡そう!
- う、サーバからエラー返ってきたらどうするんだ。 よし、(略)
- ・・・
- getIineCommentは、 callback関数を引数にとり登録 し、 定期的にJSONRPCを呼び出して 、 サーバがエラーを返さなかった時 のみ、 前回取得した イイネ・コメント数との 差分を求めて 、{ newIineCount : x, newCommentCount : y }を callbackに渡す 関数である。
- 責務の巨大化 、 インタフェースの複雑化
- テストから書くことで、テストできるレベルの、シンプルな仕様になる
- アンチパターン:
- ソフトウェアテストには、テスト対象ごとにいろいろな種類がある
- テストを書くといいことがある
- シンプルな責務とインタフェース
- 正しい仕様と実装
- 実装のスピードと安心感
- 変更可能性
- 注:書かないと不幸なことが起きる
- 自分の身を守るため、プロジェクトを成功させるためにもテストを書きましょう
一般的なユニットテストの話をしていきます。
ユニットテスト、または単体テストは一般的に、一つのクラスやモジュールの振る舞いを 検証するためのテストで、ソフトウェアテストの中で、もっとも粒度が小さいテストです。
例として以下のような非常に簡単なコードをテストしてみます。
lib/Foo/Bar.pm
package Foo::Bar;
use strict;
use warnings;
sub hello { return 'hello'; }
1;
Foo::Barというクラスはhelloメソッドを実行すると、 返り値に'hello'という文字列が返ってくるのが期待値である ということを表明しているテストです。
t/Foo/Bar.t
use strict;
use warnings;
use Test::More;
is +Foo::Bar->hello, 'hello', 'Foo::Bar should say hello';
done_testing;
isというのがPerlのユニットテストにおける一般的なassertionと呼ばれるもので、 isの第1引数に実際に得られる値・実測値(actual)、第2引数に期待値(expected)を与えると、 kk実測値と期待値が同一であるかどうかを判定してくれます。 *ただし、Perlの場合isはスカラー値のみ比較できる。
実行してみると、以下のようにテストが成功したことが分かります。
$ prove -Ilib t/Foo/Bar.t
t/Foo/Bar.t .. ok
All tests successful.
Files=1, Tests=2, 0 wallclock secs ( 0.02 usr + 0.00 sys = 0.02 CPU)
Result: PASS
このように、ユニットテストは大抵の場合シンプルに記述することができ、 ある程度のパターンに分けることができます。
- 標準的なテスト対象の振る舞いを検証するパターン
- 例外や警告を送出するかどうかを検証するパターン
- クラスのコンストラクタを検証するパターン
- ...
などなど、ユニットテストは何度も似たようなテストコードを書くことが多いため、 パターンごとにどうやって書けばよいのか?ということを学んでいくと、 効率的に書いていくことができます。
ユニットテストの目的はもちろん、コードの振る舞いの保証という目的が大きいですが、 他にも以下の様なものがあります。
- 開発中のエンバグやリグレッションを検知できる
- このコードをいじったら、いつの間にかあのメソッドが動かなくなっちゃった...ってことがなくなる
- コードの変更を恐れずに出来るようになる
- 設計の欠点を早期に出来る
- テストが出来る=外部から実装したメソッドを呼び出すといことなので、テストしづらいインターフェイスは、使いづらいインターフェイスです
- テストしやすいコードになるということは、外部からも扱いやすいコードであるという事になります
→あとで説明するTDDと密接に関わってきます
このように、品質の保証以外の目的でも利用されることから、デベロッパーテストとも言われます。
ユニットテストは、テストの対象によって書き方と難易度が変わります。 例を交えながら簡単な順に説明します。
ユーティリティ系のメソッドとは、つまりインスタンスを作らずに、 メソッドへ同じ入力に対しては必ず同じ値を返すようなメソッドです。 (いわゆる副作用の無いもの)
my $actual = Calc->subtract(10, 7);
is $actual, 3, '10から7を引くと3になる';
状態を持っているので、前提条件(コンテキスト)を準備しなければならない。 オブジェクトの状態を外部から取得しなければテストできない事が多い。
my $stack = Stack->new();
ok $stack->is_empty;
$stack->push(37);
ok not $stack->is_empty;
is $stack->pop, 37;
ok $stack->is_empty;
package Queue;
sub new {
my $class = shift;
return bless { content => [] }, $class;
}
sub enqueue {
my ($self, $val) = @_;
push $self->{content}, $val; # perl v5.14では ARRAYREFにpushとかshiftが使えます
}
sub dequeue {
my $self = shift;
shift $self->{content};
}
1;
my $array = Array->new;
my $queue = Queue->new($array);
$queue->enqueue 'kinoshita';
$queue->enqueue 'suzuki';
is $queue->dequeue, 'kinoshita';
たとえば、「引数次第でDBにアクセスする前に一旦すべてのいろいろなキャッシュを消して、DBからブログ記事のデータをとってきて、データの取得数が10件より多かったら、お気に入りデータと合わせてキャッシュに載せたい。」 なんていう超便利メソッドを実装して下さい要件が仮にあった時に、 すべての処理がひとつのメソッドに埋め込めれているとテストしづらくて辛いですよね。
*注意:あくまで悪い例のため↓のようにすべてを詰め込んだコードは書かないで下さい。
sub ultra_hyper_super_convenience_method {
my ($self, $args) = @_;
...
if ($args->{clear_cache}) {
$self->_cache->clear_cache_for('entries');
$self->_cache->clear_cache_for('users');
$self->_cache->clear_cache_for('ranking');
}
...
my $db = DBI->connect(xxxxx);
my $sth = $db->prepare("select * from entries where category_id = ?");
$sth->execute($args->{category_id});
my $rows = $sth->fetchrow_arrayref;
....
if (scalar(@$rows) > 10) {
for $row (@$rows) {
my $favorites = $self->_get_favorites($row->{id});
my $entry = {
title => $row->{title},
body => $row->{body},
favorites => $favorites,
};
my $key = 'entry:' . $row->{id};
$self->_cache->set($key, encode_json($entry));
}
}
return $rows;
}
このようなメソッドは例えば、まず前提条件として
- キャッシュが存在している場合/存在していない場合
- EntryがDBにデータが存在している場合/存在していない場合
- FavoriteがDBにデータが存在している場合/存在していない場合
- DBにデータが存在している場合、レコードの件数が10件以下、ちょうど10件、10件以上
などが考えられて、入力のパターンとしても
- clear_cacheの値が真の時/偽の時
- category_idが正常値/エラーが出るような異常値
と考えると、仮に全部のコードをカバーしているテストを書いたとしても、 テストが失敗orエラーが起きた時、全ての条件でテストを書いていなければ どういう環境でコケたのかを把握しづらい状態になります。
実際には全部で 2 * 2 * 2 * 3 * 2 * 2 = 96パターンの組み合わせのコンテキストを テストしてなければならず、テストをするのもなかなか大変で、そもそもメソッドの要件や、 設計を見直したほうが懸命です。
他にも各処理の部分部分でもミスがないかどうか(キャッシュの消し忘れがないかどうか等)を 確認するってのをやりだしたら、かなーり大変です。
こういうコードはどうすればいいのかってのはTDDのところで後で説明します。
例えば、Webアプリを作った時にデータベースに依存しているテストを書いていると、 データベースの接続先が本番のままだったら、テストを走らせるために本番のDBに 影響を与えてしまいます。
また、外部のWebAPIを利用するコードのテストの場合を書いた時に、必ずずしも 外部APIが利用できるかどうかわかりません。テストダブルを用いて、 テストすることをおすすめします。全部が全部、テストダブルで置き換えてしまうと テストの意味がなくなるので、外部に依存してしまうようなところなど最低限に利用にしておくと良いです。
上述したようなテストビリティが低いようなテストに対しては、 しばしばテストダブル(代役)と呼ばれるものが利用されます。 例えば、REST APIと通信を利用するクラスのテストをしたいときには、 実際に通信を行う部分を、通信せずにそのまま規定の値を返すように変更すると、 通信を受け取って値を処理する部分のテストが可能となります。 このように期待する値を常に固定で返すテストダブルをスタブと呼びます。
他にもテストダブルには以下のようなものがあります。
- モック
- テスト対象のコード内での、期待動作を事前に定義して呼び出し方が正しいかどうかを検証するためのものです
- モックオブジェクトに対するメソッド呼び出しの引数や、呼び出し回数などをチェックします
- スタブ
- 期待する値を返すだけでモックの超簡易版と言えます
- 既存のオブジェクトの一部のメソッドだけをスタブ化してテストすることがあります
- フェイク
- 実際の挙動とほぼ同じだけど、実際のものよりも簡易的な実装
- DBのテストをするときにMySQLの代わりにSQliteなどインメモリDBを使う
- スパイ
- テスト対象プログラムの何らかの出力を記録するものです
- その出力をあとで取り出してテストで利用します
- ダミー
- テスト対象のメソッドの引数に必要ではあるが、動作には関係ないものに関して適当な値として準備するもの
モックとスタブはよく使うかと思います。
良いユニットテスト、というのは上述したような効能(フィードバック)が得られるテストです。
ユニットテストは、開発中に何度も繰り返して実行することで、開発者へのフィードバックを行うため 実行するのに1ファイルあたり5分とか10分とかかかるようなテストは嬉しくないです。 (待ち時間が苦痛でしか無いので)
高速に実行するために、できるだけDBへのアクセスや外部のAPIへのアクセスを モックやスタブ(後述)などで避けたりする場合があります。
テストケースが明確に記述されていて何をテストしているかが分かりやすい状態
テストケースが何をテストしているのかが記述されていると、 あとでコードを読まなきゃいけないメンテナンスをする人が、 ドキュメント代わりに読むことができ、メンテナンスがしやすくなります。
Perlだと2通りの書き方があります。
my $actual = Calc->subtract(10, 7);
is $actual, 3, '10から7を引くと3になる';
subtest '10から7を引くと3になる' => sub {
my $actual = Calc->subtract(10, 7);
is $actual, 3;
};
テストケースが多い場合は、テストケースを構造化して定義すると良いです。
1個のテストケースが行うテストの範囲が小さいほど、失敗した時の条件をしぼりやすいです。 テストが失敗した時に原因を特定しやすくなるという利点があります。
subtest "引数に、'a', 1, undefを与えたらfizz0になる" => sub {
my $actual = $obj->foo('a', 1, undef);
is $actual, 1;
is $obj->bar, 'a';
is $obj->baz, undef;
is scalar($obj->fizz), 0;
};
subtest "引数に、'a', 1, オブジェクトを与えたらfizzが変わる" => sub {
my $actual = $obj->foo('a', 1, $other_obj);
is $actual, 1;
is $obj->bar, 'a';
is $obj->baz, undef;
is scalar($obj->fizz), 1;
};
というよりも、
subtest "第1,2引数に'a', 1を与える時" => sub {
my ($arg1, $arg2) = ('a', 1);
subtest "args3にundefを与えるとfizzが0になる" => sub {
my $actual = $obj->foo($arg1, $arg2, undef);
is scalar($obj->fizz), 0;
};
subtest "args3にオブジェクトを与えるとfizzが1になる" => sub {
my $actual = $obj->foo($arg1, $args2, $other_obj);
is scalar($obj->fizz), 1;
};
};
subtest "第1,2引数に'a', undefを与える時" => sub {
....
};
というように、細かくテストケースに合わせて一つの条件でテストする内容を 絞ったほうが読みやすく、分かりやすいテストになります。
my $actual = foo(1,2,1);
is $actual, 4;
$actual = foo(1,2,5);
is $actual, -3;
subtest "aが1、bが2の時" => sub {
my ($a, $b); = (1,2);
subtest "cが1なら4を返す" => sub {
my $actual = foo($a, $b, 1);
is $actual, 4;
}
subtest "cが5なら-3を返す" => sub {
my $actual = foo($a, $b, 5);
is $actual, -3;
};
};
- ユニットテストとは一つのクラスやモジュールの振る舞いを検証するためのテスト
- ユニットテストは開発者自身が恩恵をうけるためのデベロッパーテストである
- ユニットテストには書きやすさなどの難易度がある
- 良いユニットテストとは、高速に何度も実行できてドキュメントとして読めるテストだ
はてなさんの研修資料が良い感じにまとまっているので、随時こちらを御覧ください。
Hatena-Textbook/test-for-perl.md at master · hatena/Hatena-Textbook · GitHub
Test::Harnessに付属しているコマンドで、本来はただのPerlのスクリプトである、 テストのファイルたちを実行してTAP形式で結果を出力してくれる。
テストを書くためには、テストを書くためのライブラリが必要です。
Perlにおける一番メジャーなテスト用ライブラリ。okやisなど、Perlらしいテストが書ける。 簡単なテストはこれだけで事足りる。subtestというサブルーチンでテストの構造化が出来る。
ok $object;
is $actual, 30; # ok $actual == 30; と等価
isnt $actual, 30;
like $string, qr/test/;
can_ok 'Encode', 'encode';
use_ok 'Encode';
などなど、他にもPerlのコードをテストするためのアサーションはありますがとりあえずこんなもんで。 もっと詳細を知りたい人は Test::More - search.cpan.org を読みましょう。
PerlらしくxUnit系のテストを書くためのフレームワーク。Test::Moreと組み合わせて使う。 setup/teardownの仕組みが利用できるため、テストケースごとに毎回フィクスチャーオブジェクトを 準備してテストしたい場合には綺麗に書きやすいかも。
use strict;
use warnings;
use Test::More;
use Test::Class;
sub setup : Test(setup) { ... }
sub teardown : Test(teardown) { ... }
sub test_update_records : Tests { ... }
Test::Moreは基本的に、一致や比較などしかテストをすることが出来ないため、
- Test::Exeption 例外(dieとかcroakとか)を送出したかどうかをテストすることが出来る。
lives_ok(sub { 1 / 1 }, 'description');
dies_ok(sub { 1 / 0 }, 'description');
- Test::Warn
warning_is { ... }, 'warn message', $test_name;
warning_like { ... }, [qr/warn regex/], $test_name;
- Test::MockObject
- Test::MockObject::Extends
- Test::Mock::Guard
- Test::MockModule
TDDとは、テスト駆動開発(Test-Driven-Development)の略のことです。 名前の通り、テスト中に開発を行なっていきます。ここまでさんざん説明してきた内容っていうのは、 このTDDで開発するといろいろ捗るんだよ!ってことを言いたがための説明でした。
TDDではおおよそ以下のようなサイクルを繰り返すことで開発を進めていきます。
- 失敗するテストケースを書く
- テストに失敗する
- テストが通るようにプロダクションのコードを書く
- テストを成功させる
- テストが通ったらリファクタリングをする
このようなサイクルを通すことで、まず最初はテストが通ることが 保証されたコードを書ききり、その後に、メンテナンス可能な、 綺麗なコードにリファクタリングをしていきます。
「動く、綺麗なコード」が最終目標です。
リファクタリングとは、動作を買えずにプログラムのソースコードの内部構造を整理することです。 単純に、読みづらいイディオムを使ってたり、複雑なロジックを実装して理解しづらいコードを 綺麗に書きなおすのもリファクタリングですし、これ以上機能を追加していくのが難しいため、 設計そのものを変更するものもリファクタリングと呼びます。 Wikipediaにも具体的なリファクタリング方法が幾つかのってます。
リファクタリング (プログラミング) - Wikipedia
TDDにおけるで黄金の回転で言うリファクタリングは、テストを通すために行った、 最初の実装を整理することが主な作業なため、前者の読みづらい・理解しづらいコードを 整理する作業をすることが多いです。
読みやすい・理解しやすい綺麗なコードにするためには、以下の様な本を読むと良いと思います。
また、設計を変更するような大規模なリファクタリングについては、 以下の本を読むと良いでしょう。
- Amazon.co.jp: リファクタリング―プログラムの体質改善テクニック (Object Technology Series): マーチン ファウラー, Martin Fowler, 児玉 公信, 平澤 章, 友野 晶夫, 梅沢 真史: 本
- Amazon.co.jp: レガシーコード改善ガイド (Object Oriented SELECTION): マイケル・C・フェザーズ, ウルシステムズ株式会社, 平澤 章, 越智 典子, 稲葉 信之, 田村 友彦, 小堀 真義: 本
いずれにせよ、リファクタリングを行う際には、かならず元の動作を保証するための テストがなければなりません。でなければ、コードは綺麗になったとしても、 元々の機能が正しく動作しているかどうかを確かめられないからです。 その点、TDDを行うと、最初からテストが存在しているため、テストが全てグリーンになった時点で、 すぐさまリファクタリングを行なって「動作する綺麗なコード」を目指すことができます。
再掲
sub ultra_hyper_super_convenience_method {
my ($self, $args) = @_;
...
if ($args->{clear_cache}) {
$self->_cache->clear_cache_for('entries');
$self->_cache->clear_cache_for('users');
$self->_cache->clear_cache_for('ranking');
}
...
my $db = DBI->connect(xxxxx);
my $sth = $db->prepare("select * from entries where category_id = ?");
$sth->execute($args->{category_id});
my $rows = $sth->fetchrow_arrayref;
....
if (scalar(@$rows) > 10) {
for $row (@$rows) {
my $favorites = $self->_get_favorites($row->{id});
my $entry = {
title => $row->{title},
body => $row->{body},
favorites => $favorites,
};
my $key = 'entry:' . $row->{id};
}
}
return $rows;
}
この場合、設計を変更出来なかったとしても、出来るだけひとつのメソッドでやることは一つだけという 方針で実装していくことで、一つ一つのメソッドはテストしやすい程度に十分に小さなメソッドにすることができます。
sub clear_all_cache { ... }
sub select_entries_by_category_id { ... }
sub set_entries_on_cache { ... }
sub ultra_hyper_super_convenience_method {
my ($self, $args) = @_;
...
$self->clear_all_cache if $args->{clear_cache};
...
$self->select_entries_by_category_id($args->{category_id});
....
$self->set_entries_on_cache($rows) if scalar(@$rows) > 10;
return $rows;
}
テストは、これらの内部で呼ばれているメソッド毎に個別に書いてやれば良くて、 最終的にultra_hyper_super_convenience_methodに対するテストは、 内部で使われているメソッドたちが正しく呼ばれているかどうかを確認するだけでよくなります。 (呼ばれているかどうかをテストするのにはモックオブジェクトを使います。)
オブジェクト指向の設計をしていく際に、単一責任の原則(SRP)という方針があります。 これは、あるクラスを変更しようとした時に1つ以上の変更理由があってはいけない、 言い換えると1つのクラスには1つの責務を持つだけにするという設計の目安です。
そうすることで、1つ1つのコードがシンプルになり、テストも容易に書けるため、 メンテナンスしやすいコードとなります。ただ、普通にコードを書いていくと、 この原則を守ることはかなり厳しいです。
しかし、TDDを利用することで、設計を考えながらコードを書くことが出来、 かなり早い段階で単一責任の原則の違反を発見することができます。
MVCパターンでは、Model, View, Controllerごとに役割を持たせ、 各コードを分離して記述していきます。しかし、MVCパターンのVやCの部分は、 意外とテストをすることが大変で、例えばViewは、デザインが変更されたら、 テストも変更しなければならない可能性が高く、テストを書かないことが多いです。
また、Controllerもテストが出来なくはないですが、基本的にControllerの 責務はModelの値をViewに渡すことであり、それ以外の部分のテストは出来る限り、 別の箇所で行いたいところです。さもなければ、Modelのテストを行うために、 Webサーバを起動して、リクエストを投げる、並のコストが掛かったテストとなってしまいます。
そこで、Controllerの中には、ビジネスロジックと呼ばれるものは記述せずに、 Modelにビジネスロジックとなる、様々な処理を記述していき、 Modelに対するテストをしっかりと記述していくのが良いです。
「Skinny Controller, Fat Model」という言い回しが、 あるようにアプリケーション自身のロジックを含むコードは 出来る限りModelに書いてテスト出来るようにしましょう。
出典は詳しくはわかりませんが、海外のアジャイル開発系のイベントで、行われていた催しみたいです。 自分は、Shinjuku.rbというイベントで存在を知りました。
プロジェクターを置き、そこに1台のマシンと2つの席を用意して、その周りを囲んで参加者が座って、 2人がペアプログラミングしながら周りの参加者もコードに関する議論をしていくというイベントです。 (開発技法ではない)
似たようなものに、Coding Dojoというものがありますが、全員が議論に参加して良いという点が違います。 Welcome to the Coding Dojo 第3回 DojoとKataでRubyを学ぼう - @IT
よりよいコードを書くのに練習が必要だ!という思想から来ているものなので、自分一人でコーディングできる必要はありません。ペアと話し合いながら、ペアの人の考えを学んで実装する流れを体験していきたいと思います。
特にペアプロやったことない人にはやってみて欲しいです。例えちっとも実装が進まなかったとしてもペアプロ後の成長は感じられると思います。
- 2人1組になってプログラムを書く
- 書く人(ドライバー)と、横で考える人(ナビゲーター)は随時入れ替える
- コーディングするときは必ず何を書こうとしているのか喋りながら書く
- 15分の作業が終わる前にgit commitして、速やかに次のペアに席を譲る
- コーディング中のペア以外の人達は以下の様に何でも良いのでその場で口を出して良い。ただし、質問が優先されるべき。
- 技術的な質問(このメソッド何なの?みたいな)
- 書き方の間違いの指摘
- 改善案の提案
- コードの仕様上で気をつけなければならないことの指摘
などなど...
Coding Dojo Wikiというサイトに載っている、型ボウリングの問題をみんなで解きましょう!
- 1ゲームは、10ターンで、1ターンをフレームと呼ぶ
- 1フレーム中、プレイヤーはすべてのピンを倒すまでに2回投げられる
- 2投で全てのピンを倒せなかった場合は、そのフレームでのプレイヤーのスコアは2投で倒せたピンの合計数
- 2投の試行ですべてのピンを倒した場合は、スペアと呼ばれ、そのフレームでのスコアは、10+プレイヤーの次のターンの最初の1投で倒したピンの数
- フレームの最初の1投で全てのピンを倒した場合はストライクと呼び、そのフレームのスコアは、10+プレイヤーの次の2投で倒したピンの単純な合計数
- プレイヤーは最後の10フレーム目でスペアを取得した場合追加で1回、ストライクを取得した場合は追加で2回投げることが出来、追加で投げる分の得点計算は同じフレーム(10フレーム目)のものとして扱われる
- ゲームのスコアは全てのフレームのスコアの合計
1つの文字が1投の結果を示し、数字を倒したピンの数、"X"をストライク、"/"をスペア、"-"をガーターとする
- "XXXXXXXXXXXX"(12投:12ストライク)= 10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 = 300
- "9-9-9-9-9-9-9-9-9-9-"(20投:9とガーターの10組)= 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 = 90
- "5/5/5/5/5/5/5/5/5/5/5"(21投:5のスペア10フレームと最後の5)= 10 +5 +10 +5 +10 + 5 + 10 +5 +10 +5 +10 +5 +10 +5 +10 +5 +10 +5 +10 +5 = 150
- レガシーコード改善ガイド
- 実践テスト駆動開発
- JUnit 実践入門
- 右手に感情、左手に数値 - カバレッジを味方にしよう - t-wadaの日記 : http://d.hatena.ne.jp/t-wada/20111207/coverage_is_your_friend ...