20160607 ss2016 fp
TRANSCRIPT
ソリューション・サービス開発本部 秋山 浩一
非連続な実装を持つ連続値の境界値分析の考察
© 2016 Fuji Xerox Co., Ltd. All rights reserved.
~ IK問題 ~
2016年6月7日(火) SS2016
1
2
(テスト技法の)同値分割と境界値分析の基本のおさらい
線で解く
定型封書料金は~25g(80円)、25g~50g(90円)と変化する
線分を書いて、4つの領域(同値クラス)を見つけ、-20g, 15g, 30g, 70g
をテストデータとして、エラー, 80円, 90円, 重さに応じた料金をテストする
※ A4=約4g……A4×5枚
同値図で解く
0 25 50
-1g、0g(、1g) (24g、)25g、26g (49g、)50g、51g
-20g 15g 30g 70g
25gまで
25g超え50gまで
50g超え
80円
90円
従量
エラー
-20g
15g
30g
70g
同値図は、集合のベン図のようなもの(オイラー図とよぶ)。 重ならない(=分割されている)ことが重要。
エラー結果については 線を引いて区別する
© 2016 Fuji Xerox Co., Ltd. All rights reserved.
境界値: テストしてバグが見つかりやすい
3
同値クラスの要素に何らかの意味での順序がある(数値化可能な)場合、その同値クラスは境界値を持つ(『ソフトウェアテスト実践ワークブック』より)
前ページでは重さが要素
たとえば、-20gから1,000gの1g刻みという要素
-20g, -19g, -18g, …… -2g, -1g → エラーの同値クラス
0g, 1g, 2g, …… 24g, 25g → 80円の同値クラス
26g, 27g, 28g, …… 49g, 50g → 90円の同値クラス
51g, 52g, 53g, …… 999g, 1,000g → 従量課金の同値クラス
確かに要素に順序関係がある! 順に並べた端が境界値(赤字)
仕様で示された同値クラスの境界値をONポイントと呼ぶ
ONポイントに最も近い境界を挟んだ値をOFFポイントと呼ぶ
「<」と「>」の書き間違え(=実装)よりも実は要求(仕様)が分らない
境界値の仕様が曖昧 (誕生日の前日の24時に年を取る、日付で判断するときに混乱する)
一般常識と業界常識のずれ(放送局の時間は午前4時とは言わず28時)
ユーザーの認識と実装とのずれ ← 今回のテーマ
© 2016 Fuji Xerox Co., Ltd. All rights reserved.
IK問題とは
4
(うるう年である)2016年に素数はいくつ存在するか
年月日を「YYYYMMDD」の8ケタの数値で表現したときに 「20160229」は素数か否か → 否: 929×21701
2016年に素数はいくつ? → 19個?(秋山の20数行の汚いコード)
IKさんから「追試したら19個だった」と情報をいただく ruby -rdate -rprime –e ‘p (Date.new(2016,1,1)..Date.new(2016,12,31)).select{|date|date.strftime(“%Y%2m%2d”).to_i.prime?}.size
rubyの素数パッケージを使ってあっけなく125文字で実装
しかし弘法も筆の誤り(IK問題)
その後、IKさんから「上記はコメントを書くと140文字に収まらないので」と、 ruby -rdate -rprime -e ‘p (0..366).count{|i|(http://Date.new(2016,1,1)+i).strftime(“%Y%m%d”).to_i.prime?}‘ という別解(106文字) → しかし、境界値にまつわるバグがあった
「(0..366)」では367日分チェックする → 正解は「(0...366)」
© 2016 Fuji Xerox Co., Ltd. All rights reserved.
デバッグ時の回帰テスト
5
バグ修正は「.」を追加し「(0...366)」として366を含まなくするだけ
どういうデータで回帰テストすべきか?
計算の範囲は1/1~12/31なので、この境界のONポイントとOFFポイント
当該年の1/1と12/31に加えて、前年の12/31と翌年の1/1の4つの全てが素数である年をテストする
© 2016 Fuji Xerox Co., Ltd. All rights reserved.
1月 2月 3月 …MM月… 12月
前 年
20151231:OFF
当 該 年
20160101:ON 20160102
…… 20160130 20160131
20160201 20160202
…… 20160228 20160229
20160301 20160302
…… 20160330 20160331
2016MM01 2016MM02
…… 2016MM30
(2016MM31)
20161201 20161202
…… 20161230
20161231:ON
翌 年
20170101:OFF
20160132 20160133
…… 20160198 20160199
20160230 20160231
…… 20160298 20160299
20160332 20160333
…… 20160398 20160399
2016MM32 2016MM33
…… 2016MM98 2016MM99
20161232 20161233
…… 20161298 20161299
デバッグ時の回帰テストの十分性
6
バグ修正は「.」を追加し「(0...366)」として366を含まなくするだけ
どういうデータで回帰テストすべきか?
計算の範囲は1/1~12/31なので、この境界のONポイントとOFFポイント
当該年の1/1と12/31に加えて、前年の12/31と翌年の1/1の4つの全てが素数である年をテストする
5億年分チェックしたが見つからない(8コア並列で丸一日)
(4つ全てが素数の年が)無いことの証明は、いわゆる「悪魔の証明」であるので「チェックした範囲では存在しない」としか言えない
ところが数学的には簡単に(1時間もかからずに)証明できた
© 2016 Fuji Xerox Co., Ltd. All rights reserved.
本事例からの学び
7
日付は連続的に増えていくが、年月日を8ケタの数値で表現した場合は 非連続で増加する
実装上の境界が生じていた
日常的な感覚で連続性があっても実装上は飛び地(値)があることがある
(テストの必要性とは別に)内部的に発生している境界に気がつく必要がある
今回の境界値で本当によかったのか?
うるう年ということで366という値をプログラムに書き範囲式を誤る
一つ注意したことで気持ちの中の警戒心が緩んだ
安心感が欠陥を誘発するエラーパターンに注意すべき
一つの解決手段にこだわり過ぎると失敗する
数学的に証明しようと思えば1時間もかからずに済む問題が、 プログラミングで答えを求めようとこだわると結果が得られなかった
© 2016 Fuji Xerox Co., Ltd. All rights reserved.