类中接口外的公共方法

时间:2012-09-06 15:45:20

标签: c# unit-testing testing tdd

示例:

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来说是最好的还是有更好的方法?

3 个答案:

答案 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)

我认为你需要专注于 进行测试,你会发现如何设计通过测试的对象(或对象)的问题是只是一个偏好和方便的问题。你的问题没有一个正确的答案。

由于您决定分两步打破全局操作,因此您需要测试:

  1. 验证第一部分(DoSomething())的行为是否符合预期。这可能包括测试它是否调用了正确的依赖项,将Foo对象置于正确的状态等等。

  2. 确认第一步后跟第二步,换句话说DoSomething()调用DoOtherThing(),如果需要,请使用正确的参数。

  3. 验证第二步(DoOtherThing())是否按预期运行。同样,这可能包括它正确地与其依赖关系,产生正确的输出,等等。

  4. 没有关于如何。虽然#1非常容易测试和实现,因为我们有DoSomething()公开的先决条件,#2和#3使您的实现和测试选项更加开放。你基本上可以做两件事:

    • 将2个职责留在一个班级。这个选项反过来会破坏许多可能性:使DoOtherThing() 公开(易于测试但不安全,因为我们可能不希望将操作的内部子步骤暴露给外部),将其作为内部,如@AlSki指出的那样,使其成为 protected虚拟并在测试中使用partial mock来验证2之间的协作方法。名单肯定会继续。

    • 为每一步提供自己的课程。如果它们实际上是处理系统的不同部分或与不同层交谈的2个不同职责,那么这尤其重要。您通常的模拟和协作测试适用于此处。

    旁注1 :如果您未能区分操作中的2个职责并将这两个步骤放在一个方法中,那么事情会有很大不同,因为您的测试确实存在是集成测试而不是单元测试。这可能会带来一些问题,例如只关注管道两端发生的事情,并且无法验证所有中间作业的正确性。因此,我认为在尽可能多的合理步骤中分解大型操作总是更好(例如在电子邮件发送示例中,创建正确的MailMessage数据结构显然与发送它的责任不同)和测试每一个。

    旁注2 :您的类实现IFoo的事实只与所有这些远程相关。它基本上影响硬币的另一面 - 从其他类定义你的类的入口点。如果你想单独测试一些东西,你可能不得不在IFoo消费者的测试中创建IFoo模拟,并验证这些消费者类是否正确地调用了DoSomething()