将unit testing作为正在testing的类的朋友有什么不好?
在C ++中,我经常把一个unit testing类作为我正在testing的类的一个朋友。 我这样做是因为我有时觉得需要编写一个私有方法的unit testing,或者我想访问一些私有成员,这样我可以更容易地设置对象的状态,所以我可以testing它。 对我来说,这有助于保持封装和抽象,因为我不修改该类的公共或受保护的接口。
如果我购买第三方库,我不希望它的公共接口受到一些公共方法的污染,我不需要知道,因为供应商想unit testing!
我也不需要担心一群受保护的成员,如果我从一个class级inheritance,我不需要知道这些成员。
这就是为什么我说它保留了抽象和封装。
在我的新工作中,他们甚至对unit testing也不喜欢使用朋友class。 他们说,因为class级不应该“知道”有关testing的任何事情,而且你不希望class级与testing紧密结合。
有人可以向我解释这些原因,以便我可以更好地理解? 我只是不明白为什么使用朋友进行unit testing是不好的。
理想情况下,你根本不需要单独testing私有方法。 你们class的所有消费者都应该关心的是公共接口,所以这就是你应该testing的。 如果一个私有方法有一个bug,它应该被一个unit testing所捕获,这个unit testing会调用这个类的一些公共方法,最终会调用这个bug的私有方法。 如果一个错误能够解决,这表明你的testing用例不能完全反映出你希望你的课程实现的合同。 这个问题的解决scheme几乎可以肯定的是testing更公开的方法,而不是让你的testing案例挖掘到类的实现细节。
再次,这是理想的情况。 在现实世界中,情况可能并不总是如此清晰,unit testing课是testing类的朋友,可能是可以接受的,甚至是可取的。 不过,这可能不是你想要做的事情。 如果它似乎经常出现,这可能表明你的class级太大和/或执行太多的任务。 如果是这样的话,通过将复杂的私有方法集重构成单独的类来进一步细分它们将有助于消除对unit testing的需求以了解实现细节。
你应该考虑有不同的风格和方法来testing: 黑盒testing只testing公共接口(把这个类视为一个黑盒子)。 如果你有一个抽象的基类,你甚至可以对所有的实现使用相同的testing。
如果你使用白盒testing ,你甚至可以看看实现的细节。 不仅是一个类有哪些私有方法,而且包含了什么样的条件语句(例如,如果你想增加你的条件覆盖率,因为你知道条件很难编码)。 在白盒testing中,你肯定有类/实现和testing之间的“高度耦合”,因为你想testing实现而不是接口。
正如bcat指出的那样,使用组合和更多更小的类,而不是许多私有方法通常是有帮助的。 这简化了白盒testing,因为您可以更轻松地指定testing用例来获得良好的testing覆盖率。
我觉得Bcat给出了一个很好的答案,但是我想说明他提到的例外情况
在现实世界中,情况可能并不总是那么清楚,unit testing课是testing类的朋友,可能是可以接受的,甚至是可取的。
我在一家拥有大量遗留代码库的公司工作,这有两个问题,都有助于使朋友进行unit testing。
- 我们忍受着大量需要重构的大型函数和类,但是为了重构它有助于进行testing。
- 我们的大部分代码都依赖于数据库访问,由于各种原因,不应将其纳入unit testing。
在某些情况下,Mocking对缓解后一个问题是有用的,但是通常这会导致复杂的devise(没有其他需要的类),但是可以通过以下方式简单地重构代码:
class Foo{ public: some_db_accessing_method(){ // some line(s) of code with db dependance. // a bunch of code which is the real meat of the function // maybe a little more db access. } }
现在我们有了这个function的肉需要重构的情况,所以我们想要一个unit testing。 它不应该公开暴露。 现在,在这种情况下可以使用一种叫做嘲笑的奇妙技术,但事实是,在这种情况下,一个模拟是矫枉过正的。 这将需要我用不必要的层面来增加devise的复杂性。
更实际的做法是做这样的事情:
class Foo{ public: some_db_accessing_method(){ // db code as before unit_testable_meat(data_we_got_from_db); // maybe more db code. } private: unit_testable_meat(...); }
后者为我提供了unit testing所需的所有好处,包括给我提供了一个宝贵的安全networking,用于捕获重构代码时产生的错误。 为了进行unit testing,我必须要有一个UnitTest类的朋友,但我强烈地认为,这比一个无用的代码规范要好得多,只是为了让我使用Mock。
我认为这应该成为一个成语,我认为这是一个合适的,务实的解决scheme,以提高unit testing的投资回报率。
就像bcatbuild议的一样,尽可能地使用公共接口本身来查找错误。 但是如果你想做一些事情,如打印私有variables和比较期望的结果等(对于开发人员来说很容易debugging问题很有帮助),那么你可以使UnitTest类成为朋友类来testing。 但是您可能需要将其添加到下面的macros下面。
class Myclass { #if defined(UNIT_TEST) friend class UnitTest; #endif };
仅在需要unit testing时启用标志UNIT_TEST。 对于其他版本,您需要禁用此标志。
在许多情况下,我没有看到使用朋友unit testing课程的任何错误。 是的,把大class分解成小class有时候是更好的办法。 我认为人们有点过于草率地使用friend关键字这样的东西 – 它可能不是理想的面向对象的devise,但如果这是我真正需要的,我可以牺牲一点点理想主义来获得更好的testing覆盖率。
通常你只testing公共接口,以便你可以自由地重新devise和重构实现。 为私人成员添加testing用例定义了对您的课程实施的要求和限制。
使您想要testing的function得到保护。 现在在你的unit testing文件中,创build一个派生类。 创build公共包装函数,调用您的受testing类受保护的函数。