我是单元测试的新手 - 我只使用纯粹的Testmethods进行了基本的断言测试(我的最后一个模块,我创建了大约50个)。
我目前正在阅读一本关于单元测试的书,本书中的许多例子中的一个让我为每个单独的测试创建一个新类。下面是为一个测试用例创建的示例对象之一。我的问题是有必要这样做吗?或者什么时候应该采用这种方法,什么时候不必要?
public class and_saving_an_invalid_item_type : when_working_with_the_item_type_repository
{
private Exception _result;
protected override void Establish_context()
{
base.Establish_context();
_session.Setup(s => s.Save(null)).Throws(new ArgumentNullException());
}
protected override void Because_of()
{
try
{
_itemTypeRepository.Save(null);
}
catch (Exception exception)
{
_result = exception;
}
}
[Test]
public void then_an_argument_null_exception_should_be_raised()
{
_result.ShouldBeInstanceOfType(typeof(ArgumentNullException));
}
}
答案 0 :(得分:3)
您是否需要为每个单独的测试创建一个新课程?我会说不,你当然不会。我不知道为什么这本书会这么说,或者他们只是为了帮助说明他们的例子。
为了回答你的问题,我建议为每个组测试使用一个类...但它实际上要比这复杂一点,因为你定义“组”的方式是多变的,取决于你当时在做什么。
根据我的经验,一组测试实际上是逻辑结构的,就像一个文档,它可以包含一组或多组测试,通过一些共同的方面组合(有时嵌套)。用于测试面向对象代码的自然分组是按类分组,然后按方法分组。
这是一个例子
不幸的是,在C#或java(或类似的语言)中,你只有两个级别的结构可以使用(而不是你真正想要的3或4),所以你必须要破解东西以适应。
这样做的常用方法是使用一个类将一组测试组合在一起,而不是在方法级别对任何内容进行分组,如下所示:
class TestsForClass1 {
void Test_method1_primary()
void Test_method1_alternate()
void Test_method2_primary()
void Test_method2_alternate()
}
如果您的方法1和方法2都具有相同的设置/拆卸,那么这很好,但有时他们没有,导致这种分解:
class TestsForClass1_method1 {
void Test_primary()
void Test_alternate()
}
class TestsForClass1_method2 {
void Test_primary()
void Test_alternate()
}
如果您有更复杂的要求(假设您对method_1有10个测试,前5个有设置要求X,接下来有5个有不同的设置要求),那么人们通常最终只会制作越来越多的类名:
class TestsForClass1_method1_withRequirementX { ... }
class TestsForClass1_method1_withRequirementY { ... }
这很糟糕,但是嘿 - 方钉,圆洞等等。
就个人而言,我喜欢在方法中使用lambda函数来为你提供第三级分组。 NSpec显示了一种方法可以做到这一点......我们有一个内部测试框架,略有不同,它有点像这样:
class TestsForClass1 {
void TestsForMethod1() {
It.Should("perform it's primary function", () => {
// ....
});
It.Should("perform it's alternate function", () => {
// ....
});
}
}
这有一些缺点(如果第一个It语句失败,其他语句不运行),但我认为这种权衡是值得的。)
- 最初的问题是:“是否真的有必要为我想要执行的每个测试创建一个对象?”。根据这个解释,答案是(大多数情况下)是肯定的。
通常,单元测试涉及两部分的交互
为了使单元测试可靠,每个测试都需要“新鲜”,以确保系统状态良好且可靠。
如果每个测试都没有刷新被测试的东西,那么一个函数可能会改变对象的内部状态,导致下一个测试错误地失败
如果没有为每个测试刷新环境,那么一个函数可能会改变环境(例如:在外部数据库中设置一些变量或其他东西),这可能导致下一个测试错误地失败。
显然很多情况并非如此 - 例如,你可能有一个纯粹的数学函数,它只将整数作为参数而不会触及任何外部状态,然后你可能不想再费心了被测对象或测试环境......但一般来说,任何面向对象系统中的大多数东西都需要刷新,所以这就是为什么这是“标准做法”。
答案 1 :(得分:2)
我无法关注您的示例,但理想情况下,任何测试用例都应该能够独立于任何其他测试用例运行 - 独立于任何其他其他。