Ken Wakita (https://prg1-2019.github.io/lecture/web/)
2019-10-15
ひとまず,やる気のないコードを作成
(※レシピの「Step 2:関数定義の準備」を参照)
テストを実施するコードを作成
(※レシピの「Step 3:入出力の例」に沿ってテストコードを記述)
以下を繰り返し
テストを実行
テストに合格するようにプログラムを修正
想定外のバグを発見 → バグを再現するテストを追加
目標:西暦(y
)が与えられたときに,その年が閏年か否かを答える関数isLeap1を作成しなさい.
明治三十一年勅令第九十号(閏年ニ関スル件・明治三十一年五月十一日勅令第九十号
神武天皇即位紀元年数ノ四ヲ以テ整除シ得ヘキ年ヲ閏年トス
但シ紀元年数ヨリ六百六十ヲ減シテ百ヲ以テ整除シ得ヘキモノノ中更ニ四ヲ以テ商ヲ整除シ得サル年ハ平年トス
グレゴリオ暦では、次の規則に従って400年間に(100回ではなく)97回の閏年を設ける。
西暦年が4で割り切れる年は閏年
ただし、西暦年が100で割り切れる年は平年
ただし、西暦年が400で割り切れる年は閏年
やる気のないコードとして、これ以上はないほど愚かなコードを作る.型だけは仕様に合わせる.
sbt:lx05> runMain b.A
[warn] Multiple main classes detected. Run 'show discoveredMainClasses' to see the list
[info] Running (fork) b.A
[info] おめでとうございます。すべてのテストをパスしました!
[success] Total time: 1 s
テストのためのコードを作成 – 完璧でなくてよい
テストは成功!
もしかして完成しちゃった?
sbt:lx05> runMain c.A
[warn] Multiple main classes detected. Run 'show discoveredMainClasses' to see the list
[info] Running (fork) c.A
[info] おめでとうございます。すべてのテストをパスしました!
[success] Total time: 1 s
曰く「4で割り切れない年は平年」
4で割り切れない年のテストを追加
object A extends App {
import Calendar._ // Calendarオブジェクト内の定義をすべて読み込む
{
val msg = "4で割り切れる年は閏年である"
assert(isLeap(2016), msg)
assert(isLeap(2020), msg)
}
{
val msg = "4で割り切れない年は閏年ではない"
assert(!isLeap(2017), msg)
assert(!isLeap(2018), msg)
assert(!isLeap(2019), msg)
}
println("おめでとうございます。すべてのテストをパスしました!")
}
当然、テストは失敗する。
sbt:lx05> runMain d.A
[warn] Multiple main classes detected. Run 'show discoveredMainClasses' to see the list
[info] Running (fork) d.A
[error] Exception in thread "main" java.lang.AssertionError: assertion failed: 4で割り切れ
ない年は閏年ではない
[error] at scala.Predef$.assert(Predef.scala:219)
[error] at d.A$.delayedEndpoint$d$A$1(d.scala:33)
[error] at d.A$delayedInit$body.apply(d.scala:22)
...
☆☆☆ とても長いスタックトレースはバッサリと省略 ☆☆☆
もちろん33行目付近のテストは正しい。
(探すまでもなく,明らかだが)、以下を修正して,leapyear(2001) → false となるようにすればよい.
でも、ここで敢て(半端に)ずる賢い変更を施してみよう
4で割り切れば閏年なんでしょ?
sbt:lx05> runMain e.A
[warn] Multiple main classes detected. Run 'show discoveredMainClasses' to see the list
[info] Running (fork) e.A
[info] おめでとうございます。すべてのテストをパスしました!
[success] Total time: 1 s
こけるのは想定内(TDDだからね)
sbt:lx05> runMain f.A
[warn] Multiple main classes detected. Run 'show discoveredMainClasses' to see the list
[info] Running (fork) f.A
[error] Exception in thread "main" java.lang.AssertionError: assertion failed: 100で割り切
れる年は閏年ではない
[error] at scala.Predef$.assert(Predef.scala:219)
[error] at f.A$.delayedEndpoint$f$A$1(f.scala:42)
[error] at f.A$delayedInit$body.apply(f.scala:24)
[error] at scala.Function0.apply$mcV$sp(Function0.scala:34)
[error] at scala.Function0.apply$mcV$sp$(Function0.scala:34)
...
☆☆☆ とても長いスタックトレースはバッサリと省略 ☆☆☆
sbt:lx05> runMain g.A
[warn] Multiple main classes detected. Run 'show discoveredMainClasses' to see the list
[info] Running (fork) g.A
[info] おめでとうございます。すべてのテストをパスしました!
[success] Total time: 1 s
曰く「ただし西暦年が400で割り切れる年は閏年」
400で割り切れる例外を忘れてた!
sbt:lx05> runMain i.A
[warn] Multiple main classes detected. Run 'show discoveredMainClasses' to see the list
[info] Running (fork) i.A
[info] おめでとうございます。すべてのテストをパスしました!
[success] Total time: 1 s
曰く「勅令の施行日というものを知っておるかな?」
世の中には、明示されない仕様というものがあります
y < 1898年
を入力仕様違反として無視するアプローチ
y < 1898年
ならエラーにするアプローチ
y < 1898年
なら未定値とするアプローチ
y < 1898年
を入力仕様違反として無視ご存知assert
を利用する。
y < 1898年
ならエラーassert
vs require
assert
Tests an expression, throwing an AssertionError
if false. Calls to this method will not be generated if -Xelide-below is greater than ASSERTION.
ソフトウェア製品版は assert
を省略するようにコンパイラを設定することが普通(検査のコストを減ずるため)
require
Tests an expression, throwing an IllegalArgumentException
if false. This method is similar to assert, but blames the caller of the method for violating the condition.
どんな状況においても、この検査は実施される
検査が失敗したときに発せられる例外が異なることにも注意
y < 1898年
なら未定値Option[T]
型
値が定まっていない場合: None
値(v : T
)が定まってる場合: Some(v)
Option[T]
型から値を取り出すときはパターンマッチ
def assertEq(y: Int, b: Boolean): Unit = {
isLeap(y) match {
case None => assert(false)
case Some(x) => assert(x == b)
}
}
{
val msg = "4で割り切れる年は閏年である"
assertEq(2016, true)
assertEq(2020, true)
}
... ばっさりと省略 ...
{
val msg = "勅令施行前は未定義"
assert(isLeap(1600) == None)
assert(isLeap(1800) == None)
assert(isLeap(1897) == None)
}
{
val msg = "勅令施行後は通常"
assertEq(1898, false)
assertEq(1899, false)
assertEq(1900, false)
}
Boolean
→ Option[Boolean]
rotate
(テストが書けないから、コーディングできないとか?)