使用NUnit的.net TDD工作流最佳实践
本文关键字:最佳 工作流 TDD NUnit net 使用 | 更新日期: 2023-09-27 17:49:44
更新:我对这篇文章做了重大修改,详细信息请查看修订历史。
我开始深入研究TDD与NUnit,尽管我很喜欢检查我在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.
}
}
因此,在我的前两个失败的测试之后,下一个明显的步骤将是实现功能,使它们通过,但是,我应该保留失败的测试,并在实现代码后创建新的测试,使它们通过,还是我应该修改现有的测试,使它们通过?
关于这个话题的任何建议都将非常感谢。
如果您安装了TestDriven.net,其中一个组件(称为NCover)实际上可以帮助您了解单元测试覆盖了多少代码。
除此之外,最好的解决方案是检查每一行,并运行每个测试以确保您至少击中了该行一次。
我建议您选择一些像NCover这样的工具,它可以连接到您的测试用例以提供代码覆盖率统计。如果您不想要授权版本,也可以使用社区版的NCover。
如果你使用像NUnit这样的框架,有一些可用的方法,比如AssertThrows
,你可以断言一个方法在给定输入的情况下抛出所需的异常:http://www.nunit.org/index.php?p=assertThrows&r=2.5
基本上,验证给定好的和坏的输入的预期行为是最好的起点。
当人们(最终!)决定将测试覆盖应用于现有的代码库时,测试所有内容是不切实际的;你没有资源,而且通常没有很多真正的价值。
理想情况下,您要做的是确保您的测试适用于新编写/修改的代码以及可能受这些更改影响的任何内容。
要做到这一点,你需要知道:
-
您更改的代码。你的源代码管理系统会在this-file-changed级别帮助你。
-
作为执行新代码的结果而执行的代码。为此,您需要一个可以跟踪代码下游影响的静态分析器(不知道很多),或者一个测试覆盖工具,它可以显示在您运行特定测试时执行了什么。任何这样的执行代码也可能需要重新测试。
因为您想要最小化您编写的测试代码的数量,您显然想要比文件精确粒度的"更改"更好。您可以使用diff工具(通常构建到您的源代码控制系统中)来帮助将重点放在特定的行上。Diff工具实际上并不理解代码结构,所以它们报告的内容往往是面向行而不是面向结构的,产生了比必要的更大的差异;它们也没有告诉您测试访问的方便点,这很可能是一个方法,因为单元测试的整个风格都集中在测试方法上。
你可以得到更好的工具。我们的Smart Differencer工具在程序结构(表达式,语句,方法)和抽象编辑操作(插入,删除,复制,移动,替换,重命名)方面提供了差异,这可以使代码更改更容易解释。这并不能直接解决"哪种方法改变了?"的问题,但它通常意味着在做出决定时要看更少的东西。
您可以获得能够回答这个问题的测试覆盖率工具。我们的测试覆盖工具能够比较以前的测试覆盖运行和当前的测试覆盖运行,从而告诉您哪些测试必须重新运行。他们通过检查代码差异(类似于Smart Differencer)来做到这一点,但将更改抽象回方法级别。