C#怎么测试静态方法?我给出了2种方案

问题

假设有一个方法需要判断当前小时范围,代码如下:.

public class Class1
{

    public bool SomeMethod()
    {
        var hour = DateTime.Now.Hour;
        if (hour >= 9 && hour <= 12)
        {
            return true;
        }

        return false;
    }
}

但是,在做单元测试时,测试会偶尔失败。

因为DateTime.Now没法控制

[Fact]
public void Test1()
{
    var expect = new Class1().SomeMethod();
    Assert.True(expect);
}

怎么办?!

1.接口替代

常用的解决方案是使用接口替代静态方法的直接调用,接口的实现可以使用依赖注入在运行时获得:

public interface IClock
{
    DateTime Now { get; }
}

public class Class1
{
    public Class1(IClock clock)
    {
        this.Clock = clock;
    }

    public IClock Clock { get; }

    public bool SomeMethod()
    {
        var hour = Clock.Now.Hour;
        if (hour >= 9 && hour <= 12)
        {
            return true;
        }

        return false;
    }
}

可以手工或使用第三方Mock框架生成接口的实例,返回一个固定值,保证单元测试始终成功:

public class MockClock :IClock
{
    private readonly DateTime _now;

    public MockClock(DateTime now)
    { 
        this._now = now; 
    }

    public DateTime Now => _now;
}

[Fact]
public void Test1()
{
    var expect = new Class1(new MockClock(new DateTime(1900,1,1,9,0,0)))
            .SomeMethod();
    Assert.True(expect);
}  

2.Pose Mock

使用第三方Mock框架,比如Pose,直接Mock静态方法。

无需修改业务代码实现,只需在单元测试项目中引入nuget包Pose,然后修改测试用例代码如下:

[Fact]
public void Test1()
{
    Shim dateTimeShim = Shim.Replace(() => DateTime.Now).With(()=>new DateTime(1900, 1, 1, 9, 0, 0));
    
    PoseContext.Isolate(() =>
    {
        var expect = new Class1().SomeMethod();
        Assert.True(expect);
    }, dateTimeShim);
}

结论

这2种方案各有优缺点:

  • 接口替代
    • 优点:实现简单
    • 缺点:所有调用原静态方法的业务代码都需要增加依赖注入参数
  • Pose Mock
    • 优点:无需修改业务代码
    • 缺点:第三方库不能保证Mock所有静态方法成功

建议,针对新功能尽量使用接口,仅对不易改动的代码才使用Pose Mock。