Kar*_*sar 7 c# unit-testing dependency-injection ninject
关于如何设计适合单元测试的应用程序,我遇到了一个问题.
我正在尝试实施SRP(单一责任原则),据我所知,这涉及在单独的专用类中拆分大多数功能以保持代码更有条理.例如,我有这个特定的场景.
一个类RecurringProfile,它有一个方法.ActivateProfile().此方法的作用是将状态标记为已激活,并为下一个截止日期创建下一个(第一个)定期付款.例如,我打算拆分功能,以便在单独的类中创建下一个定期付款RecurringProfileNextPaymentCreator.我的想法是让这个类'RecurringProfile'在它的构造函数中作为参数:
RecurringProfileNextPaymentCreator(IRecurringProfile profile)
Run Code Online (Sandbox Code Playgroud)
但是,我认为这对于单元测试来说会有问题.我想创建一个测试ActivateProfile功能的单元测试.此方法将获取IRecurringProfileNextPaymentCreatorvia依赖注入(Ninject)的实例,并调用该方法.CreateNextPayment.
我创建一个单元测试的想法是创建一个模拟IRecurringProfileNextPaymentCreator和替换,以便我可以验证.ActivateProfile()实际调用的方法.但是,由于构造函数参数,这不适合作为NInject的默认构造函数.必须为这种情况创建一个自定义的NInject提供程序(我可以在整个解决方案中拥有许多这样的类)将有点矫枉过正.
任何想法/最佳实践如何进行此操作?
- 下面是关于上述示例的示例代码:(请注意,代码是手写的,在语法上不是100%正确)
public class RecurringProfile
{
public void ActivateProfile()
{
this.Status = Enums.ProfileStatus.Activated;
//now it should create the first recurring payment
IRecurringProfileNextPaymentCreator creator = NInject.Get<IRecurringProfileNextPaymentCreator>();
creator.CreateNextPayment(this); //this is what I'm having an issue about
}
}
Run Code Online (Sandbox Code Playgroud)
一个样本单元测试:
public void TestActivateProfile()
{
var mockPaymentCreator = new Mock<IRecurringProfileNextPaymentCreator>();
NInject.Bind<IRecurringProfileNextPaymentCreator>(mockPaymentCreator.Object);
RecurringProfile profile = new RecurringProfile();
profile.ActivateProfile();
Assert.That(profile.Status == Enums.ProfileStatus.Activated);
mockPayment.Verify(x => x.CreateNextPayment(), Times.Once());
}
Run Code Online (Sandbox Code Playgroud)
回到示例代码,我的问题是将RecurringProfile作为参数传递给creator.CreateNextPayment()方法是否是一个好习惯,或者RecurringProfile在获取一个实例时是否更有意义地将其传递给DI框架.IRecurringProfileNextPaymentCreator,考虑到IRecurringProfileNextPaymentCreator将始终采取行动IRecurringProfile创建下一笔付款.希望这会使问题更加明确.
由于您没有显示任何代码,我猜测您想做这样的事情
public class RecurringProfile
{
private readonly DateTime _dueDate;
private readonly TimeSpan _interval;
public RecurringProfile(DateTime dueDate, TimeSpan interval)
{
_dueDate = dueDate;
_interval = interval;
}
public bool IsActive { get; private set; }
public DateTime DueDate
{
get { return _dueDate; }
}
public TimeSpan Interval
{
get { return _interval; }
}
public RecurringProfile ActivateProfile()
{
this.IsActive = true;
return new RecurringProfile(this.DueDate + this.Interval, this.Interval);
}
}
Run Code Online (Sandbox Code Playgroud)
这还不够简单吗?
更新
不要滥用 DI 容器作为 ServiceLocator。您将付款创建者注入为 ctor 参数的想法是正确的方法。ServiceLocator 被认为是现代应用程序架构中的反模式。像下面的代码这样的东西应该可以正常工作。
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
var mock = new Mock<INextPaymentCreator>();
DateTime dt = DateTime.Now;
var current = new RecurringProfile(mock.Object, dt, TimeSpan.FromDays(30));
current.ActivateProfile();
mock.Verify(c => c.CreateNextPayment(current), Times.Once());
}
}
public class RecurringProfile
{
private readonly INextPaymentCreator _creator;
private readonly DateTime _dueDate;
private readonly TimeSpan _interval;
public RecurringProfile(INextPaymentCreator creator, DateTime dueDate, TimeSpan interval)
{
_creator = creator;
_dueDate = dueDate;
_interval = interval;
}
public bool IsActive { get; private set; }
public DateTime DueDate
{
get { return _dueDate; }
}
public TimeSpan Interval
{
get { return _interval; }
}
public RecurringProfile ActivateProfile()
{
this.IsActive = true;
var next = this._creator.CreateNextPayment(this);
return next;
}
}
public interface INextPaymentCreator
{
RecurringProfile CreateNextPayment(RecurringProfile current);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
16491 次 |
| 最近记录: |