在 C++ 中使用友元类与添加访问器进行单元测试?
添加返回对象内部状态的函数进行单元测试是否比使测试类成为友元更好? - 特别是,当除了单元测试之外的功能没有用处时。
Is it better to add functions that return the internal state of an object for unit testing, as opposed to making the testing class a friend? - especially, when there is no use for the functions except for the case of unit testing.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
95% 的时间单元测试应该只测试类的公开暴露的表面。 如果您正在幕后测试某些内容,那就是测试实现细节,这本质上是脆弱的,因为您应该能够轻松更改实现并仍然让测试工作。 它不仅脆弱,而且您还可能会尝试测试在计划的使用场景中实际上不可能的东西,这是浪费时间。
如果你想添加的访问器的目的只是为了测试函数是否达到了预期的效果,那么你的类设计可能会违反另一个原则,即类状态机的类应该始终明确它所处的状态,如果这会影响人们与班级互动时发生的事情。 在这种情况下,提供这些只读访问器是正确的。 如果它不影响类的行为,请参阅我之前有关实现细节的内容。
正如您正确地说的那样,用未使用的东西弄乱班级的公共表面也是不可取的,因为其本身的原因。
如果我必须在您的情况下在访问器和好友之间进行选择,我会选择好友,仅仅是因为您拥有您的测试并且可以在紧要关头更改它。 您可能不拥有找到使用您的额外访问器的方法的小丑的代码,然后您就会陷入困境。
Unit tests should 95% of the time only test the publicly exposed surface of a class. If you're testing something under the covers, that's testing implementation details, which is inherently fragile, because you should be able to easily change implementation and still have the tests work. Not only is it fragile, but you could also be tempted into testing things that aren't actually possible in planned usage scenarios, which is a waste of time.
If the point of the accessors you want to add is just to test whether the function had the desired effect, your class design may violate another principle, which is that a state-machine-like class should always make it clear what state its in, if that affects what happens when people interact with the class. In that case, it'd be right to provide those read-only accessors. If it doesn't affect the class's behavior, refer back to my previous bit about implementation details.
And as you rightly said, cluttering up the public surface of a class with unused stuff is also undesirable for its own reasons.
If I had to pick between accessors and friending in your case, I would choose friending, simply because you own your test and can change it in a pinch. You may not own the code by the clown who finds a way to use your extra accessors, and then you'll be stuck.
我不同意已接受的答案,而是建议使用朋友类。
您正在测试的部分状态可能特定于您的类的实现; 您正在测试其他代码通常不知道或不关心且不应该依赖的细节。 公共访问器函数使这些实现细节成为类接口的一部分。 如果您正在测试的内部状态不是预期接口的一部分,则它不应该通过公共函数可见。 从纯粹主义者的角度来看,您陷入了两个错误答案之间,因为友元类在技术上也是公共接口的一部分。 在我看来,问题是,哪种选择不太可能导致日后出现糟糕的编码选择? 使用一组依赖于实现的公共访问器函数将无意中鼓励类的依赖于实现的概念模型,从而导致类的依赖于实现的使用。 适当命名和记录的单一朋友类不太可能被滥用。
虽然总的来说,我强烈同意优先使用访问器函数而不是直接访问成员变量的建议,但我不同意这种最佳实践适用于依赖于实现的内部状态的单元测试。 合理的中间立场是对单元测试将关心的那些状态片段使用私有访问器函数,并遵守足够的纪律在单元测试中使用访问器函数。 只是我的观点。
I'll disagree with the accepted answer and instead recommend the use of a friend class.
Part of the state you are testing is probably specific to the implementation of your class; you're testing details that other code normally doesn't know about or care about and shouldn't rely on. Public accessor functions make these implementation details part of the class's interface. If the internal state you are testing is not part of the intended interface, it shouldn't be visible through public functions. From a purist point of view you're stuck between two wrong answers, as friend classes are also technically part of the public interface. In my mind the question becomes, which option is less likely to lead to poor coding choices down the road? Going with a set of implementation-dependent public accessor functions will inadvertantly encourage an implementation-dependent conceptual model of the the class, leading to implementation-dependent use of the class. A single friend class, appropriately named and documented, is less likely to be abused.
While in general I strongly agree with the recommendation to prefer accessor functions over direct access to member variables, I don't agree that this best practice applies to unit testing of implementation-dependent internal state. A reasonable middle ground is to use private accessor functions to those pieces of state your unit test will care about, and be disciplined enough to use the accessor functions in your unit tests. Just my opinion.
使用友元类进行单元测试是完全合法的,并且允许您维护封装。 您不应该仅仅为了使类更易于测试而修改类的公共接口。 这样想吧。 如果您购买了第三方 FTP 库并尝试使用它,而它的公共界面中充斥着一堆您甚至不需要了解的方法(仅仅因为单元测试),该怎么办! 即使修改受保护的接口来补偿单元测试也是不好的。 如果我从某个类继承,我不想担心哪些方法对我有用以及哪些方法仅因单元测试而存在! 使用友元类进行单元测试可以帮助您维护一个简单、更易于使用的类接口; 它有助于保持封装和抽象!
我听说过这样的论点:使用友元类进行单元测试是不好的,因为被测类不应该与其测试类“紧密耦合”,并且不应该“知道”有关其测试类的任何信息。 我不买这个。 它是添加到类顶部的一行:
friend class MyClassTest;
现在您可以以任何您想要的方式测试您的课程!
现在我确实同意除非有必要否则你不应该使用朋友类。 如果您可以测试需要测试的内容而不将其作为朋友,那么请务必这样做。 但是,如果生活变得困难,而使用朋友班级让生活再次变得轻松,那就使用它吧!
Using friend classes for unit testing is a perfectly legitimate and allows you to maintain encapsulation. You should not modify your classes public interface simply to make the class more testable. Think about it this way. What if you purchased a third party FTP library and you are trying to use it and it's public interface is cluttered with a bunch of methods you don't even need to know about simply because of unit tests! Even modifying a protected interface to compensate for unit tests is BAD. If I am inheriting from some class, I don't want to have to worry about what methods are useful to me and which ones are only there because of unit tests!!! Using friends classes for unit tests helps you maintain a simple, easier to use class interface; it helps to preserve encapsulation and abstraction!!!
I have heard the argument that using friend classes for unit testing is bad because the class under test should not be "tightly coupled" with its test class and it should not "know" anything about its test class. I do not buy this. It's a single line added to the top of the class:
friend class MyClassTest;
and now you can test your class any way you want!
Now I do agree that you should not use a friend class unless it is necessary. If you can test what needs testing without making it a friend, by all means do. But if life gets difficult and using a friend class makes life easy again, use it!
我建议使用访问器,而不是允许通过公共成员或友元类进行访问。
我不认为使用朋友类实际上会给你带来任何好处,而且它有可能让你以后的生活变得更糟。 如果您的代码将保留很长一段时间,那么它很可能会以您意想不到的方式使用。 访问功能现在可能只用于测试,但谁知道将来会发生什么? 使用访问器而不是提供对变量的直接访问可以为您提供更大的灵活性,并且成本非常低。
另一个论点是使用访问器而不是公共成员是一个好习惯。 养成良好的习惯是作为程序员的一项重要技能。
I recommend using accessors rather than allowing access via public members or friend classes.
I don't think that using friend class actually gets you any benefits, and it has the potential to make your life a lot worse down the road. If your code is going to stay around for a long time there's a good chance that it might be used in ways that you don't anticipate. The access functions might be only used for testing now, but who knows what will happen in the future? Using accessors rather than providing direct access to variables gives you a lot more flexibility, and it has a very low cost.
The other argument is that using accessors rather than public members is a good habit. Developing good habits is an important skill as a programmer.
如何让内部状态“受到保护”?
然后使用派生类进行单元测试。
How about making internal state "protected"?
And then do unittest using derived class.
我认为需要区分通过向用户提供访问器(如果这样做有意义)来保证类的未来性和提高可测试性。 我也不热衷于为了测试的唯一目的而交友类,因为这会在我不喜欢的地方引入紧密耦合。
如果访问器的唯一用途是为测试用例提供一种检查类内部状态的方法,那么公开公开它们通常没有意义。 它还可以限制您稍后可能希望更改的实现细节,但随后发现您无法更改,因为其他人正在使用所述访问器。
我的首选解决方案是提供受保护的访问器函数,以便向类的用户明确传达这些函数不是公共接口的一部分。 然后,您的测试将创建一个原始的最小派生类,其中包含父函数的调用存根,但也使访问器公开,以便您可以在测试用例中使用它们。
I think there needs to be a distinction between future-proofing the class by providing accessors to its users if it makes sense to do so, and improving testability. I'm also not a big fan of befriending classes for the sole purpose of testing as this introduces a tight coupling in a place where I prefer not to have it.
If the sole use of the accessors is to provide a way for the test cases to check internal state of the class, it normally doesn't make sense to expose them publicly. It also can tie down implementation details that you might wish to change later on but then find you can't because someone else is using said accessors.
My preferred solution for this would be to provide protected accessor functions to communicate clearly to the class's users that these are not part of the public interface. Your tests would then create a minimal derived class of the original which contains call-through stubs for the parent's functions but also makes the accessors public so you can make use of them in the test cases.