フローチャートは砂粒の様な粒度で書けばいいのかもしれないけど、アルゴリズムやワークフレームは大雑把に書いて概要やメソッドのシーケンス(動き)を把握できる方がいい。
面倒なのがワークフローで、UIのテストにも流用できるようとついつい砂粒の様な粒度で書いてしまい、全体がどうなっているのかサッパリ判らなくなり、コードする時に外部(staticっぽい)変数の初期化のタイミングがブレブレで、UIの操作の順(画面1→画面2→画面3とか,画面1→画面3→画面2だったり)で、初期表示で設定する内容がグダグダになりやすい。GUIな画面のテストで操作の順でグダグダになるケースをリストアップするなんて、最悪だ。
例えば、画面1~9へ遷移するボタンがあるメニュー画面を考えてみよう。
このメニュー画面はどういう訳か、ボタンの押す順序でボタンnに対応する画面nの初期状態がバグってしまう事があります。一通り操作して、どんな順序でボタンを押すとバグるのか調べてみましょう。
画面1 | 画面2 | 画面3 |
画面4 | 画面5 | 画面6 |
画面7 | 画面8 | 画面9 |
ここでの「何通り」は、数学で云うところの順列になるので
9P9=(9!)÷(0!)=9!=362,880通り
画面を操作してバグのケースを探し出すのは徹夜しても無理っぽく思える。
しかし、これも【ボタン】と云うコントロールを使用している場合であって
画面の座標から画面nを決定するコードをガリガリ書いていたら、画面の全ドットをクリックするテストケースになってしまうので、まだマシ。
今から40年くらい前にロクなライブラリィが無いのにCUIからGUIへ移行した時期のテストは、
「人数をかき集め好き勝手に画面をマウスで叩かせる」≒100人で実施した≒多分大丈夫
の様なMMORPGのαテスト的なシロモノで、テストケースを見積もると桁違いの数になり「テストケースの見積りを諦めていた」のは「今だから云える」話である。
(閑話休題)
さすがに40年も経つと一部の人は経験を積み、
画面nの中で、外部(staticっぽい)変数を書き換える箇所を無くし、テストケースを日常的な業務量ぐらいに削減でき、テストケースの粒度(?)を
メニューの操作、【画面1】の操作、・・・、【画面9】の操作
と大まかに9通りに縮小できる。(それでも中身は相当な数かもしれない
これがうまくいかないと362,880通りの「ボタンを押すダケ」のテストケースがスポーンするので、とても有用である。
また各画面でも、粗相が無い様にコードしないと、地獄を見ることは云うまでもない。
え?そんなの有り得ない?変数のスコープのブロック化や変数をまとめたクラス化や例外処理のTry~Catch~Finallyで解決済み?
だがその常識は40年くらい前から少しづつ確立していったもので未だ未完成である。
Tryブロックをスコープとする変数をCatchやFinallyで参照できないため、Tryブロックの外に変数を配置しなおす(例外処理のスコープの外へ押し出す)ハメになったことは無いかな?
SqlConnection connection= new SqlConnection(DBConnectionString);
try {
// データベースコネクションを開く
connection.Open();
SqlTransaction transaction= connection.BeginTransaction(IsolationLevel.Serializable);
try {
// データベースを色々操作してみる
SqlCommand command1 = connection.CreateCommand();
command1.CommandText = "SELECT * FROM table001 ORDER BY CategoryID";
command1.CommandTimeout = 15;
command1.CommandType = CommandType.Text;
command1.ExecuteReader();
・・・
SqlCommand command2 = new SqlCommand("INSERT INTO table001(CategoryID) values '001'", transaction.Connection);
command2.Connection.Open();
command2.ExecuteNonQuery()
・・・
// 操作を終えたので、データベーストランザクションをコミットする
transaction.Commit();
} catch (Exception ex) {
// 失敗したらしいので、データベーストランザクションを巻き戻す
transaction.Rollback();
// 失敗したことを通知
throw ex;
} finally {
transaction.dispose();
}
} catch (Exception ex) {
// 失敗したことを通知
throw ex;
} finally {
connection.dispose();
}
C#やVBのusingステートメントは自動的にdisposeし変数を始末してくれるので変数を外に出すことは無くなるが、変数がデータベースのトランザクション・オブジェクトの様にシーケンスな手順がある場合にはusingステートメントの中で try~catchを使い適切なシーケンスを維持するべきだろう。
using (SqlConnection connection= new SqlConnection(DBConnectionString)) {
// データベースコネクションを開く
connection.Open();
using (SqlTransaction transaction= connection.BeginTransaction(IsolationLevel.Serializable)) {
try {
// データベースを色々操作してみる
SqlCommand command1 = connection.CreateCommand();
command1.CommandText = "SELECT * FROM table001 ORDER BY CategoryID";
command1.CommandTimeout = 15;
command1.CommandType = CommandType.Text;
command1.ExecuteReader();
・・・
SqlCommand command2 = new SqlCommand("INSERT INTO table001(CategoryID) values '001'", transaction.Connection);
command2.Connection.Open();
command2.ExecuteNonQuery()
・・・
// 操作を終えたので、データベーストランザクションをコミットする
transaction.Commit();
} catch (Exception ex) {
// 失敗したらしいので、データベーストランザクションを巻き戻す
transaction.Rollback();
// 失敗したことを通知
throw ex;
}
}
}
transactionがusingステートメントに入り見た目も綺麗なコードになり、transaction.dispose()もthrow exも書かずに済むので大助かり。つまり、usingステートメントとtry~catchは補完関係にある。
しかし、処理の粒度は変わらないから、ちょっと短くなりパッとみ綺麗になっただけ。