使用朋友类在C ++中进行单元测试私有方法


15

我知道这是一个有争议的做法,但是让我们假设这对我来说是最佳选择。我想知道执行此操作的实际技术是什么。我看到的方法是这样的:

1)与我要测试的方法的班级做一个朋友班。

2)在friend类中,创建一个公共方法,该公共方法调用被测试类的私有方法。

3)测试好友类的公共方法。

这是一个简单的示例来说明上述步骤:

#include <iostream>

class MyClass
{
  friend class MyFriend; // Step 1

  private:
  int plus_two(int a)
  {
    return a + 2;
  }
};

class MyFriend
{
public:
  MyFriend(MyClass *mc_ptr_1)
  {
    MyClass *mc_ptr = mc_ptr_1;
  }

  int plus_two(int a) // Step 2
  {
    return mc_ptr->plus_two(a);
  }
private:
  MyClass *mc_ptr;
};

int main()
{
  MyClass mc;
  MyFriend mf(&mc);
  if (mf.plus_two(3) == 5) // Step 3
    {
      std::cout << "Passed" << std::endl;
    }
  else
    {
      std::cout << "Failed " << std::endl;
    }

  return 0;
}

编辑:

我看到在讨论中,有人对我的代码库感到疑惑,而在下面的答案之一中。

我的课有被其他方法调用的方法。这些方法都不应该在类外部调用,因此它们应该是私有的。当然,可以将它们放在一种方法中,但是从逻辑上讲,它们要好得多。这些方法非常复杂,需要进行单元测试,并且由于性能问题,我很可能必须重构这些方法,因此最好进行测试以确保重构不会破坏任何东西。我不是唯一在团队中工作的人,尽管我是唯一致力于这个项目(包括测试)的人。

综上所述,我的问题不是关于为私有方法编写单元测试是否是一种好习惯,尽管我很感谢反馈。


5
这是一个有疑问的问题的建议。我不喜欢朋友的耦合,因为那时发布的代码必须了解测试。Nir在下面的回答是缓解这种情况的一种方法,但是我仍然不太喜欢更改类以使其符合测试要求。由于我不经常依赖继承,因此有时我只是将本应私有的方法保护起来,并根据需要让测试类继承和公开。我希望对此评论至少有三个“嘘声”,但实际情况是,公共API和测试API可以有所不同,但仍然与私有API有所不同。嗯
J特拉纳2014年

4
@JTrana:为什么不把它写成一个正确的答案呢?
Bart van Ingen Schenau

好。这不是您为之感到骄傲的一种,但希望会有所帮助。
J特拉纳2014年

Answers:


23

我经常使用的一种替代朋友(某种意义上来说)是一种我已经知道的模式,称为access_by。很简单:

class A {
  void priv_method(){};
 public:
  template <class T> struct access_by;
  template <class T> friend struct access_by;
}

现在,假设类B参与了测试A。您可以这样编写:

template <> struct access_by<B> {
  call_priv_method(A & a) {a.priv_method();}
}

然后,您可以使用access_by的这种专门化来调用A的私有方法。基本上,这样做是将声明友谊的责任放在了要调用A的私有方法的类的头文件中。它还可以让您将朋友添加到A,而无需更改A的来源。从习惯上讲,从扩展其接口的意义上讲,它也向任何阅读A来源的人指示A并不表示B是真正的朋友。相反,A的接口是给定的,并且B需要对A的特殊访问(测试是一个很好的例子,我在实现boost python绑定时也使用了这种模式,有时在C ++中需要私有的函数很容易公开到python层中以供实施)。


好奇是否有一个有效的用例,使friend access_by第一个非朋友就足够了-是嵌套结构,它可以访问A中的所有内容?例如。coliru.stacked-crooked.com/a/663dd17ed2acd7a3
tangy

10

如果很难测试,那就写得不好

如果您的类具有足够复杂的私有方法来保证自己的测试,则该类做得太多。里面有另一个班试图逃脱。

将要测试的私有方法提取到一个或多个新类中,并将其公开。测试新类。

除了使代码更易于测试之外,这种重构还将使代码更易于理解和维护。


1
我完全同意这个答案,如果您不能通过测试公共方法来完全测试私有方法,那是不对的,将私有方法重构为自己的类将是一个很好的解决方案。
David Perfors 2014年

4
在我的代码库中,类有一个非常复杂的方法来初始化计算图。它依次调用了几个子功能来实现此目的的各个方面。每个子功能都非常复杂,并且代码的总和也非常复杂。但是,如果未按正确的顺序在此类上调用,则子功能将毫无意义。用户唯一关心的是计算图已完全初始化;中间体对用户毫无价值。拜托,我想听听我应该如何重构它,以及为什么它比仅仅测试私有方法更有意义。
尼尔·弗里德曼

1
@Nir:琐碎:将所有这些方法都公开提取一个类,并使现有类成为新类的外观。
凯文·克莱恩

这个答案是正确的,但实际上取决于您正在使用的数据。在我的情况下,我没有提供实际的测试数据,因此我必须通过观察实时数据来创建一些数据,然后将其“注入”到我的应用程序中,并查看如何单个数据太复杂而无法处理,因此人为地创建部分测试数据要比实际复制实时数据要容易得多,该部分测试数据只是要针对每个实时数据的一个子集,而实际复制实时数据并不容易。足以呼吁实现其他几个小类(具有各自的功能)
rbaleksandar

