移除耦合,然后模拟单元测试
本文关键字:模拟 单元测试 然后 耦合 | 更新日期: 2023-09-27 18:16:30
这是一个两难的选择。假设我们有两个类
Class A
{
public int memberValue;
}
interface IB
{
int fun();
}
Class B : IB
{
public int fun()
{
var a = new A();
switch(a.memberValue)
{
case 1:
//do something
break;
case 2:
//do something
break;
}
}
}
现在显示了两个紧密耦合的类。为了测试b.f unfun(),我们需要模拟类A,并为a.p membervalue提供多个值。
由于A对象在B.fun()范围之外的任何地方都不需要,我不明白为什么要通过B的构造函数注入它。如何对fun()方法进行单元测试?
首先,您可能也应该为A
创建一个接口,但如果这只是一个简单的POCO数据类,那么最好将其属性设置为virtual
,以允许mock。我认为你有三个选择:
-
将A注入
B
的构造函数,如果它是一个经常使用的类(例如,日志类或其他东西)。然后,您可以在测试中创建一个模拟版本,以检查如何使用A
(记住,模拟是用于测试具有依赖关系的行为)。public class A : IA { ... } public class B : IB { private readonly A a; public B(IA a) { this.a = a; } public void Func() { //... use this.a ... } } [Test] public void Func_AHasValue1_DoesAction1() { Mock<IA> mock = new Mock<IA>(); mock.Setup(a => a.somevalue).Returns("something"); B sut = new B(mock.Object); sut.Func(); mock.Verify(m => m.SomethingHappenedToMe()); }
- 传递
A
的方法,如果它是B
需要工作的东西(因为它似乎在这里)。您仍然可以创建模拟版本以在测试中使用。这与上面的代码相同,但是mock
被传递给方法而不是构造函数。如果A
是在运行时生成的一些数据类,而不是具有行为的类,则这是更好的方法。 -
为
A
创建一个工厂类并将其注入构造函数。更改方法以从工厂获取A
实例,并在测试中注入模拟工厂以验证行为。public class AFactory : IAFactory { public IA Create() { ... } } public class B : IB { private readonly IAfactory factory; public B(IAFactory factory) { this.factory = factory; } public void Func() { IA a = factory.Create(); //... use this.a ... } } [Test] public void Func_AHasValue1_DoesAction1() { Mock<IAFactory> mockFactory = new Mock<IAFactory>(); Mock<IA> mock = new Mock<IA>(); mockFactory.Setup(f => f.Create()).Returns(mock.Object); mock.Setup(a => a.somevalue).Returns("something"); B sut = new B(mockFactory.Object); sut.Func(); mock.Verify(m => m.SomethingHappenedToMe()); }
- 选项1是可以在没有任何运行时信息(例如日志类)的情况下构建类的标准方法。
- 选项2更适合于输入只是在运行时生成的数据类(例如,用户填写表单,而您有一个表示表单输入的POCO数据类)。
- 选项3是更好的,当
A
是有行为的东西,但不能没有在运行时生成的东西创建。
你可以用几种方法来做到这一点。最简单的可能是创建工厂方法,并在测试中创建继承B
并覆盖工厂方法的类。我认为这是Feathers或Gojko Adzic在这种情况下提出的建议(我真的不记得我在谁的书中读过,我想是Feathers的《有效地使用遗留代码》)。示例实现类似于:
class B : IB
{
public int fun()
{
A a = this.CreateA();
...
}
protected A CreateA() { return new A(); }
}
,在单元测试中:
class BTest : B
{
protected override A CreateA() { return mockA(); }
}
这是一个非常简单和直接的解决方案,它不限制类级别的耦合,但至少将不同的功能移动到不同的方法中,因此执行某些操作的方法不关心对象创建。
但是你需要仔细考虑它是否真的是你想要的。对于小而短的项目来说,这是可以的。对于更大的或长期的东西,重构代码并从B
中移除对A
的依赖以使类之间的耦合更少可能是有用的。此外,您所展示的模式开始看起来像Strategy
设计模式。也许你不应该注入A
,而是策略对象,将知道如何处理您的当前情况?当我看到if
阶梯或switch
语句时,我总是会想到这一点。