プログラミング言語とテスト
プログラミング言語は概してテストをうまくサポートしていない。
ではどうするか?
- 開発と並行してテストを作成(TDD)
- 時間をかけてテストが容易に実装できるように配慮した設計を行う
巨大な用紙の文字の羅列
プログラムはたくさんのリストでしかなく、一つずつ書いて理解しなければいけない関数群。
一カ所の変更が失敗していて壊れてしまうと動かなくなってしまうもろいものとなる(モノリシック)。
モジュール化できれば、独立して再利用ができる。しかし現実的には再利用は困難。
お互いは独立しているように見えて微妙に依存関係を持っているため。
接合部
単体テストのために個々のクラスを取り出そうとすると、依存関係の排除が必要になる。
設計が優れていると言っても、依存関係の排除にそれなりの作業が発生する。
そもそも、単体テストを行う上で、「優れた」設計とはどういうものなのかの意味合いが変わってくる。
ソフトウェア自体の見方を変える必要がある。
bool CAsyncSslRec::Init()
{
if (m_bSslInitialized) {
return true;
}
...
...
if (!m_bFailureSent) {
m_bFailureSent = true;
PostReceiveError(SOCKETCALLBACK, SSL_FAILURE);
}
...
...
return true;
}
Q1. このメソッドで、以下の処理だけを実行したくない場合はどうするか?
A. その行を削除する
Q2. PostReceiveError自体は別のサブシステムとやり取りするグローバル関数(依存関係を持ってしまっている)だが、テスト時にこの関数を呼び出さずにInit()メソッドを実行するにはどうすればよいか?
A. いろんな回答があるが、まずは接合部について学んでから答える
接合部とは?
接合部(seam)とはその場所を直接編集しなくても、プログラムの振る舞いを変えることができる場所である。
接合部を考えながら依存関係を取り除いてみる
方法としては継承を用いて、メソッドをオーバーライドすることで振る舞いを変える。
手順は次の通り。
- グローバル関数PostReceiveErrorをラップするメソッドをCAsyncSslRecクラスに追加
- 1で定義したメソッドでグローバル関数PostReceiveErrorメソッドを呼び出す
- CAsyncSslRecクラスのサブクラスを作成し、1で作成したメソッドをオーバーライド
- サブクラスをテスト用クラスとして実行する(依存関係は無効化できている)
この手順によって、厄介な副作用を気にせずにコードのテストを書くことができる。
このメソッド呼び出しの部分をオブジェクト接合部(object seam)と呼んでいる。
レガシーコードをテストする上で、最も重要なのが依存関係の排除。ソフトウェアを接合部という観点から見ることで、依存関係を排除する手がかりを見つけることができる(接合部自体が依存関係が発生しているところ)。
接合部で振る舞いを置き換えることができれば、テスト時に依存関係を取り除くことができる。
接合部の種類
許容点について
どの接合部にも許容点がある。
許容点の定義: どの接合部も許容点(enabling point)を持つ。許容点では、どの振る舞いを使うか決定できる。
プリプロセッサ接合部
プリプロセッサがある言語(C, C++, Objective-Cなど)はプリプロセッサを用いて振る舞いを変えることができる。
テストだったり、プラットフォームだったり。
これは、Webフレームワークで言うところの環境変数に近いもの?
TESTとかSTAGINGとかPRODUCTIONとか(その場合の許容点はENV変数)。
リンク接合部
- その名の通りリンクする処理が接合部となり、リンクする対象を変えることで振る舞いを変える
- 同じメソッドを呼び出していても振る舞いが変わる
- 接合部はメソッド内のインスタンス生成
- 許容点はCLASSPATH
オブジェクト接合部
OOで一番役に立つもの。ココだけ覚えておけばよい。
例:他のクラスに関する依存関係の排除(委譲)
抽象クラスCellがあり、それを継承しているクラスValueCell、FormulaCellがあるとする。
public class CustomSpreadsheet extends Spreadsheet
{
public Spreadsheet buildMartSheet() {
...
Cell cell = new FormulaCell(this, "A1", "=A2+A3");
cell.Recalculate();
}
}
上記の記述の仕方だと、buildMartSheet()はFormulaCellクラスの依存している。
以下のように変える。
public class CustomSpreadsheet extends Spreadsheet
{
public Spreadsheet buildMartSheet(Cell cell) {
...
cell.Recalculate();
}
}
buildMartSheetメソッドで利用しているcellオブジェクトは交換可能になった。
接合部はbuildMartSheetメソッドで、許容点はその引数となる。
もう一つの例:メソッドへの依存関係の排除(継承)
public class CustomSpreadsheet extends Spreadsheet
{
public Spreadsheet buildMartSheet(Cell cell) {
...
Recalculate(cell);
}
private static void Recalculate(Cell cell) {
...
}
}
プライベートメソッドでは振る舞いを変更できないので、protectedに変えてオーバーライドする。
public class CustomSpreadsheet extends Spreadsheet
{
public Spreadsheet buildMartSheet(Cell cell) {
...
Recalculate(cell);
}
protected void Recalculate(Cell cell) {
...
}
}
public class TestingCustomSpreadsheet extends CustomSpreadsheet
{
protected void Recalculate(Cell cell) {
...
}
}