测试是否抛出正确的异常(Test throwing the right exception)

xiaoxiao2025-02-05  14

[b]问题:[/b] 你是否想过异常也要去测试?你想了怎么验证一个方法是否在某种特定的情况下抛出期望的异常,也许你正在找是否要测试,以及有没有简单的测试方法 [b]背景:[/b] 要想知道任何实现这种测试,你需要了解JUnit如何判定一个测试时通过还是失败。如果一个断言失败或者抛出一个异常的时候,测试就会失败,否则测试就通过。换句话说,如果一个测试全部走完,就是说程序从头运行到了尾,而没有从中间跳过,那么它就通过了。知道了这些,就足够你推断出然后写这种测试了:如果该抛出异常的代码段没有抛出异常,那么这个测试就应该失败;测试只能捕捉期望的异常;任何其他的异常都应该由JUnit框架捕捉。 [b]诀窍:[/b] 下面的代码展示了,如何写这种验证是否抛出正确异常的测试示例: public void testConstructorDiesWithNull() throws Exception{ try{ Fraction oneOverZero = new Fraction(1,0); fail("Created fraction 1/0! That's undefined!"); } catch(IllegalArgumentException excepted){ assertEquals("denominator",excepted.getMessage()); } } 首先分析上面的代码 1. 找到可能抛出异常的代码段,将它放入一个try语句内。 2. 调用了应该抛出异常的方法以后,在try语句内写一个fail()方法来说明:“如果运行到了这里,那么说明期望的异常没有被抛出”。 3. 添加一个catch语句以捕获期望的异常。 4. 在catch语句内,如果需要的话,验证捕获的异常的属性与你期望的相同。 5. 声明该测试方法会抛出异常,这可以让代码适应性更强。有人可能会在测试程序外声明这个方法可抛出其他的异常,这种变化不应该影响你的测试,因此它不应该导致你的测试不能被编译。 [b]讨论:[/b] 如果测试的方法抛出其他的异常---------不同于你要捕获的异常-----那么JUnit将报告一个error,而不是failure,因为测试方法将一个异常抛出给JUnit框架。记住这样的error一般是环境或者测试程序自身的错误,而不是产品代码的问题。如果产品代码抛出了一个预期之外的异常,那么一般可能是潜在的问题阻碍了测试的正常运行。 [b]一个更面向对象的解决办法[/b] 先看一个匿名内部类 public void testForException(){ assertThrows(MyException.class, new ExceptionClosure(){ public Object execute(Object input) throws Exception{ return doSomethingThatShouldThrowMyException(); } }); } 虽然有人觉得Java的匿名内部类不太好读,但这个方法的意图再明显不过了:"测试这段代码是否抛出期望的异常"。可以按如下的方式实现assertThrows()方法: public static void assertThrows(Class expectedExceptionClass, ExceptionalClosure closure){ String expectedExceptionClassName = expectedExceptionClass.getName(); try{ closure.execute(null); fail("Block did not throw an exception of type"+expectedExceptionClassName); }catch(Exception e){ assertTrue("Caught exception of type <" + e.getClass().getName() +">, expected one of type <" +expectedExceptionClassName+">", expectedExceptionClass.isInstance(e));}} 我们捕捉了所有的异常,而不仅是期望的异常,这是因为当编译的时候,我们还不知道代码会抛出什么样的异常。如果这样设置断言,那么错误信息就很重要,因为你取走了错误信息的控制权。另外一个可选的方案是,在assertThrows()方法中添加另外一个参数,用来接收自定义的异常,最后,因为我们必须测试所有的异常,我们必须将捕获的异常是否是期望的异常的实例,这与使用instanceof()方法是一样的。 [b]注意写的断言:[/b] 如果你的断言与特定的异常相关,那么你要小心:如果验证异常对象的结构过于紧凑,那么可能导致测试与产品代码耦合得过强。这时候,测试的结果可能有点不太可靠。假设异常信息是给终端用户看,而你要写直接验证该消息的断言。一般来说,你会照着如下的方式编写测试代码: public void testNameNoEntered(){ try{ log("", "password"); fail("User logged in without a user name !"); } catch(MissingEntryException expected){ assertEquals("userName", expected.getEnteryName()); assertEquals("Please enter a user name and try again", expected.getMessage()); } } 测试代码看上去很清楚:如果一个用户不输入用户名的情况下登录,那么登录模块就抛出一个MissingEntryException。这个异常包含了登录所缺少的必要项的名称,以及提供给终端用户的信息。这看起来很好,因为这个异常对象包含的信息非常简单,它包含的数据像终端用户读取的信息一样易于理解。虽然catch语句中的第一断言写得不错,但是第二个有点不同的意见,虽然userName是程序的内部名称,并且是行为的一部分,但给终端用户看的信息可以在不影响登录功能的前提下随时改变。换句话说,如果userName改变的话,测试程序就需要随之改变。然而,测试似乎不应该随着终端用户信息的改变而改变,由于测试时现在写的,将来任何属性值的改变,都会要求测试程序随之改变。 这个例子中,我们建议去除第二个断言而仅保留第一个,因为涉及的一般准则是:将给终端用户看的信息与内部对象的行为相分离。你可以经常简单地初始化应该异常对象,然后检查它的toString()以及getMessage()方法的返回值。
转载请注明原文地址: https://www.6miu.com/read-5024012.html

最新回复(0)