4

下面的代码工作正常, calc... 生成一个异常,将其注释掉或将 calc... 更改为不抛出异常并且测试失败。

  StartExpectingException(exception);
  calcMembersPIPEndDate(EncodeDate(2005,01,01),true);
  StopExpectingException('calcMembersPIPEndDate - 1st after aDay');

我的问题是,在此之后我在此测试方法中进行的任何检查都不会执行。
所以

  checkEquals(1,0);
  StartExpectingException(exception);
  calcMembersPIPEndDate(EncodeDate(2005,01,01),true);
  StopExpectingException('calcMembersPIPEndDate - 1st after aDay');

第一次 checkEquals 失败

  StartExpectingException(exception);
  calcMembersPIPEndDate(EncodeDate(2005,01,01),true);
  StopExpectingException('calcMembersPIPEndDate - 1st after aDay');
  checkEquals(1,0);

通过 - 为什么?

我试图弄清楚我使用的是什么版本的 Dunit:

testframework.pas has the following - which didn't seem to 
rcs_id: string = '#(@)$Id: TestFramework.pas,v 1.117 2006/07/19 02:45:55
rcs_version : string = '$Revision: 1.117 $';
versioninfo.inc
ReleaseNo : array[1..3] of Integer
          = (9,2,1);
ReleaseStr     = '9.2.1';
ReleaseWhen : array[1..6] of Integer
          = (2005,09,25,17,30,00);
4

2 回答 2

4

这两个方法,StartExpectingException并不StopExpectingException意味着直接调用。

相反,您应该使用该ExpectedException属性。当你设置这个属性时,StartExpectingException被调用。虽然你可以打电话给StartExpectingException我相信预期的用途是你分配给ExpectedException.

至于StopExpectingException,你不叫它。框架调用它。它在TTestCase.RunTest执行您的测试方法的框架代码中执行此操作。

因此,您的测试用例代码可能如下所示:

ExpectedException := ESomeException;
raise ESomeException.Create(...);

当您声明您期待异常时,您的意思是您的测试方法将引发该异常。由于引发异常会改变控制流,因此引发异常后出现的代码将不会执行。异常会向上传播调用堆栈,直到它们被捕获。框架将在TTestCase.RunTest. 如果您已指示预期捕获的异常,则测试将通过,否则将记录失败。

所有这一切的最终结果是,ExpectedException如果测试方法的最终行为是引发预期的异常,则可以使用该机制。如果您想在引发异常后执行进一步的测试,则该ExpectedException机制根本没有用。如果您希望这样做,那么您应该:

  1. 在您的测试方法中编写您自己的异常处理代码,检查异常是否按设计引发。
  2. 使用CheckException.
于 2016-03-21T12:53:29.090 回答
2

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。现在每当引发异常时:

  • 指令指针从每个方法(有点像)跳到调用堆栈中找到的第一个exceptfinally块。goto
  • Writeln('End');不会被调用。
  • //3不会调用线路。
  • 接下来执行except块中存在的任何代码。OuterStep
  • finallyraise;被调用时,异常被重新引发,指令指针跳转到下一个exceptfinally块。
  • 还要注意像raise; except块中的任何其他异常也会跳出,(有效地隐藏第一个异常)。

所以当你写:

StartExpectingException(...);
DoSomething();
StopExpectingException(...);

有2种可能:

  1. DoSomething引发异常并且StopExpectingException永远不会被调用。
  2. 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异常以向框架报告测试失败。而且您不希望意外吞下会触发预期异常测试失败的异常。

与异常测试相关的细微问题很重要:首先测试,确保您有正确的失败,然后才实施生产代码更改以获得通过。该过程将显着减少哑测试的机会。

于 2016-11-05T04:07:43.330 回答