【问题标题】:TDD in C++. How to test friend functions of private class?C++ 中的 TDD。如何测试私教的好友功能?
【发布时间】:2014-10-06 14:47:16
【问题描述】:

如果我有一个类里面有一个助手(私有成员)类,像这样

    class Obj;
    class Helper {
        friend class Obj;
        private:
        int m_count;
        Helper(){ m_count = 0;};  // Note this is a private constructor
        void incrementCount(){
            ++m_count;
        };
    };
    class Obj {
        Helper *m_pHelper;
        // note that this is a private getter
        int getHelperCount() { return m_pHelper->m_count; };

        // the public API starts here
        public:
        Obj() { m_pHelper = new Helper(); };
        void incrementCount(){ m_pHelper->incrementCount(); };
    };

那么我该如何 TDD 这样的系统呢?

    auto obj = new Obj();
    obj->incrementCount();
    // what to assert???

这是我的问题,以下只是一些背景知识。

对一些答案和cmets的回应。

如果班外没有人感兴趣,那么你的测试也不应该感兴趣。 ——阿恩·默茨

如果没有人对课外的价值感兴趣,你为什么 – utnapistim

即使外部没有人需要该值,我可能仍然想知道它是否设置正确,因为它被使用该值的类的其他自包含内部方法使用。也许该值是控制器将使用它来更新模型的速度。或者也许它是视图将使用它在屏幕上绘制某些东西的位置。事实上,Obj 的所有其他组件都可以访问该变量。这可能是一个糟糕的设计问题,在这种情况下,我想知道我可以有什么更好的选择。该设计列在本文底部的背景部分。

定义私人公共 - 马森毛

喜欢这种巧妙地滥用关键字哈哈。但目前可能还不是最好的解决方案。

你需要在你的班级标题中“暴露”友谊关系。因此,您必须承认存在用于测试您的课程的课程。 如果您使用 pImpl 习语,您可以将 pImpl 本身的成员全部公开,将 pImpl 本身设为私有,并让您的单元测试访问 pImpl - CashCow

这是否意味着我应该在我原来的班级中加入测试?或者向它添加额外的“测试”方法? 我最近才开始 TDD。使用测试类依赖侵入原始类是否常见(或者更好的是)?我认为我没有适当的知识来判断。对此有何建议?

其他:AFAIK TDD 不仅仅是编写测试,而是一个开发过程。我已经读过我应该只将测试写入公共接口。但问题是,就像所讨论的情况一样,大多数代码等都包含在私有类中。如何使用 TDD 创建这些代码?

背景

如果您想知道我为什么要开设私人课程,仅供参考: 我正在从 cocos2dx 开发游戏。游戏引擎采用节点树结构进行更新、渲染等,每个游戏对象都继承自引擎中提供的节点类。现在我想在游戏对象上实现 MVC 模式。因此,对于每个对象,我基本上创建了一个 Object 类,其中包含 3 个帮助类,对应于名为 ObjectModel、ObjectView、ObjectController 的每个 MVC 组件。从理论上讲,没有人应该直接访问 MVC 类,并且只能通过 Object 类以某种方式访问​​,所以我将它们中的 3 个设为私有。将 MVC 组件显式设为类的原因是因为 View 和 Controller 以不同的速率进行更新(更具体地说,Controller 执行与帧相关的更新,而 View 基于模型数据进行简单的插值)。 Model 类是纯粹出于宗教原因创建的,哈哈。

提前致谢。

【问题讨论】:

  • 测试公共接口。
  • 当您创建Obj 实例时,Helper 实例的计数器应设置为零,您可以对其进行测试。然后递增,并检查结果是否为 1。并且由于“增量计数器”和“获取计数器”是唯一的公共函数,这就是你可以测试的全部。
  • 问题是我什至在公共接口中都没有getter。那么我的测试如何检查该值是 0 还是 1?并且故意将 getter 设为私有,因为没有人应该对类之外的值感兴趣。
  • 如果类以外的人不应该感兴趣,那么您的测试也不应该感兴趣,因为只要类行为正确,该值是 0 还是 1 或 54 都无关紧要。如果您的类需要某个公开可用函数的该值,请测试该函数。如果您的类在公共接口中的任何函数都不需要该值,则将值全部丢弃,因为它根本没有用。如果您要测试的是您的类所依赖的一些更通用的算法,则将其放入自己的类中并单独对其进行单元测试。

标签: c++ tdd


【解决方案1】:

如何测试私有类的好友函数?

你不能!

一个类(或模块或库或其他)出于某种原因公开一个公共接口。你有公共接口(它是为客户使用而设计的,所以它有不变量、前置条件、后置条件、副作用等等——可以而且应该测试)和实现细节,允许你更容易地实现公共接口。