4

您不应该测试私有方法。期。使用您的类的类仅关心它提供的方法,而不关心它在幕后使用的方法。

如果您担心代码覆盖率,则需要找到允许您从一个公共方法调用中测试该私有方法的配置。如果您不能这样做,那么首先使用该方法有什么意义?这只是无法访问的代码。


6
私有方法的重点是简化开发(通过分离关注点或保持DRY或任何其他方式),但是这并不是永久性的。因此,它们是私有的。它们可能从一种实现到另一种实现大幅度地出现,消失或功能改变,这就是为什么将它们与单元测试联系起来并不总是可行的,甚至是有用的。
Ampt入渗

8
它可能并不总是实用或有用的,但这与说永远不要测试它们相去甚远。您在谈论私有方法,就好像它们是别人的私有方法一样。“它们可能出现,消失...”。不,他们不能。如果直接对它们进行单元测试,那应该仅仅是因为您自己维护它们。如果更改实现,则更改测试。简而言之,您的笼统声明是不合理的。尽管警告OP很好,但他的问题仍然合理,您的答案实际上并没有回答。
尼尔·弗里德曼

2
让我还请注意:OP事先表示,他知道这是一个有争议的做法。因此,如果他仍然想这样做,也许他确实有充分的理由吗?我们俩都不知道他的代码库的细节。在我使用的代码中,我们有一些非常有经验的专家程序员,他们认为在某些情况下对私有方法进行单元测试很有用。
尼尔·弗里德曼

2
-1,例如“您不应该测试私有方法”。恕我直言没有帮助。在该站点上已充分讨论了何时测试和何时不测试私有方法的主题。OP已表明他知道此讨论,并且很显然,他正在寻找解决方案,前提是假设要测试私有方法是可行的方法。
Doc Brown

3
我认为这里的问题是这可能是一个非常经典的XY问题,因为OP认为他出于某种原因需要对自己的私有方法进行单元测试,而实际上他可以从更务实的角度进行测试,查看私有的方法。作为公共功能的简单辅助方法,即与该类最终用户的合同。
Ampt入渗

3

有几种方法可以执行此操作,但请记住,它们本质上是修改模块的公共接口,以便您可以访问内部实现详细信息(将单元测试有效地转换为紧密耦合的客户端依赖项,您应该在其中完全没有依赖性)。

  • 您可以在测试的类中添加好友(类或函数)声明。

  • 您可以#define private public#include-ed被测代码之前将其添加到测试文件的开头。如果测试的代码是已编译的库,则这可能使标头不再与已编译的二进制代码匹配(并导致UB)。

  • 您可以在测试的课程中插入一个宏,并在以后决定该宏的含义(使用不同的测试代码定义)。这将允许您测试内部,但是也将允许第三方客户端代码侵入您的类(通过在添加的声明中创建它们自己的定义)。


2

这是一个有疑问的问题的建议。我不喜欢朋友的耦合,因为那时发布的代码必须了解测试。Nir的答案是缓解这种情况的一种方法,但是我仍然不太喜欢更改类以使其符合测试要求。

由于我不经常依赖继承,因此有时我只是将本应私有的方法保护起来,并根据需要让测试类继承和公开。现实情况是,公共API和测试API可以有所不同,但仍然与私有API有所不同,这会使您陷入困境。

这是一个实际的示例,我可以借助此技巧来实现。我编写嵌入式代码,我们相当依赖状态机。外部API不一定需要了解内部状态机状态,但是测试应该(可以说)测试与设计文档中状态机图的一致性。我可能将“当前状态”的吸气剂暴露为受保护状态,然后对它进行测试访问,使我可以更全面地测试状态机。我经常发现这种类型的课程很难像黑盒子一样进行测试。


尽管这是一种Java方法,但这是使私有函数成为默认级别的相当标准,而允许同一包中的其他类(测试类)能够看到它们。

0

您可以使用许多变通办法来编写代码,以阻止您不得不使用朋友。

您可以编写类,而根本没有任何私有方法。然后,您要做的就是在编译单元中创建实现函数,让您的类调用它们,并传入需要访问的任何数据成员。

是的,这意味着您将来可以更改签名或添加新的“实现”方法而无需更改标题。

您必须权衡是否值得。这实际上取决于谁将看到您的标头。

如果我使用的是第3方库,则我不希望看到其单元测试人员的朋友声明。我也不想建立他们的库并在运行时运行他们的测试。不幸的是,我建立了太多的第三方开放源代码库。

测试是库编写者而不是用户的工作。

但是,并非所有类对库用户都是可见的。许多类都是“实现”,您可以以最佳方式实现它们,以确保它们正常工作。在那些方法中,您可能仍然拥有私有方法和成员,但希望单元测试人员对其进行测试。因此,如果可以更快地生成强大的代码,那么就以这种方式进行操作,对于需要这样做的人来说,维护起来很容易。

如果您的班级用户都在您自己的公司或团队中,则可以假定该方法在您公司的编码标准允许的情况下放宽一些。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.