我已经忽略了一段时间的单元测试。我写了单元测试,但他们相当差。 我现在正在阅读“单元测试的艺术”,以使自己达到最佳状态。
如果我有一个界面,例如:
public interface INotificationService
{
void AddError(string _error);
void AddIssue(string _issue);
IEnumerable<string> FetchErrors();
IEnumerable<string> FetchIssues();
}
此接口的具体实现包含:
private readonly ICollection<Message> messages;
添加错误或问题会创建一条带有枚举的新消息,表示其类型并将其添加到集合中。 调用FetchErrors()/ FetchIssues()从集合中返回该类型的消息。
以下测试是否有效?:
[Test]
public void FetchErrors_LoggingEnabledAddErrorFetchErrors_ReturnsError()
{
notificationService = new NotificationService();
notificationService.AddError("A new error");
Assert.AreEqual(new []{"A new error"}, notificationService.FetchErrors());
}
我担心的是我首先调用AddError(),然后测试FetchErrors()的结果。所以我称之为两个功能。这是不正确的?
我应该公开集合并直接断言它包含包含已记录错误消息的相应类型的消息吗?
这种情况下的最佳做法是什么?
答案 0 :(得分:3)
您的方法看起来很好 - 通过公共方法测试实现是设计中唯一可用的访问。
为了相互独立地测试方法,您需要使用像反射这样的白盒测试后门来访问私有消息,以确保相应的Add*
方法有效。这根本不是一个好主意,因为每次基础CUT实现发生变化时,您的测试都会中断(在运行时)。
反射的常见,更好的替代方法是在测试的具体类上公开非接口internal
方法,这允许您对ICollection<Message> messages;
实现字段进行某种“维护孵化”访问,然后允许InternalsVisibleTo
访问Unit测试程序集。
一个注意事项 - 您会发现可枚举字符串上的Assert.AreEqual
将进行参考比较,从而失败(因为您将它与新数组进行比较)。
您可能需要将其更改为:
Assert.IsTrue(notificationService.FetchErrors().Contains("A new error"));
修改强>
IMO你不需要在.Net中创建字段public
。由于您已经具有接口抽象,理论上应该限制对CUT的访问方式,因此使用internal
范围和InternalsVisibleTo
来允许UT特殊访问是很常见的。 Jon Skeet等人有mentioned this here
#region Unit Testing side-door
internal ICollection<Message> Messages
{
get { return _messages };
// No Setter
}
#endregion
答案 1 :(得分:2)
你的方法完全没问题。单元测试应遵循 Arrange-Act-Assert 结构 - 您对notificationService.AddError(..)
的调用只是测试 Arrange 部分的一部分。
并不是说你不能在测试中调用多个方法(在很多情况下你必须这样做),关键是你应该仅断言/测试一个单一的事实。这就是你做的,所以一切都很好。
答案 2 :(得分:1)
正如其他人指出的那样,使用这两种方法都可以。您正在测试类的行为,并且只要发布错误,您就可以阅读它。
如果实现更复杂,那么您可能需要重新考虑设计,因为您的方法做得太多。看看你描述的内容,为什么实际上你需要getter作为一种方法呢?
一般来说,我不喜欢仅为测试目的而公开内部方法的想法。这会向将来维护该代码的人发送错误信息(即使是在您忘记之后)。它说 - 是的,继续,直接从这个集会中的任何一个类访问这个集合。
答案 3 :(得分:1)
我们无法根据您发布的代码说明您的测试是否有效。我们不知道AddError()的作用,因此无法知道单元测试是否正确。
如果AddError只是在集合中添加错误并执行其他任何操作,那么您的测试很好并且调用FetchErrors()是有效的,因为它必须有一个单独的单元测试来验证它。在任何情况下,如果您的AddError方法执行任何其他操作,则必须更改测试并验证方法执行的所有步骤。