使用NUnit的.NET的TDD工作流最佳实践

时间:2011-04-08 17:50:48

标签: c# tdd nunit code-coverage

更新:我对此帖进行了重大更改 - 请查看修订历史记录以获取详细信息。

我开始使用 NUnit 深入 TDD ,尽管我很乐意检查我在stackoverflow找到的一些资源,但我经常发现自己没有获得良好的牵引力。

所以我真正想要获得的是获得某种清单/工作流程 - 这就是我需要你们帮助我的地方 - 或者“测试计划”这将给我良好的代码覆盖率。

所以让我们假设一个理想的情况,我们可以从头开始一个项目,让我们说一个具有以下代码的Mailer助手类:

(我创建这个课程的目的只是为了通过代码示例帮助解决问题,因此鼓励任何批评或建议,并且非常欢迎)

Mailer.cs

using System.Net.Mail;
using System;

namespace Dotnet.Samples.NUnit
{
    public class Mailer
    {
        readonly string from;
        public string From { get { return from; } }

        readonly string to;
        public string To { get { return to; } }

        readonly string subject;
        public string Subject { get { return subject; } }

        readonly string cc;
        public string Cc { get { return cc; } }

        readonly string bcc;
        public string BCc { get { return bcc; } }

        readonly string body;
        public string Body { get { return body; } }

        readonly string smtpHost;
        public string SmtpHost { get { return smtpHost; } }

        readonly string attachment;
        public string Attachment { get { return Attachment; } }

        public Mailer(string from = null, string to = null, string body = null, string subject = null, string cc = null, string bcc = null, string smtpHost = "localhost", string attachment = null)
        {
            this.from = from;
            this.to = to;
            this.subject = subject;
            this.body = body;
            this.cc = cc;
            this.bcc = bcc;
            this.smtpHost = smtpHost;
            this.attachment = attachment;
        }

        public void SendMail()
        {
            if (string.IsNullOrEmpty(From))
                throw new ArgumentNullException("Sender e-mail address cannot be null or empty.", from);

            SmtpClient smtp = new SmtpClient();
            MailMessage mail = new MailMessage();
            smtp.Send(mail);
        }
    }
}

MailerTests.cs

using System;
using NUnit.Framework;
using FluentAssertions;

namespace Dotnet.Samples.NUnit
{
    [TestFixture]
    public class MailerTests
    {
        [Test, Ignore("No longer needed as the required code to pass has been already implemented.")]
        public void SendMail_FromArgumentIsNotNullOrEmpty_ReturnsTrue()
        {
            // Arrange
            dynamic argument = null;

            // Act
            Mailer mailer = new Mailer(from: argument);

            // Assert
            Assert.IsNotNullOrEmpty(mailer.From, "Parameter cannot be null or empty.");
        }

        [Test]
        public void SendMail_FromArgumentIsNullOrEmpty_ThrowsException()
        {
            // Arrange
            dynamic argument = null;
            Mailer mailer = new Mailer(from: argument);

            // Act
            Action act = () => mailer.SendMail();
            act.ShouldThrow<ArgumentNullException>();

            // Assert
            Assert.Throws<ArgumentNullException>(new TestDelegate(act));
        }

        [Test]
        public void SendMail_FromArgumentIsOfTypeString_ReturnsTrue()
        {
            // Arrange
            dynamic argument = String.Empty;

            // Act
            Mailer mailer = new Mailer(from: argument);

            // Assert
            mailer.From.Should().Be(argument, "Parameter should be of type string.");
        }

        // INFO: At this first 'iteration' I've almost covered the first argument of the method so logically this sample is nowhere near completed.
        // TODO: Create a test that will eventually require the implementation of a method to validate a well-formed email address.
        // TODO: Create as much tests as needed to give the remaining parameters good code coverage.
    }
}

所以在我的前两个失败的测试之后,下一个明显的步骤是实现使它们通过的功能,但是,我应该保持失败的测试并在实现代码之后创建新的测试让这些通过,或者我应该在通过之后修改现有的?

对此主题的任何建议都会非常感激。

4 个答案:

答案 0 :(得分:3)

如果安装TestDriven.net,其中一个组件(称为NCover)实际上可以帮助您了解单元测试涵盖的代码量。

除此之外,最好的解决方案是检查每一行,并运行每个测试以确保您至少一次击中该行。

答案 1 :(得分:1)

如果使用像NUnit这样的框架,可以使用AssertThrows这样的方法,在这些方法中,您可以声明方法在输入时抛出所需的异常:http://www.nunit.org/index.php?p=assertThrows&r=2.5

基本上,在给出良好和不良投入的情况下验证预期行为是最佳起点。

答案 2 :(得分:1)

我建议您选择一些像NCover这样的工具,它可以挂钩您的测试用例以提供代码覆盖率统计信息。如果您不想要许可版本,还有一个NCover社区版。

答案 3 :(得分:1)

当人们(最终!)决定将测试覆盖率应用于现有代码库时,测试所有内容是不切实际的;你没有资源,而且往往没有太大的实际价值。

您最理想的做法是确保您的测试适用于新编写/修改的代码以及可能受这些更改影响的任何内容

要做到这一点,你需要知道:

  • 您更改了哪些代码。您的源代码管理系统将在此文件更改的级别为您提供帮助。

  • 执行新代码后执行的代码是什么。为此,您需要一个可以跟踪代码的下游影响的静态分析器(不知道其中的许多代码)或测试覆盖率工具,它可以显示运行特定测试时执行的内容。任何这样执行的代码也可能需要重新测试。

因为您希望最小化您编写的测试代码量,所以您显然希望比“已更改”的文件精度粒度更好。您可以使用diff工具(通常构建到源控件系统中)来帮助将焦点转移到特定的行。差异工具实际上并不理解代码结构,因此他们报告的内容往往是面向行而不是面向结构,产生比必要更大的差异;他们也没有告诉你方便的测试访问点,这可能是一个方法,因为整个单元测试的风格都集中在测试方法上。

你可以获得更好的差异工具。我们的Smart Differencer工具提供了程序结构(表达式,语句,方法)和抽象编辑操作(插入,删除,复制,移动,替换,重命名)方面的差异,这使得更容易解释代码更改。这并没有直接解决“哪种方法改变了?”问题,但这通常意味着要做出更少的事情来做出决定。

您可以获得将回答此问题的测试覆盖率工具。我们的Test Coverage工具可以将先前的测试覆盖率运行与当前的测试覆盖率运行进行比较,以告诉您必须重新运行哪些测试。他们通过检查代码差异(类似于智能差异器)来做到这一点,但将更改抽象回方法级别。