我正在尝试根据行为驱动开发进行编程,它指出如果不先编写失败的单元测试,就不应编写任何代码行。
我的问题是,如何将 BDD 与私有方法一起使用?
如何对私有方法进行单元测试?
是否有比以下更好的解决方案:
- 当我编写使用这些私有方法的公共方法时,先将私有方法公开,然后再将它们设为私有;
或
- 在 C# 中将所有私有方法设为内部并使用 InternalsVisibleTo 属性。
我正在尝试根据行为驱动开发进行编程,它指出如果不先编写失败的单元测试,就不应编写任何代码行。
我的问题是,如何将 BDD 与私有方法一起使用?
如何对私有方法进行单元测试?
是否有比以下更好的解决方案:
- 当我编写使用这些私有方法的公共方法时,先将私有方法公开,然后再将它们设为私有;
或
- 在 C# 中将所有私有方法设为内部并使用 InternalsVisibleTo 属性。
当您首先编写代码测试时,您是针对公共接口编写代码。此时没有私有方法。
然后编写代码以通过测试。如果任何代码被分解为私有方法,那并不重要——它应该仍然存在,只是因为它被公共接口使用。
如果代码没有先写测试,那么——无论如何——在.net中——反射可以用来直接生产私有方法;虽然这是最后的手段。
私有方法是内部实现细节。不应直接测试它们,因为它们将通过测试您的公共接口进行间接测试。如果由于某种原因在完全测试公共接口时未涵盖私有方法,则不需要私有方法,应将其删除。
通常,将测试代码绑定到私有实现细节是一个坏主意。这将您的测试与那些私人细节结合起来,减少了您随意更改这些细节的自由,即使它们不会影响面向公众的界面和行为。这增加了编写和维护单元测试所需的工作量,这是一件消极的事情。您应该争取尽可能多的覆盖范围,同时只绑定到公共接口。
简短的回答:你不测试私有方法。
如果你编程得很好,你的测试代码覆盖率应该是隐式测试私有方法。
如果存在私有方法方法,则它可以被公共方法使用。因此我会为公共方法编写一个测试。
我编写测试来测试课程的公共部分。如果课程设计得很好,那么默认情况下会测试私有部分。
如果私有方法不是从公共方法调用的,那它为什么存在呢?
在你的情况下,我会做以下
* Write failing test for the public method
* Write public method that calls the private method that doesn't exist yet(test still fails as your class is incomplete
* Write the private method
* Test should now pass
简短回答:您不能测试私有方法。
长答案:您无法测试私有方法,但如果您倾向于测试它所做的任何事情,请考虑重构您的代码。有两种简单的方法:
第一个很简单,但在编写更多测试时倾向于让您自己动手,而后者促进了更好的代码和测试设计。
做作的回答:好吧,所以我撒谎了。您可以借助一些反射魔法来测试私有方法(一些 TDD 工具支持测试私有方法)。但根据我的经验,它会导致复杂的单元测试。复杂的单元测试会导致更糟糕的代码。糟糕的代码会导致愤怒。愤怒导致仇恨。仇恨导致痛苦……
生产代码变差的直接影响是被测类趋于变大并且处理很多事情(违反单一职责原则)并且更难维护。这违背了 TDD 的目的,即让生产代码可测试、可扩展,更重要的是:可重用。
如果您正在为已部署的类编写测试,则可以调查调用私有方法的所有内容并相应地编写测试。如果您有机会重写课程,请通过拆分课程来重构它。如果你很幸运,那么你最终会得到一些你可以利用的代码重用。
我们可以分别使用 PrivateType 和 PrivateObject 对静态和实例私有方法进行单元测试。以下 2 篇文章解释了这些技术
我同意关于不测试私有方法本身以及应该针对公共 API 编写测试的观点,但是还有一个您没有在上面列出的选项。
您可以使方法受保护,然后从被测类派生。您可以使用派生类上的公共方法公开受保护的基方法,例如,
public class TestableClassToTest : ClassToTest
{
public new void MethodToTest()
{
base.MethodToTest();
}
}
您可能已经使用此提取和覆盖模式来覆盖基类的虚拟属性以进行依赖注入,在这种情况下,这对您来说可能是一个可行的选择。
Mbunit Reflector 可以帮助您解决这个问题。
Reflector objectReflection = new Reflector(new ObjectWithprivateMethods());
objectReflection.InvokeMethod(AccessModifier.NonPublic,,"Add",1,6));
我已经与它斗争了1个多月,但找到了答案:
var objectOfPrivateMethod = new ObjectOfPrivateMethod(); //yes here is contructor
object[] arguments = { }; // here as Object you provide arguments
var extractedPrivateMethod = typeof(ObjectOfPrivateMethod).GetMethod("Name_Of_Private_Method", BindingFlags.NonPublic|BindingFlags.Static); //if fails returns null. delete flag static if it's not static. Returns your method as an object.
Assert.AreNotEqual(null, extractedPrivateMethod, "Mathod does not exist"); // good to catch if even exists.
object result = extractedPrivateMethod.Invoke(null, arguments); // here as object you'll get return value of your function. change null for object of class where is method, if your method is not static
就这样。
您应该只测试您的类的外部 API,即公共方法。如果您的测试没有触及私有方法中的代码,那么您需要编写更多测试或重构类。
测试 API,尤其是分发给第三方的 API 的全部意义在于,只要不破坏其公共方法的外部契约,您就可以随意更改类的内部结构.
正如您所确定的,这就是 BDD 使用模拟类的“传统”TDD 发挥作用的地方,其中每个方法调用都必须提前设置以进行测试。我不是这两个方面的专家,希望其他人能比我更好地回答这个问题。
如果你发现自己想要测试一个私有方法,那么它里面有一些复杂的东西,你想要测试它可能是对的,这是一种设计味道。在界面上公开该方法只会将一种气味换成另一种更糟糕的气味。
是时候重构了:)
通常我将内部复杂性分解为辅助类。但是,请检查“特征嫉妒”或“不适当的亲密关系”的方法。可能有一个更好的地方住的方法。现在使用 .net 中的扩展方法,即使是基本类型也可能是一个不错的选择。
祝你好运
如果您真的认为私有方法足够复杂,值得对其进行单元测试 - 这表明您的类做的太多,您应该将部分或全部私有方法提取到接口后面的自己的类中.
在测试原始类时模拟接口。您现在应该有一个新类的公共访问器,它以前是私有方法。
有时在处理写得不好或没有使用 TDD 编写的旧代码时,可能需要测试私有类。在这种情况下,您应该使用反射,但在可能的情况下更新代码以更接近 TDD 方法。