Upload
higaki
View
2.199
Download
2
Embed Size (px)
DESCRIPTION
Citation preview
2013年6月15日
Ruby初級者向けレッスン 46回— Test::Unit —
ひがき @ Ruby関西
お品書き• テスト フレームワーク• テスト駆動開発
– Red–Green–Refactor
• Test::Unit の小技• Test Double
テスト フレームワーク いろいろ• Test::Unit
• RSpec
• Cucumber
• Capybara
• etc.
Test::Unit• プログラムの単体テストを行う• Ruby用の xUnit
– MiniTest::Unit — 標準添付– Test::Unit — 標準添付∗ 内部で MiniTest::Unit を使用
– Test::Unit Gem
RSpec
• プログラムの振舞いを記述する (BDD)
• ドメイン特化言語 (DSL)
• 参考– http://jp.rubyist.net/
magazine/?0021-Rspec
Cucumber (::)
• 自然言語に近い形式で書ける• 受け入れテスト向け• これからは Turnip なの?
http://magazine.rubyist.net/
?0042-FromCucumberToTurnip
Capybara
• Webアプリケーション向け– ブラウザ上のユーザ操作– JavaScript
自動テストのメリット• 機能的な保証
– バグの検出– バグ混入を監視
• 開発支援– 不安を解消– リファクタリング
テスト駆動開発1. まずテストを書き、失敗することを確認する2. テストが成功するように、製品コードを書く3. テストはそのまま、コードをきれいに
Red–Green–Refactor
テスト プロダクト
Red Green
Refactor
-
?�
テスト駆動開発をやってみよう• プロダクトコード — myarray.rb
• テストコード — test myarray.rb
• MyArray が空のときMyArray#empty? は真であるべき
Step 0 プロダクトclass MyArray
def initialize
@array = []
end
end
Step 0 テストrequire ’test/unit’
require ’./myarray’
class TestMyArray < Test::Unit::TestCase
def setup
@array = MyArray.new
end
def test_empty_by_empty_myarray
assert(@array.empty?)
end
end
テストのながれテストごとに、以下を実行する。
1. setup — 準備2. test ~ — テスト本体3. teardown — 後始末
• テストの実行順序は無保証• 各テストは他のテストに依存してはいけない
Step 0 ERROR 実行$ ruby test_myarray.rb
...
E
===================================================
Error: test_empty_by_empty_myarray(TestMyArray)
NoMethodError: undefined method ‘empty?’ for
#<MyArray:0x802ea72c @array=[]>
test_myarray.rb:12:in ‘test_empty_by_empty_myarray’
...
1 tests, 0 assertions, 0 failures, 1 errors,
0 pendings, 0 omissions, 0 notifications
Step 1 プロダクトclass MyArray
def initialize
@array = []
end
def empty? # <= 追加end # <= 追加
end
Step 1 Red 実行$ ruby test_myarray.rb
...
F
===================================================
Failure: <nil> is not true.
test_empty_by_empty_myarray(TestMyArray)
test_myarray.rb:12:in ‘test_empty_by_empty_myarray’
...
1 tests, 1 assertions, 1 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
Step 2 プロダクトclass MyArray
def initialize
@array = []
end
def empty?
true # <= 追加end
end
Step 2 Fake it! 実行$ ruby test_myarray.rb
Loaded suite test_myarray
Started
.
Finished in 0.000942 seconds.
1 tests, 1 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
assert式が真なら、テストが通る。
assert("".empty?, "空であること")
assert(obj.nil?, "nil であること")
assert equal
第1引数 == 第2引数 なら、テストが通る。
assert_equal(0, array.size)
assert_equal(’matz’, author)
assert_equal(0.3, 0.2 + 0.1, "Fail!!")
Fail!!
<0.3> expected but was
<0.30000000000000004>
assert in epsilon
第1引数と第2引数の誤差が第3引数の許容範囲内なら、テストが通る。
assert_in_epsilon(0.3, 0.2 + 0.1, 1e-15)
assert raiseブロック内で、第1引数の例外が発生したら、
テストが通る。
ex = assert_raise(IndexError) do
[].fetch(0)
end
Step 3 プロダクトclass MyArray
...
def empty?
true # <= Fake it!
end
def << o # <= 追加@array << o # <= 追加self # <= 追加
end # <= 追加end
Step 3 テスト...
class TestMyArray < Test::Unit::TestCase
...
def test_empty_by_empty_myarray
assert(@array.empty?)
end
def test_empty_by_nonempty_myarray # <= 追加@array << "1st" # <= 追加assert([email protected]?) # <= 追加
end # <= 追加end
Step 3 Triangulate 実行$ ruby test_myarray.rb
...
.F
===================================================
Failure: <false> is not true.
test_empty_by_nonempty_myarray(TestMyArray)
test_myarray.rb:17:in ‘test_empty_by_nonempty_myarray’
...
2 tests, 2 assertions, 1 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
Step 4 プロダクトclass MyArray
...
def empty?
@array.empty? # <= 修正end
def << o
@array << o
self
end
end
Step 4 Green 実行$ ruby test_myarray.rb
Loaded suite test_myarray
Started
..
Finished in 0.00123 seconds.
2 tests, 2 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
小技 --help
$ ruby test_myarray.rb --help
Test::Unit automatic runner.
Usage: test_myarray.rb [options] [-- untouched arguments]
-r, --runner=RUNNER Use the given RUNNER.
--collector=COLLECTOR Use the given COLLECTOR.
--ignore-name=NAME Ignores tests matching NAME.
-t, --testcase=TESTCASE Runs tests in TestCases matching TESTCASE.
...
-h, --help Display this help.
Deprecated options:
--console Console runner (use --runner).
小技 --verbose$ ruby test_myarray.rb --verbose
Loaded suite test_myarray
Started
TestMyArray:
test_empty_by_empty_myarray: .: (0.000630)
test_empty_by_nonempty_myarray: .: (0.000499)
Finished in 0.001676 seconds.
2 tests, 2 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
小技 テストの指定$ ruby test_myarray.rb -v -n /_non/
Loaded suite test_myarray
Started
TestMyArray:
test_empty_by_nonempty_myarray: .: (0.000499)
Finished in 0.000892 seconds.
1 tests, 1 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
こんなときどうするrequire ’person’
class TestPerson < Test::Unit::TestCase
def test_age
matz = Person.new(
’matz’, Time.local(1965, 4, 14))
assert_equal(48, matz.age)
# 来年はテストに失敗する!?
end
end
Test Double• テスト対象 (プロダクト) が依存している
コンポーネントを置き換える– ダミーオブジェクト– テストスタブ– テストスパイ– モックオブジェクト– フェイクオブジェクト
http://goyoki.hatenablog.com/entry/
20120301/1330608789
例えば影武者を使うrequire ’kagemusha/time’
class TestPerson < Test::Unit::TestCase
def test_age
matz = Person.new(
’matz’, Time.local(1965, 4, 14))
Kagemusha::Time.at(2013, 6, 15) do
assert_equal(48, matz.age)
end
end
end
もしくは Mocha を使うrequire ’mocha/setup’
class TestPerson < Test::Unit::TestCase
def test_age
matz = Person.new(
’matz’, Time.local(1965, 4, 14))
Time.stubs(:now)
.returns(Time.local(2013, 6, 15))
assert_equal(48, matz.age)
end
end
あるいは RR を使うrequire ’rr’
class TestPerson < Test::Unit::TestCase
def test_age
matz = Person.new(
’matz’, Time.local(1965, 4, 14))
mock(Time).now{Time.local(2013, 6, 15)}
assert_equal(48, matz.age)
end
end
まとめ• テスト フレームワーク• テスト駆動開発
– Red–Green–Refactor
• Test::Unit の小技• Test Double
演習問題 0今日のレッスンで分からなかったこと、疑問に思ったことをグループで話し合ってみよう。
演習問題 1• スタッククラスを作ろう!
– テスト駆動開発で「スタック」クラスを作ってみよう。
– push で順に要素を追加できること。– pop で最後から順に要素を取り出せること。– このような動作を LIFO (Last In, First
Out) という。
実装するメソッド• empty?
– スタックが空なら真、空でなければ偽を返す。• size
– スタックの要素数を返す。• push(val)
– 引数の値をスタックの最後に積む• pop
– スタックの最後の値を取り除いて返す。– スタックが空ならStack::EmptyStackErrorを発生させる。
Step 1 empty?
• 新しいスタックの empty? は真であること。• テストが失敗することを確認しよう。Red
• Stack#empty? で真を返そう。Fake it!
Step 2 push pop
• 新しいスタックに 3 を push して、pop すると 3が返ること。
• テストが失敗することを確認しよう。Red
• pop で 3 を返そう。Fake it!
Step 3 size
• 新しいスタックに 3 を push するとsize が 1 になること。
• テストが失敗することを確認しよう。Red
• Stack#size で 1 を返そう。Fake it!
Step 4 size
• 新しいスタックに 3 を push した後、5 を push すると size が 2 になること。
• Fake it! が通らなくなることを確認しよう。Triangulate
• テストが通るように修正しよう。
Step 5 empty?
• 新しいスタックに 3 を push するとempty? は偽であること。
• Fake it! が通らなくなることを確認しよう。Triangulate
• テストが通るように修正しよう。
Step 6 pop
• 新しいスタックから pop するとStack::EmptyStackErrorが発生すること。
Step 7 pop
• 新しいスタックに 3 を push した後、5 をpush し、pop すると size が 1 であること。
Step 8 pop
• 新しいスタックに 3 を push した後、5 を push し、pop すると 5 が返ること。
自己紹介• 名前 (ニックネーム)
• 普段の仕事・研究内容・代表作• Ruby歴・コンピュータ歴• 勉強会に来た目的• などなど
参考• るりまhttp://doc.ruby-lang.org/ja/
• サンプルコードhttps://github.com/higaki/
learn ruby kansai 58
• XP祭り関西にてユニットテストの保守に関する発表http://goyoki.hatenablog.com/entry/
20110202/1296663870