我正在为我的一些代码编写单元测试,并遇到了一个案例,我有一个具有小暴露接口但内部结构复杂的对象,因为每个公开的方法都运行大量的内部函数,包括对象的依赖性州。这使得外部接口上的方法很难进行单元测试。
我最初的问题是我是否应该单独测试这些内部函数,因为它们更简单,因此更容易编写测试?我的直觉是肯定的,这导致后续问题,如果是这样,我将如何在C ++中这样做呢?
我提出的选项是将这些内部函数从private更改为protected,并使用friend类或inheritence来访问这些内部函数。这是保持内部方法隐藏的一些语义的最佳/唯一方法吗?
答案 0 :(得分:16)
如果您的对象执行非常难以通过有限公共接口测试的高度复杂操作,则可以选择将一些复杂逻辑分解为封装特定任务的实用程序类。然后,您可以单独对这些类进行单元测试将代码组织成容易消化的块总是一个好主意。
答案 1 :(得分:11)
简答:是的。
关于如何,我几天前在SO上找到了一个过往的参考文献:
#define private public
在读取相关标题之前评估的单元测试代码中... 同样对于受保护。
很酷的想法。
稍微长一点的答案:测试代码是否显然是正确的。这基本上意味着任何代码都可以做一些非平凡的事情。
考虑到这一点,我很想知道这一点。您将无法链接到在生产版本中使用的同一对象文件。现在,单元测试是一个人为的环境,所以也许这不是一个交易破坏者。任何人都可以对这一伎俩的利弊有所了解吗?
答案 2 :(得分:5)
我个人认为,如果测试公共接口不足以充分测试私有方法,则可能需要进一步分解类。我的理由是:私有方法应该只做足以支持公共接口的所有用例。
但我的单位测试经验(不幸)很苗条;如果有人可以提供一个令人信服的例子,其中大量的私人代码无法分开,我当然愿意重新考虑!
答案 3 :(得分:5)
有几种可能的方法。假设你的班级是X:
答案 4 :(得分:3)
我的意见不是,通常他们应该不直接测试。
从系统的更高角度来看,单元测试是白框,但从测试类接口(其公共方法及其预期行为)的角度来看,它们应该是黑盒。
例如,一个字符串类(不需要遗留char *支持):
这使您可以在不触及后续测试的情况下重构实现 这有助于您通过强制执行职责来减少耦合 这使您的测试更容易维护
您希望更彻底地验证相当复杂的辅助方法,例外情况 但是,这可能暗示这段代码应该是“官方的”,因为它是公开静态的,或者用它的公共方法在自己的类中提取。
答案 5 :(得分:2)
我想说使用代码覆盖工具来检查这些功能是否已经过某种程度的测试。
理论上,如果您的公共API通过了所有测试,那么只要涵盖了每个可能的场景,私有函数就可以正常工作。我认为这是主要问题。
我知道有使用C / C ++的工具。 CoverageMeter就是其中之一。
答案 6 :(得分:2)
除非您正在制作通用库,否则您应该尝试将所构建的内容限制为您将使用的内容。根据需要进行扩展。 因此,您应该拥有完整的代码覆盖率,并且应该对其进行全部测试。
也许你的代码有点臭?是时候重构了吗? 如果你有一个很大的类在内部做很多事情,也许它应该分成几个较小的类,你可以单独测试接口。
答案 7 :(得分:2)
如果您使用测试驱动开发,我一直认为这会倾向于落实到位。有两种方法可以处理开发,要么是从公共接口开始,要么在每次添加复杂的私有方法之前编写一个新的测试,要么开始将复杂的东西作为公共工作,然后重构代码以使方法成为私有以及您已编写的测试使用新的公共接口。无论哪种方式,你都应该得到全面报道。
当然,我从来没有设法以严格的tdd方式编写整个应用程序(甚至是类),如果可能的话,将复杂的东西重构为实用程序类是可行的。
答案 8 :(得分:0)
你总是可以在私人网站周围使用编译开关:比如
#if defined(UNIT_TEST)
或者使用代码覆盖率工具验证您的公共功能的单元测试是否完全运用了私有功能。
答案 9 :(得分:0)
是的,你应该。它被称为白盒测试,这意味着你必须知道很多关于程序内部的正确测试。
我会创建调用私有函数进行测试的公共“存根”。 #ifdef存根,以便在测试完成时将它们编译出来。
答案 10 :(得分:0)
编写测试程序可能会觉得很有效。在测试程序中,创建一个使用要测试的类作为基础的类。
向新类添加方法,以测试在公共接口中不可见的函数。有你的简单测试程序,调用这些方法来验证你关注的函数。
答案 11 :(得分:0)
如果您的类正在执行复杂的内部计算,则实用程序类甚至是外部函数可能是中断计算的最佳选择。但是,如果对象具有复杂的内部结构,则该对象应具有一致性检查功能。即,如果对象代表一棵专门的树,则该类应具有一些方法来检查树是否仍然正确。诸如树深度之类的其他功能通常对此类用户很有用。其中一些函数可以在#ifdef DEBUG或类似结构中声明,以限制嵌入式应用程序中的运行时空间。从封装的角度来看,使用仅在设置DEBUG时才编译的内部函数会更好。您没有破坏封装。另外,依赖于实现的测试与实现一起保存,因此很明显,当实现更改时,测试也需要更改。