2

例子:

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

4

3 回答 3

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)。

因此,最好花时间反思类设计,而不是尝试测试私有方法。

于 2012-09-06T15:49:44.100 回答
2

这里的诀窍是使用

[assembly: InternalsVisibleTo("NameOfYourTestAssembly")] 

在 AssemblyInfo.cs 文件中。

这允许您将可测试方法设为内部,这意味着它们只能在您正在编写的程序集中以及此属性中的程序集中访问。

(如果您有一个 Mycode.dll 和一个 Mycode.Tests.dll,那么您将属性添加到 MyCode/Properties/AssemblyInfo.cs)

于 2012-09-06T15:55:35.453 回答
0

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

由于您决定分两步分解全局操作,因此您需要测试以下内容:

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

  2. 验证第一步之后是第二步,换句话说,如果需要,使用正确的参数调用DoSomething()DoOtherThing()

  3. 验证第二步 ( DoOtherThing()) 的行为是否符合预期。同样,这可能包括它正确地与其依赖项对话,产生正确的输出等等。

知道如何。虽然 #1 的测试和实施非常简单,因为我们有DoSomething()公开的先决条件,但 #2 和 #3 让您的实施和测试选项更加开放。基本上你可以做两件事:

  • 将 2 个职责留在一个类中。反过来,此选项在许多可能性中失效:DoOtherThing() 公开(易于测试但不安全,因为我们可能不希望将操作的内部子步骤暴露给外部),正如@AlSki 指出的那样使其成为内部的,使其受到保护虚拟并在您的测试中使用部分模拟来验证这两种方法之间的协作。名单肯定还在继续。

  • 给每个步骤一个自己的类。如果它们确实是处理系统的不同部分或与不同层对话的两个不同的职责,则这一点尤其重要。您通常的模拟和协作测试适用于此。

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

Side note 2 : The fact that your class implements IFoo is only remotely related to all this. It basically affects the other side of the coin - defining the entry point to your class from other classes. If you want to test things in isolation, you'll probably have to create IFoo mocks in IFoo consumers' tests and verify that these consumer classes call DoSomething() properly.

于 2012-09-06T20:45:10.040 回答