示例:
public interface IFoo
{
bool DoSomething();
}
public class Foo:IFoo
{
public bool DoSomething()
{
var result = DoOtherThing();
...
return result;
}
public bool DoOtherThing()
{
...
}
}
我的正常TDD方法是在DoSomething()
和DoOtherThing()
方法上编写单元测试。但如果DoOtherThing
是私有方法,这将很难做到。我还读过,测试私有方法是禁止的。
在类上使用公共方法以便于代码覆盖和测试是否被认为是可接受的,即使该类的目的只是通过其(IFoo)接口访问?通常我会将超出接口范围的方法作为私有方法,但这不允许您有效地测试所有代码。使该方法公开允许您对Foo
类进行正确的测试,但至少对我来说,拥有未从类外部调用的公共方法似乎是不正确的。这种方式对TDD来说是最好的还是有更好的方法?
答案 0 :(得分:6)
公共方法是公开的,因为它们应该是可访问的。如果不打算从外部调用它,请将其设为私有。
如果您未能获得代码覆盖率,您可能希望将您的类分解为多个类并使用合成。
难以测试的事情通常表明存在设计缺陷。
更新
好的,我们假设您有一种发送电子邮件的方法。第1步是生成一个MailMessage类并填充它。第2步是发送电子邮件
这是两个责任(SRP)。撰写电子邮件并实际发送。我不会在同一个班级那样做。如果您的所有电子邮件类组成他们的邮件然后发送它们,它也将是代码重复。你如何处理网络故障?你也复制了那些支票吗?
做类似的事情:
public class SendWelcomeEmailComposer
{
MailMessage Compose(User user)
}
public class EmailSender
{
void SendEmail(MailMessage);
}
public class EmailService
{
void SendWelcomeEmail(User user)
{
// compose email
// and send using the classes above.
}
}
更新2
为什么你不应该测试私有方法的原因是我关注质量测量。如果你的测试覆盖率很低,你可能会违反一些基本原则(SOLID)。
因此,最好花些时间反思课堂设计,而不是试图测试私人方法。
答案 1 :(得分:2)
这里的诀窍是使用
[assembly: InternalsVisibleTo("NameOfYourTestAssembly")]
在AssemblyInfo.cs文件中。
这允许您将可测试方法设置为内部,这意味着它们只能在您正在编写的程序集中访问,并且可以在此属性中使用程序集。
(如果您有Mycode.dll和Mycode.Tests.dll,那么您将该属性添加到MyCode / Properties / AssemblyInfo.cs)
答案 2 :(得分:0)
我认为你需要专注于 进行测试,你会发现如何设计通过测试的对象(或对象)的问题是只是一个偏好和方便的问题。你的问题没有一个正确的答案。
由于您决定分两步打破全局操作,因此您需要测试:
验证第一部分(DoSomething()
)的行为是否符合预期。这可能包括测试它是否调用了正确的依赖项,将Foo对象置于正确的状态等等。
确认第一步后跟第二步,换句话说DoSomething()
调用DoOtherThing()
,如果需要,请使用正确的参数。
验证第二步(DoOtherThing()
)是否按预期运行。同样,这可能包括它正确地与其依赖关系,产生正确的输出,等等。
没有关于如何。虽然#1非常容易测试和实现,因为我们有DoSomething()
公开的先决条件,#2和#3使您的实现和测试选项更加开放。你基本上可以做两件事:
将2个职责留在一个班级。这个选项反过来会破坏许多可能性:使DoOtherThing()
公开(易于测试但不安全,因为我们可能不希望将操作的内部子步骤暴露给外部),将其作为内部,如@AlSki指出的那样,使其成为 protected虚拟并在测试中使用partial mock来验证2之间的协作方法。名单肯定会继续。
为每一步提供自己的课程。如果它们实际上是处理系统的不同部分或与不同层交谈的2个不同职责,那么这尤其重要。您通常的模拟和协作测试适用于此处。
旁注1 :如果您未能区分操作中的2个职责并将这两个步骤放在一个方法中,那么事情会有很大不同,因为您的测试确实存在是集成测试而不是单元测试。这可能会带来一些问题,例如只关注管道两端发生的事情,并且无法验证所有中间作业的正确性。因此,我认为在尽可能多的合理步骤中分解大型操作总是更好(例如在电子邮件发送示例中,创建正确的MailMessage
数据结构显然与发送它的责任不同)和测试每一个。
旁注2 :您的类实现IFoo的事实只与所有这些远程相关。它基本上影响硬币的另一面 - 从其他类定义你的类的入口点。如果你想单独测试一些东西,你可能不得不在IFoo消费者的测试中创建IFoo模拟,并验证这些消费者类是否正确地调用了DoSomething()
。