拥有私有实现的意义在于,您可以随意更改它,而不会影响其他代码(甚至不会影响测试)。在您更改私有实现后,所有测试都应该通过,并且客户端(和测试)代码应该(按设计)根本不关心您更改了私有实现。

那么我该如何 TDD 这样的系统呢?

仅 TDD 您的公共接口。测试实现细节意味着您最终将编码为实现,而不是接口。

关于您的评论:

问题是我什至在公共接口中都没有 getter。那么我的测试如何检查该值是 0 还是 1?并且故意将 getter 设为私有,因为没有人应该对类之外的值感兴趣

如果没有人对类之外的值感兴趣,你为什么要测试它(即你为什么要测试它?)

【讨论】:

  • 不确定是否正确,但就我而言,我想测试这些值以确保一切顺利。每次修改我的课程(无论是公共的还是私人的)时,我都可以进行测试并运行它。如果测试通过了,那么我知道我没有打破任何东西。比如我做了一个std::map-like 类,MyMap;我对其进行了单元测试,设计了一些程序,如许多insert 然后erase,然后测试MyMap 对象的所有内部值以确保实现正确。下次我更改 MyMap 的私有实现时,我再次运行测试以检查结果是否仍然正确。
  • 如果您想测试私有函数的效果,请弄清楚它如何影响您的公共函数的输出或副作用,然后为它们编写测试。这些测试将更难编写,但它们最终会成为您公共 API 的极端案例。这些测
【解决方案2】:

#define private public 技巧可能会对某些编译器修改函数符号的方式产生副作用(Visual c++ 编译器在其名称修改中包含访问说明符)

您还可以使用 using 语句更改可见性:

struct ObjTest : public Obj
{
   using Obj::incrementCount;
}

但就像其他人所说的那样,尽量不要测试私人的东西。

【讨论】:

  • 注意:#define private public 的另一个副作用是属性顺序重新排列,具体取决于某些编译器上的访问说明符。
【解决方案3】:

我在写单元测试的时候也遇到过这样的问题。

经过一番搜索,我决定最有效的方法是将其添加到您的Test.cpp

#define private public

注意:在你想要的包含文件之前添加这个,例如你的Obj.h

我认为这个方法看起来很疯狂,但实际上是合理的,因为这个#define 只会影响你的测试文件,所以所有其他人使用你的Obj.h 是完全可以的。

一些参考:

Unit testing of private methods

【讨论】:

    【解决方案4】:

    正如@Marson Mao 所说,我投票支持#define private public。

    如果您想控制更多的私有或公开内容,您可以在 yourtests.cpp 中执行此操作

    #define private public
    #include "IWantAccessViolationForThis.h"
    #undef private
    #include "NormalFile.h"
    

    通过这种方式,您可以获得更多控制权,并尝试在尽可能少的地方执行此技巧。

    这种方法的另一个不错的特性是它是非侵入性的,这意味着您无需使用 #ifdefs 来混淆您的实际实现和头文件以进行测试而不是测试模式。

    【讨论】:

    • +1 用于 undef 私有和非侵入性;我认为非侵入性是#define private public 最好的部分。
    【解决方案5】:

    您的朋友可以完全访问它是朋友的班级。这可能有很多原因,其中一个很可能是出于单元测试目的,即您希望能够编写一个可以调用私有成员的单元测试并检查内部变量是否显示您希望它们显示的内容,但您不希望它成为公共 API 的一部分。

    你需要在你的班级标题中“暴露”友谊关系。因此,您必须承认存在用于测试您的课程的课程。不用担心,您在现实世界中进行开发并且课程已经过测试。

    为了编写单元测试,您需要实现该类以提供受保护的成员函数(可能是静态的),这些函数调用所有相关的私有函数或获取私有成员,然后您编写派生自您的类的类。请注意,这些将无法直接访问,因为友谊不是继承的,因此是静态受保护的成员。

    如果您使用 pImpl 惯用语,您可以将 pImpl 本身的成员全部公开,将 pImpl 本身设为私有,并让您的单元测试访问 pImpl(通过与上述相同的模型)。现在这更简单了,因为您只需为“测试人员”创建一种方法。

    关于类的数据成员,近年来我知道将所有这些都放入一个结构中,即将它们全部公开,然后让该类拥有该结构的私有实例。处理这种事情会更容易,也可以更容易地对你的类进行序列化/工厂,他们可以在其中创建所有公共的结构,然后从中构造你的类。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-07-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-23
      相关资源
      最近更新 更多