问题
假设有一个方法需要判断当前小时范围,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class Class1
{
public bool SomeMethod()
{
var hour = DateTime.Now.Hour;
if (hour >= 9 && hour <= 12)
{
return true;
}
return false;
}
}
|
但是,在做单元测试时,测试会偶尔失败。
因为 DateTime.Now
没法控制:
1
2
3
4
5
6
|
[Fact]
public void Test1()
{
var expect = new Class1().SomeMethod();
Assert.True(expect);
}
|
1. 接口替代
常用的解决方案是使用接口替代静态方法的直接调用,接口的实现可以使用依赖注入在运行时获得:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
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 框架生成接口的实例,返回一个固定值,保证单元测试始终成功:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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
,然后修改测试用例代码如下:
1
2
3
4
5
6
7
8
9
10
11
|
[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。