单元测试是否应该内联声明?

时间:2011-02-07 17:17:04

标签: .net unit-testing

我一直是Python doctest库的粉丝,原因很简单,注释不仅有用,而且可用于断言正确的行为。我最近偶然发现了(貌似)鲜为人知的System.Diagnostics.ConditionalAttribute for .NET。这可以很容易地用于允许您在类本身内定义类方法的测试。这是一个简单的例子:

using System.Diagnostics;
using NUnit.Framework;

namespace ClassLibrary1
{
    public class Class1
    {
        public static int AddTwoNumbers(int x, int y)
        {
            return x + y;
        }

        [Conditional("DEBUG")]
        [TestCase(1, 1, 2)]
        [TestCase(1, 2, 3)]
        [TestCase(2, 1, 3)]
        [TestCase(11, 7, 18)]
        public static void TestAddTwoNumbers(int x, int y, int sum)
        {
            int actual = AddTwoNumbers(x, y);
            Assert.AreEqual(sum, actual);
        }
    }
}

执行此操作,您可以创建一个调试程序集,该程序集将运行测试和生产程序集,并将其全部删除,类似于FAKE can build projects的方式。问题是,你呢?这是一个好习惯吗?为什么或为什么不呢?

你会进一步发现这个例子实际上并没有像我期望的那样工作。我不确定为什么该属性允许编译测试方法。关于为什么的任何想法?

4 个答案:

答案 0 :(得分:4)

ConditionalAttribute不会更改方法本身是否已编译。它会改变是否生成方法的调用。

例如,Debug.WriteLine已应用Conditional("DEBUG") - 但代码仍然存在。关键是包含对Debug.WriteLine的调用的 client 代码在没有定义DEBUG预处理器符号的情况下构建时将忽略这些调用。

要有条件地编译整个方法,您可以使用:

#if DEBUG
...
#endif

即使把它放在一边,我也不会自己做。我喜欢将生产代码与测试代码分开。我发现它更清晰 - 尽管它确实意味着我无法测试私有方法。 (有些人说你永远不应该测试实现细节,但那是一个完全不同的问题。)

还有针对真实代码进行测试的问题。如果您要构建一个版本的代码,其中包含内置测试而没有内置测试,并且您在生产中使用非测试程序集,则意味着您正在运行尚未测试的代码。当然,它可能与DEBUG定义的方式相同......但是如果不是这样的话呢?我喜欢能够针对完全相同的二进制文件运行单元测试,然后在生产中使用。

答案 1 :(得分:2)

(IMO)绝对不是。

虽然使用Debug属性将使方法不会在您的发布项目中公开,但事实是在开发时(将在DEBUG模式下,很可能),这将污染根据您开发的测试用例数量(并且您可以开发许多测试用例,即使对于占用空间小的类),也可以上课。

另外,我认为它的封装很差。您正在寻找为类编写测试,这些测试实际上无助于提供或增强类的实际功能,因此,不应该是它的一部分。

最后,单独测试工具的一大优势是,您可以在多个类之间设置复杂的相互依赖关系,允许您测试这些类之间的交互。

在您的情况下,愿景的范围是一个单一的类。如果你需要引入其他类(模拟实现等)那么你会把它们放进去吗?

答案 2 :(得分:2)

这种方法会有利有弊。

一方面,您将能够测试内部。有些人会认为这是件好事。其他人会争辩说你应该只测试公共接口。就个人而言,我认为偶尔需要测试内部(如果没有其他原因而不是隔离特定行为),但这是你可以用InternalsVisibleTo实现的。

另一方面,您将能够测试内部。您的测试代码的行为就像它属于程序集一样。我个人认为它不是申请的一部分,不应该在那里。将其视为“关注点分离”的一种形式。测试测试,应用程序执行测试测试的事情。但是测试应该尽可能少地了解实现细节。如果测试是在内部,那么他们很容易知道所有细节。如果稍后更改实现细节,则测试将失效并且必须重写。

就个人而言,我更喜欢外部装配。它不仅强制执行我的测试只测试软件的概念,而且它让我问自己如何编写软件,以便它可以由外部源测试。这导致整体软件设计更好。我还没后悔。

答案 3 :(得分:0)

我同意其他大多数答案:最好将单元测试分开。特别注意Jon关于ConditionalAttribute如何工作的观点:特别是代码仍然在那里,它只是没有被调用。

但是,我想你想Code Contracts。它们实际上使用了重写器,在编译后修改代码。这允许您轻松设置包含所有类型的运行时检查的Debug构建以及没有任何代码的Release构建。您还可以安装VS扩展,在代码编辑器中显示前置和后置条件作为弹出窗口。

我的博客上有一个简短的getting started post,我在其中为图书馆设置CC。我的风格是拥有一个Debug构建(带有完整检查)和一个Release构建(没有检查,但包含一个包含前置条件的单独dll)。

请注意,代码合同不是单元测试的替换,但它是一个很好的补充。