C# 使用NUnit的.NET TDD工作流最佳实践
更新:我对这篇文章做了重大修改-查看修订历史以了解详细信息 我开始用NUnit深入研究TDD,尽管我很喜欢在stackoverflow这里找到一些资源,但我经常发现自己没有获得很好的吸引力 所以,我真正想要实现的是获得某种检查表/工作流程——这里我需要你们的帮助——或者“测试计划”,这将为我提供体面的代码覆盖率 因此,让我们假设一个理想的场景,在这个场景中,我们可以从头开始一个项目,比如说一个Mailer helper类,它将具有以下代码: (我创建这个类只是为了用代码示例帮助回答问题,因此鼓励并欢迎任何批评或建议) Mailer.csC# 使用NUnit的.NET TDD工作流最佳实践,c#,tdd,nunit,code-coverage,C#,Tdd,Nunit,Code Coverage,更新:我对这篇文章做了重大修改-查看修订历史以了解详细信息 我开始用NUnit深入研究TDD,尽管我很喜欢在stackoverflow这里找到一些资源,但我经常发现自己没有获得很好的吸引力 所以,我真正想要实现的是获得某种检查表/工作流程——这里我需要你们的帮助——或者“测试计划”,这将为我提供体面的代码覆盖率 因此,让我们假设一个理想的场景,在这个场景中,我们可以从头开始一个项目,比如说一个Mailer helper类,它将具有以下代码: (我创建这个类只是为了用代码示例帮助回答问题,因此鼓励
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);
}
}
}
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.
}
}
MailerTests.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);
}
}
}
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.
}
}
使用系统;
使用NUnit.Framework;
使用FluentAssertions;
命名空间Dotnet.Samples.NUnit
{
[测试夹具]
公共类邮件测试
{
[测试,忽略(“不再需要,因为需要通过的代码已经实现了。”)]
public void SendMail\u fromagumentisnotnull或empty\u ReturnsTrue()
{
//安排
动态参数=null;
//表演
Mailer-Mailer=new-Mailer(from:argument);
//断言
IsNotNullOrEmpty(mailer.From,“参数不能为null或空”);
}
[测试]
public void SendMail\u FromArgumentIsNullOrEmpty\u ThrowsException()
{
//安排
动态参数=null;
Mailer-Mailer=new-Mailer(from:argument);
//表演
Action act=()=>mailer.SendMail();
应该扔的动作;
//断言
抛出(newtestdelegate(act));
}
[测试]
public void SendMail_FromArgumentIsOfTypeString_ReturnsTrue()
{
//安排
动态参数=String.Empty;
//表演
Mailer-Mailer=new-Mailer(from:argument);
//断言
mailer.From.Should().Be(参数“参数应为字符串类型”);
}
//信息:在第一次“迭代”中,我几乎涵盖了该方法的第一个参数,所以从逻辑上讲,这个示例远未完成。
//TODO:创建一个测试,最终需要实现一个方法来验证格式良好的电子邮件地址。
//TODO:根据需要创建尽可能多的测试,以便为其余参数提供良好的代码覆盖率。
}
}
因此,在我的前两个失败测试之后,下一个明显的步骤是实现使其通过的功能,但是,我应该保留失败的测试并在实现使其通过的代码后创建新的测试,还是应该在使其通过后修改现有的测试
关于此主题的任何建议都将受到极大的赞赏。如果您使用NUnit这样的框架,那么有一些可用的方法,例如
AssertThrows
,您可以断言,在给定输入的情况下,某个方法会引发所需的异常:
基本上,验证给定好输入和坏输入的预期行为是最好的开始。如果安装,其中一个组件(称为NCover)实际上可以帮助您了解单元测试覆盖了多少代码
除此之外,最好的解决方案是检查每条线路,并运行每个测试,以确保至少击中该线路一次 我建议您选择一些工具,比如它可以连接到您的测试用例上,以提供代码覆盖率统计数据。如果您不想要许可版本,还可以使用社区版的NCover。当人们(最终!)决定对现有代码库应用测试覆盖率时,测试所有内容是不切实际的;你没有足够的资源,也没有太多的实际价值 理想情况下,您希望做的是确保您的测试应用于新编写/修改的代码以及可能受这些更改影响的任何内容 为此,您需要知道:
- 您更改了什么代码。您的源代码管理系统将帮助您在此级别更改此文件
- 执行新代码的结果是执行什么代码。为此,您需要一个可以跟踪代码下游影响的静态分析器(其中许多是不知道的)或一个测试覆盖率工具,它可以显示在运行特定测试时执行的内容。任何这样执行的代码可能也需要重新测试