StopExpectingException
期待どおりに動作することはできません。その理由を理解するには、例外状態での実行の流れを理解することが重要です。
次のコードを検討してください。
procedure InnerStep(ARaiseException);
begin
Writeln('Begin');
if ARaiseException then
raise Exception.Create('Watch what happens now');
Writeln('End');
end;
procedure OuterStep;
begin
try
InnerStep(False); //1
InnerStep(True); //2
InnerStep(False); //3
except
//Do something because of exception
raise;
end;
end;
上記を呼び出すとOuterStep
、 line//2
は内部で例外を発生させますInnerStep
。例外が発生するたびに:
- 命令ポインターは、各メソッドから( に少し似ています)ジャンプして
goto
、呼び出しスタックで見つかった最初のexceptまたはfinallyブロックに移動します。
Writeln('End');
呼び出されません。
- ライン
//3
は呼び出されません。
- のexceptブロックに存在するコードが
OuterStep
次に実行されます。
- 最後に
raise;
が呼び出されると、例外が再発生し、命令ポインタが次のexceptまたはfinallyブロックにジャンプします。
- 同様にレイズすることにも注意してください。例外ブロック内の他の例外も飛び出します (最初の例外を効果的に隠します)。
だからあなたが書くとき:
StartExpectingException(...);
DoSomething();
StopExpectingException(...);
2 つの可能性があります。
DoSomething
例外が発生し、StopExpectingException
呼び出されません。
DoSomething
は例外を発生させず、StopExpectingException
呼び出されたときに例外はありません。
David は、DUnit フレームワークがStopExpectingException
あなたを必要としていると説明しました。しかし、複数の例外シナリオをチェックするテスト ケースにどのようにアプローチするか疑問に思うかもしれません。
オプション1
より小さなテストを作成します。
いずれにせよ、それがあなたがすべきだと誰もが言っていることですよね?:)
例えば
procedure MyTests.TestBadCase1;
begin
ExpectedException := ESomethingBadHappened;
DoSomething('Bad1');
//Nothing to do. Exception should be raised, so any more code would
//be pointless.
//If exception is NOT raised, test will exit 'normally', and
//framework will fail the test when it detects that the expected
//exception was not raised.
end;
procedure MyTests.TestBadCase2;
begin
ExpectedException := ESomethingBadHappened;
DoSomething('Bad2');
end;
procedure MyTests.TestGoodCase;
begin
DoSomething('Good');
//Good case does not (or should not) raise an exception.
//So now you can check results or expected state change.
end;
オプション 2
David が提案したように、テスト内に独自の例外処理を記述できます。ただし、少し面倒になる可能性があることに注意してください。ほとんどの場合、オプション 1 をお勧めします。特に、明確に名前が付けられたテストにより、何が問題だったかを正確に特定しやすくなるという追加の利点がある場合は特にそうです。
procedure MyTests.TestMultipleBadCasesInTheSameTest;
begin
try
DoSomething('Bad1');
//This time, although you're expecting an exception and lines
//here shouldn't be executed:
//**You've taken on the responsibility** of checking that an
//exception is raised. So **if** the next line is called, the
//expected exception **DID NOT HAPPEN**!
Fail('Expected exception for case 1 not raised');
except
//Swallow the expected exception only!
on ESomethingBadHappened do;
//One of the few times doing nothing and simply swallowing an
//exception is the right thing to do.
//NOTE: Any other exception will escape the test and be reported
//as an error by DUnit
end;
try
DoSomething('Bad2');
Fail('Expected exception for case 2 not raised');
except
on E: ESomethingBadHappened do
CheckEquals('ExpectedErrorMessage', E.Message);
//One advantage of the manual checking is that you can check
//specific attributes of the exception object.
//You could also check objects used in the DoSomething method
//e.g. to ensure state is rolled back correctly as a result of
//the error.
end;
end;
注意!注意!オプション 2 で注意すべき非常に重要なことがあります。飲み込む例外クラスに注意する必要があります。DUnit のFail()
メソッドはETestFailure
例外を発生させ、テストが失敗したことをフレームワークに報告します。また、予期される例外のテストの失敗をトリガーする例外を誤って飲み込みたくはありません。
例外テストに関連する微妙な問題により、最初にテストし、正しい失敗があることを確認してから、合格を得るために製品コードの変更を実装することが重要になります。このプロセスにより、不発試験の可能性が大幅に減少します。