如何对私有方法进行单元测试?

510

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

正确的操作方式是什么?


3
也许是我漏掉了什么,或者可能只是因为这个问题在互联网年代上已经过时了,但是现在对私有方法进行单元测试变得非常容易和直截了当,使用Visual Studio可以在需要时生成必要的访问器类,并将测试逻辑预填充为接近所需用于简单功能测试的代码段。请参阅例如http://msdn.microsoft.com/en-us/library/ms184807%28VS.90%29.aspx - mjv
5
这似乎是与 https://dev59.com/bHVD5IYBdhLWcg3wRpaX 相近的内容。 - Raedwald
6
不要对内部单元进行单元测试:http://blog.ploeh.dk/2015/09/22/unit-testing-internals - Mark Seemann
自 2012 年起,Visual Studio 已将私有访问器(Private Accessors)列为弃用项。 - Blaine DeLancey
显示剩余3条评论
32个回答

2

MbUnit推出了一个名为Reflector的不错的包装器。

Reflector dogReflector = new Reflector(new Dog());
dogReflector.Invoke("DreamAbout", DogDream.Food);

你也可以设置和获取属性的值。

dogReflector.GetProperty("Age");

关于“测试私有”,我同意在完美的世界中,没有必要进行私有单元测试。但在现实世界中,你可能会想编写私有测试而不是重构代码。


4
仅供参考,Reflector 已经被更强大的 Mirror 替代,出现在 Gallio/MbUnit v3.2 中。(http://www.gallio.org/wiki/doku.php?id=mbunit:mirror) - Yann Trevin

2

这里有一篇关于测试.NET中私有方法的好文章。但我不确定哪种方式更好,是为测试而设计应用程序(就像仅为测试创建测试)还是使用反射进行测试。相信大多数人会选择第二种方式。


2

我使用PrivateObject类。但是,如前所述,最好避免测试私有方法。

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(retVal);

1

在调试模式下,您还可以将其声明为公共或内部(使用InternalsVisibleToAttribute):

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

它会使代码变得臃肿,但在发布版本中将会是“private”。

1

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

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);
}

1

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

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

  • 使用反射(过于复杂)
  • 在同一类中编写单元测试(使生产代码变得丑陋,因为测试代码也在其中)
  • 重构并在某种工具类中将方法公开
  • 使用@VisibleForTesting注释并删除私有部分

我更喜欢注释方法,最简单和最不复杂。唯一的问题是我们增加了可见性,我认为这不是一个大问题。 我们应该始终编写接口,所以如果我们有一个接口MyService和一个实现MyServiceImpl,那么我们可以有相应的测试类,即MyServiceTest(测试接口方法)和MyServiceImplTest(测试私有方法)。所有客户端都应该使用接口,因此即使私有方法的可见性已经增加,它也不应该真正的影响。


1
一种实现方式是将你的方法设置为protected,并编写一个测试夹具,该测试夹具继承你要测试的类。这样,你不会将你的方法改为public,但可以进行测试。

我不同意这种做法,因为它会让你的用户从基类继承并使用受保护的函数。这正是你最初想要通过将这些函数设为私有或内部来防止的事情。 - Nick N.

1
在我看来,你应该只对类的公共API进行单元测试。
为了进行单元测试而将方法公开会破坏封装性并暴露实现细节。
一个良好的公共API解决客户端代码的一个即时目标,并完全解决该目标。

在我看来,这应该是正确的答案。如果您有许多私有方法,则可能是因为您有一个隐藏的类,应将其拆分为自己的公共接口。 - sunefred

0

在C#中,您可以使用我提供的代码。虽然我认为只有在绝对需要的情况下才应该对私有方法进行单元测试。我遇到过一些情况,觉得这是必要的。以下是我在一个UnitTestBase类中创建的一些C#方法,我从中继承我的UnitTest类(您也可以将其放在静态的“helper”类中)。希望对您有所帮助。

protected internal static TResult? InvokePrivateInstanceMethod<TType, TResult>(string methodName, object?[]? methodArguments = null, params object?[]? constructorArguments)
{
    var classType = typeof(TType);
    var instance = Activator.CreateInstance(classType, constructorArguments);
    var privateMethodInfo = classType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
                                        .FirstOrDefault(m => m.IsPrivate &&
                                            m.Name.Equals(methodName, StringComparison.Ordinal) &&
                                            m.ReturnType.Equals(typeof(TResult)));
 
    if (privateMethodInfo is null)
    {
        throw new MissingMethodException(classType.FullName, methodName);
    }

    var methodResult = privateMethodInfo.Invoke(instance, methodArguments);
    if (methodResult is not null)
    {
        return (TResult)methodResult;
    }

    return default;
}

protected internal static async Task<TResult?> InvokePrivateInstanceMethodAsync<TType, TResult>(string methodName, object?[]? methodArguments = null, params object?[]? constructorArguments)
{
    var classType = typeof(TType);
    var instance = Activator.CreateInstance(classType, constructorArguments);
    var privateMethodInfo = classType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
                                        .FirstOrDefault(m => m.IsPrivate &&
                                            m.Name.Equals(methodName, StringComparison.Ordinal) &&
                                            m.ReturnType.Equals(typeof(Task<TResult>)));
            
    if (privateMethodInfo is null)
    {
        throw new MissingMethodException(classType.FullName, methodName);
    }

    var methodResult = privateMethodInfo.Invoke(instance, methodArguments);
    if (methodResult is not null)
    {
        return await (Task<TResult>)methodResult;
    }

    return default;
}

0
你可以在Visual Studio 2008中为私有方法生成测试方法。当你为一个私有方法创建单元测试时,测试项目会添加一个Test References文件夹,并在该文件夹中添加一个访问器。该访问器也被引用于单元测试方法的逻辑中。这个访问器允许你的单元测试调用你正在测试的代码中的私有方法。 详情请参考:

http://msdn.microsoft.com/en-us/library/bb385974.aspx


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接