在很多TDD教程中,我看到如下代码:
public class MyClass
{
public void DoSomething(string Data)
{
if (String.IsNullOrWhiteSpace(Data))
throw new NullReferenceException();
}
}
[Test]
public DoSomething_NullParameter_ThrowsException()
{
var logic = MyClass();
Assert.Throws<NullReferenceException>(() => logic.DoSomething(null));
}
这一切都有意义,但在某些时候你会到达实际使用MyClass的类,并且你想测试是否处理了异常:
public class EntryPointClass
{
public void DoIt(string Data)
{
var logicClass = new MyClass();
try
{
logicClass.DoSomething(Data);
}
catch(NullReferenceException ex)
{
}
}
}
[Test]
public DoIt_NullParameter_IsHandled()
{
var logic = new EntryPointClass()
try
{
logic.DoIt(null);
}
catch
{
Assert.Fail();
}
}
那么为什么不把my / class中的try / catch放在首先,而不是抛出异常,并在MyClass单元测试类中测试为null,而不是在EntryPointClass单元测试类中?
答案 0 :(得分:3)
通常,您的异常处理将如下所示:
public class EntryPointClass
{
Logger _logfile;
// ...
public void DoIt(string Data)
{
var logicClass = new MyClass();
try
{
logicClass.DoSomething(Data);
}
catch(NullReferenceException ex)
{
_logfile.WriteLine("null reference exception occured in method 'DoIt'");
}
}
}
(Logger
只是您在MyClass
所在地不可用的正确异常处理所需资源的示例。您还可以在此处添加消息框的显示或类似内容这一点。)
在MyClass
级别,您通常没有适当的工具可用于正确的异常处理(并且您不希望在那里添加它们以保持类与特定的分离,例如,特定的记录机制)。
请注意,此设计决策与执行TDD无关。如果您希望您的班级MyClass
实际上自己捕捉异常,您必须以不同的方式编写测试,这是正确的。但是,如果捕获异常并不会阻止您的自动测试,那么这只是一个好主意。例如,当MyClass
单独显示硬编码警告对话框时,请尝试为MyClass
编写一个良好的单元测试。
当然,上面的示例表明,当您第一次需要像记录器这样的东西来实现工作时,单元测试EntryPointClass
可能会变得更难。通常,您可以通过使用ILogger接口在构造时提供记录器来解决此问题,该接口允许使用mock替换记录器。或者在这个简单的情况下,根本不要为单元测试初始化记录器并将其编码为:
public class EntryPointClass
{
// ....
catch(NullReferenceException ex)
{
if(_logfile!=null)
_logfile.WriteLine("null reference exception occured in method 'DoIt'");
}
// ....
}
答案 1 :(得分:1)
例外情况表明代码的异常情况(请参阅Exception Handling上的MSDN指南)。如果DoSomething
的参数null是例外,那么无论 和 将如何使用该类,抛出它都是完全合理的。值得强调的是Exception Throwing部分正在详细描述它:
通过抛出异常来报告执行失败。如果某个成员无法成功完成它的设计,那么应该将其视为执行失败并抛出异常。
更不用说,在撰写DoSomething
时,你可能不知道会使用什么代码(实际上你不应该知道,也不关心)。
因此,如果异常抛出是代码合同的一部分 - 它应该进行测试,并且应该作为DoSomething
测试的一部分进行测试。
答案 2 :(得分:1)
为什么不把try / catch放在MyClass中呢......
例外是用于解耦错误检测和错误处理。如果无法在MyClass中本地处理错误(在所有情况下),那么您应该抛出异常。
这允许本地错误检测(MyClass)和任何捕获异常的人的错误处理。
EntryPointClass
测试中的测试,如果方法DoIt是空安全的。它与MyClass的使用没什么关系。
换句话说:EntryPointClass
中的测试没有(和代码稳定性和封装问题)不应该依赖于使用MyClass
的事实。
答案 3 :(得分:1)
单元测试测试工作单元
编写一个测试异常的测试然后将代码写入该规范并观察测试通过(这是您的第一个示例)
然后编写处理异常的代码(如果可能的话),如果这样做,则测试不应该考虑该异常,因为您已经处理了它,并且您应该测量该方法预期的输出。
如果您无法处理异常,则应省略catch步骤(除非您在此阶段进行日志记录)并在此情况下让它上升到堆栈,您再次断言该方法需要异常。
你永远不会在你的测试代码中加上try catch,你只断言是否抛出异常。
当您测试班级的正确功能时。
答案 4 :(得分:1)
您实际上正在测试两种非常不同的条件: