[UnitTest] -How to test a private method in C#?

Posted by William Basics on Saturday, November 20, 2021

引言

对于是否要测试私有方法的争论由来已久。很多人对于这个问题总是不从正面回答,反而掉过头来尝试说服提问者放弃这个想法。是的,我们虽然有很多理由证明去测试一个私有方法不仅会遭人唾弃,而且纵欲过度还会引火烧身。

但是我们在开发中总是会遇到一些意外情况让我们不得不去测试它们。比如一个公共的方法调用了一个无比复杂的私有方法,但是仅仅通过设置公共方法的参数,并不能完全测试这个私有方法。

当然一个比较常见的建议就是,如果那个私有方法如此重要,我们是不是应该考虑把它作为一个公共方法挪到另一个类里去。这个建议是可行的,但是其引入了更多的复杂性,新的类的命名和意义,在整个架构中的位置等等都需要考虑。对于我们只是想做一个快速地验证和检查来讲,稍微有点过了。

适度地,谨慎地去测试私有方法,应该是最可取的方式。

下面我们来看看,如何测试私有方法。

如何测试私有方法

​ 测试私有方法目前有这些方法:

​ 第一种方法就是之前提到的,我们把私有方法提取出来作为另一个类的公共方法。

把私有方法提取出来作为另一个类的公共方法,然后我们就能对这个类的这个公共方法进行单元测试。这应该是最佳实践了。但是在团队开发中,创建新类并不是随心所欲的。在你想做这个单元测试的当时当刻,可能并不具备开会讨论的时间和条件。那么这个方法就不适用了。

​ 第二种方法是 利用 InternalsVisibleTo attribute

InternalsVisibleTo attribute 是用来告知runtime,把这个assembly里的internal 可见的类型和方法 让另一个assembly 可以访问。

具体来说,就是把待测私有方法改成 internal 方法,然后 InternalsVisibleTo 指向unit test的assembly。

例如,我们unit test的assembly的名称为 OrderService.Test,待测方法是 CalculateInternalRate

[assembly: InternalsVisibleTo("OrderService.Test")]

public class OrderService
{
   internal string CalculateInternalRate(Strategy strategy)
   {
			switch(strategy) {
				case Strategy.One:
					...
			}
   }
}

这种方法虽然简单,但是需要对原来的设计进行修改,将private改成internal。这可能会破坏类的封装性,违反设计的本意。

第三种方法是 利用反射来访问private方法。这个方法就很直接明了了。

例如,我们要测 类 MainWindowViewModelCalculation方法

// 待测类
public class MainWindowViewModel
{
    public string Title { get; set; }

    private int Calculation(int n)
    {
        return n * n;
    }
}

那么单元测试可以这么编写

using System;
using System.Reflection;
using FluentAssertions;
using Xunit;

...
[Fact]
public void CalculationShouldWork()
{
    var n = 10;

    var result =typeof(MainWindowViewModel)
        .GetMethod("Calculation", BindingFlags.Instance|BindingFlags.NonPublic)
        .Invoke(new MainWindowViewModel(), new object[]{n});
    result.Should().Be(100);
}

这种方法好在简单直接,不用对待测代码进行修改。

​ 第四种方法是 利用PrivateObject

同样的MainWindowViewModel 的例子, 单元测试可以这样实现

[Fact]
public void CalculationShouldWork()
{
    var n = 10;

    var po = new PrivateObject(typeof(MainWindowViewModel));
    var result = po.Invoke("Calculation", new object[] { n });
    result.Should().Be(100);
}

它实际上是对第三种方法的简化。但是需要安装 MSTest.TestFramework, 并且到目前为止还不支持 .NET Core。所以适用性没有第三种方法好。

结语

综上对比,第三种 利用反射的方法应该是目前的最佳选择。

最后,还是建议大家对待测试私有方法这件事要谨慎考虑,说不定这正是改善代码的一个契机,不妨稍作思考,善加利用。

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。