我正在构建一个类库,它将有一些公共和私有方法。我希望能够对私有方法进行单元测试(主要是在开发过程中,但也可以用于将来的重构)。

正确的做法是什么?


当前回答

首先,您不应该测试代码的私有方法。你应该测试“公共接口”或API,即类的公共内容。API是所有公开给外部调用者的公共方法。

原因是一旦您开始测试类的私有方法和内部结构,您就将类的实现(私有的东西)耦合到您的测试中。这意味着当您决定更改实现细节时,您也必须更改您的测试。

出于这个原因,你应该避免使用internalsvisibletoattribute。

以下是Ian Cooper关于这个主题的演讲:Ian Cooper: TDD,哪里出了问题

其他回答

测试私有方法可能没有用处。然而,我有时也喜欢从测试方法调用私有方法。大多数时候为了防止代码重复用于测试数据生成…

微软为此提供了两种机制:

访问器

转到类定义的源代码 右键单击类名 选择“创建私有访问器” 选择要在其中创建访问器的项目 你将得到一个名为foo_accessor的新类。 该类将在编译期间动态生成,并提供所有公共成员。

然而,当涉及到原始类的接口更改时,该机制有时有点棘手。所以,大多数时候我避免使用这个。

PrivateObject类 另一种方法是使用microsoft。visualstudio。testtools。unittest。privateobject

// Wrap an already existing instance
PrivateObject accessor = new PrivateObject( objectInstanceToBeWrapped );

// Retrieve a private field
MyReturnType accessiblePrivateField = (MyReturnType) accessor.GetField( "privateFieldName" );

// Call a private method
accessor.Invoke( "PrivateMethodName", new Object[] {/* ... */} );

我还使用了InternalsVisibleToAttribute方法。值得一提的是,如果你为了达到这个目的而将你之前的私有方法变成内部方法,那么也许它们不应该成为直接单元测试的对象。

毕竟,您是在测试类的行为,而不是它的具体实现——您可以在不更改前者的情况下更改后者,并且您的测试仍然应该通过。

1)如果你有一个遗留代码,那么测试私有方法的唯一方法就是反射。

2)如果它是新代码,那么你有以下选项:

使用反射(使之复杂) 在同一个类中编写单元测试(使生产代码变得丑陋 其中还包含测试代码) 在某种util类中重构并使方法为公共 使用@VisibleForTesting注释并删除private

I prefer the annotation method, simplest and least complicated. The only issue is that we have increased the visibility which I think is not a big concern. We should always be coding to interface, so if we have an interface MyService and an implementation MyServiceImpl then we can have the corresponding test classes that is MyServiceTest (test interface methods) and MyServiceImplTest (test private methods). All clients should anyway be using the interface so in a way even though the visibility of the private method has been increased it should not really matter.

在调试模式下构建时,你也可以将其声明为public或internal(使用InternalsVisibleToAttribute):

    /// <summary>
    /// This Method is private.
    /// </summary>
#if DEBUG
    public
#else
    private
#endif
    static string MyPrivateMethod()
    {
        return "false";
    }

它使代码膨胀,但在发布版本中它将是私有的。

这里有一个例子,首先是方法签名:

private string[] SplitInternal()
{
    return Regex.Matches(Format, @"([^/\[\]]|\[[^]]*\])+")
                        .Cast<Match>()
                        .Select(m => m.Value)
                        .Where(s => !string.IsNullOrEmpty(s))
                        .ToArray();
}

下面是测试:

/// <summary>
///A test for SplitInternal
///</summary>
[TestMethod()]
[DeploymentItem("Git XmlLib vs2008.dll")]
public void SplitInternalTest()
{
    string path = "pair[path/to/@Key={0}]/Items/Item[Name={1}]/Date";
    object[] values = new object[] { 2, "Martin" };
    XPathString xp = new XPathString(path, values);

    PrivateObject param0 = new PrivateObject(xp);
    XPathString_Accessor target = new XPathString_Accessor(param0);
    string[] expected = new string[] {
        "pair[path/to/@Key={0}]",
        "Items",
        "Item[Name={1}]",
        "Date"
    };
    string[] actual;
    actual = target.SplitInternal();
    CollectionAssert.AreEqual(expected, actual);
}