Ken Wakita (https://is-prg1b.github.io/lecture/)
Sep. 29, 2017
ひとまず,やる気のないコードを作成
テストを実施するコードを作成(完璧でなくてよい)
以下を繰り返し
テストを実行
テストに合格するようにプログラムを修正
想定外のバグを発見 → バグを再現するテストを追加
目標:西暦(Y)が与えられたときに,その年が閏年か否かを答えるメソッドleapYearを作成しなさい
閏年ニ関スル件・明治三十一年五月十一日勅令第九十号
神武天皇即位紀元年数ノ四ヲ以テ整除シ得ヘキ年ヲ閏年トス
但シ紀元年数ヨリ六百六十ヲ減シテ百ヲ以テ整除シ得ヘキモノノ中更ニ四ヲ以テ商ヲ整除シ得サル年ハ平年トス
神武天皇が即位された年を紀元とする年数(これが紀元年数)が4で割り切れるものを閏年とする
ただし、紀元年数から660を減じたもの(神武天皇の即位の年は西暦-660年とされている)が100で割り切れるもののうち、さらにその商が4で割り切れないもの(つまり、西暦換算が400で割り切れないもの)は平年とする。
グレゴリオ暦では、次の規則に従って400年間に(100回ではなく)97回の閏年を設ける。
西暦年が4で割り切れる年は閏年
ただし、西暦年が100で割り切れる年は平年
ただし、西暦年が400で割り切れる年は閏年
空のコードとテストのファイルを用意する.
// src/leapyear.scala
object LeapYear {
}
// test/leapyear.scala
import org.scalatest._
import LeapYear._
class LeapYearTest extends FlatSpec {
}
やる気のないコードとして、これ以上はないほど愚かなコードを作る.
型だけは仕様に合わせる.
object LeapYear {
def leapyear(y: Int) : Boolean = {
true
}
}
import org.scalatest._
import LeapYear._
class LeapYearTest extends FlatSpec {
}
テストのためのコードを作成
完璧でなくてよい
object LeapYear {
def leapyear(y: Int) : Boolean = {
true
}
}
import org.scalatest._
import LeapYear._
class LeapYearTest extends FlatSpec {
"4で割り切れる年" should "閏年である" in {
assert(leapyear(2012) == (true))
assert(leapyear(2016) == (true))
}
}
曰く「4で割り切れない年は平年」
4で割り切れない年のテストを追加
import org.scalatest._
import LeapYear._
class LeapYearTest extends FlatSpec {
"4で割り切れる年" should "閏年である" in {
assert(leapyear(2012) == (true))
assert(leapyear(2016) == (true))
}
"4で割り切れない年" should "閏年でない" in {
assert(leapyear(2014) == (false))
assert(leapyear(2015) == (false))
assert(leapyear(2017) == (false))
}
}
そこでテストコードの13行目を見る.もちろんテストの内容は正しい.
import org.scalatest._
import LeapYear._
class LeapYearTest extends FlatSpec {
"4で割り切れる年" should "閏年である" in {
assert(leapyear(2012) == (true))
assert(leapyear(2016) == (true))
}
"4で割り切れない年" should "閏年でない" in {
assert(leapyear(2014) == (false))
assert(leapyear(2015) == (false))
assert(leapyear(2017) == (false))
}
}
(テストは正しいので)プログラムの問題を(探すまでもないが)探し
object LeapYear {
def leapyear(y: Int) : Booelan = {
false
}
}
(敢て)小ずる賢い変更を施してみよう
4で割り切れば閏年なんでしょ?
object LeapYear {
def leapyear(y: Int) : Boolean = {
y % 4 == 0
}
}
調子にのってテストを追加
import org.scalatest._
import LeapYear._
class LeapYearTest extends FlatSpec {
"4で割り切れる年" should "閏年である" in {
assert(leapyear(2012) == (true))
assert(leapyear(2016) == (true))
}
// 中略
"100で割り切れる年" should "閏年でない" in {
assert(leapyear(1800) == (false))
assert(leapyear(1900) == (false))
assert(leapyear(2000) == (false))
}
}
object LeapYear {
def leapyear(y: Int) : Boolean = {
!(y % 100 == 0) && y % 4 == 0
}
}
「そもそも,法律施行前の閏年というものは...」
法律の施行は明治32年1月1日 (西暦 1899.1.1) .それ以前については意味がない.
「意味がない」をどうやって表す?
エラーを出力 → 例外処理
「あるかなきか」を表すデータ型 (Option) を利用
Option[A] ::= None | Some[A+]
Option[Boolean]
を利用Option[Boolean] ::= None | Some[Boolean+]
Option[Boolean]
な値は: None
か Some(true)
か Some(false)
class LeapYearTest extends FlatSpec {
// 中略
def compare_option[A](value: Option[A], expected: Option[A]) = {
(value, expected) match {
case (None, None) => true
case (Some(v1), Some(v2)) => v1 == v2
case _ => false
}
}
"1899年前後" should "法律の施行時期を反映" in {
assert(leapyear(1898) == (None))
assert(leapyear(1899) != (None))
}
}
true did not equal Some(true)
: Some(true)
を期待していたのに true
が来たから却下!
object LeapYear {
def leapyear(y: Int) : Option[Boolean] = {
Some(!(y % 100 == 0) && y % 4 == 0)
}
}
式全体を Some(_)
で囲うだけ.
object LeapYear {
def leapyear(y: Int) : Option[Boolean] = {
if (y < 1899) None
else Some(!(y % 100 == 0) && y % 4 == 0)
}
}
[info] Tests: succeeded 4, failed 0, canceled 0, ignored 0, pending 0
天の声「ただし西暦年が400で割り切れる年は閏年」
ということは,2000年とか1600年は閏年?
施行時期のこともあるから1600年は...
TDD手法を用いて,講義資料のここまでの手順をを順次おさらいしなさい.
さらにTDDを自分で実践し,閏年のプログラムを完成しなさい.最終的には早い話が、に記載された仕様を満すとともに天の声にも耳を傾けること.