【问题标题】:Public methods outside of an interface in a class类中接口之外的公共方法
【发布时间】:2012-09-06 15:45:19
【问题描述】:

例子:

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

【问题讨论】:

  • 如果您有从未被公共方法(直接或间接)调用的私有方法,为什么不直接删除该代码?
  • 不,我说的是我以前编写代码的方式,所有不是在接口上定义的方法的东西,我都将其作为私有方法。但是当你试图让每个方法都有一个单一的职责时,你最终会在这些私有方法中得到大部分代码。根据定义,它们不是公共接口的一部分,据我所知,没有必要进行测试。
  • @dbt 评论的附录:通过调用它们的面向公众的方法测试您的私有代码。
  • @dbt 评论的另一个角度:我看到很多人问如何覆盖私人成员。如果无法“使用”定义它们的类以便访问它们,那么它们的目的是什么? 应编写测试以使用您的类,因为它们真正已被使用。

标签: c# unit-testing testing tdd


【解决方案1】:

公共方法是公共的,因为它们应该是可访问的。如果不打算从外部调用,请将其设为私有。

如果您未能获得代码覆盖率,您可能希望将您的类分解为多个类并改用组合。

难以测试的东西通常表明存在设计缺陷。

更新

好的,假设您有一种发送电子邮件的方法。第 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 步是生成一个 MailMessage 类并填充它。第 2 步是发送电子邮件。您的公共接口有一个名为 SendEmail 的方法,它只接收要发送的数据。如果两者都在私有方法中,您应该如何测试邮件消息的创建和发送邮件消息?如果将它们都直接放在 SendEmail 方法中,则也无法单独测试它们。如果不是全部公开,我只是很难看到如何测试实现
  • 我的大脑一定只是倾向于喜欢程序代码而不是 OOP,因为我对此的直觉反应是“为每个单独的操作提供一个具有单一方法的类似乎是一种让你的代码膨胀的方法着急”。我明白你的意思。
  • 不是每一个操作。编写某些东西与分发它们有很大不同。组合可能涉及使用模板引擎、解析文本等,而发送涉及网络类。
  • 我也认为这是因为 SOLID 中的 S 对我来说意味着一个任务而不是一个操作。因此,在您的示例中,我的 EmailService 将负责发送电子邮件的所有部分。如果我所有的“电子邮件类”实际上都只是这个 EmailService,那么将组合方法放在另一个类中只是为了使其可测试似乎会增加代码膨胀。在未来可能需要扩展的情况下,是的,我明白你在说什么。但是,如果只有 1 个实例需要此 EmailService 功能,为什么要这样做?
  • 所以任务是“撰写和发送电子邮件”?注意“和”。不是很扎实。 SOLID 确实产生较小的类。这反过来又使它们更容易重用和测试。在这种情况下,组合使您能够测试以前的私有方法,因为它们在新类中是公共的(因为它们是该接口的一部分)
【解决方案2】:

这里的诀窍是使用

[assembly: InternalsVisibleTo("NameOfYourTestAssembly")] 

在 AssemblyInfo.cs 文件中。

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

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

【讨论】:

  • 好的,我可以试试这个 .net 代码。但是如果一种语言没有这样的技巧来使内部结构可以访问,如果它们是私有的呢?我认为我的问题是我看不到如何设计类以使方法具有单一职责而不将所有实现都放在面向公众的方法中
  • 有点,我用 C# 作为我的例子。但我想我可以看到这适用于任何 OOP 语言。
  • 重点是,既然你使用的是一个接口,那么这部分应该被认为是对所有消费代码都是公开的。该接口的使用者不会知道他们得到的是 BarFoo 还是 FooFoo,因此即使使用公共方法,您仍然可以将这些方法标记为不供公共使用(例如,在您最喜欢的 System.* 库中选择一个类)
【解决方案3】:

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

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

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

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

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

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

  • 将 2 个职责留在一个类中。反过来,此选项在许多可能性中被打破:使DoOtherThing() public(易于测试但不安全,因为我们可能不想将操作的内部子步骤暴露给外部),使其成为internal 正如@AlSki 指出的那样,使其 protected virtual 并在您的测试中使用partial mock 来验证这两种方法之间的协作。名单肯定还在继续。

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

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

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

【讨论】:

  • 我看到了你的思路;但是,如果使用 DoOtherThing() 的范围只会从 Foo 类中调用,然后将其拆分为能够对其进行测试并使其成为公共方法,那么在我看来,这似乎是不必要的添加复杂性。
  • 那么这似乎指向内部或受保护的 DoOtherThing()。
猜你喜欢
  • 2018-09-27
  • 2017-06-08
  • 1970-01-01
  • 2016-09-20
  • 2011-08-24
  • 2010-11-01
  • 1970-01-01
  • 2010-11-05
  • 2011-08-27
相关资源
最近更新 更多