[英]unit testing & constructor dependency injection
關於如何設計適合單元測試的應用程序,我遇到了一個問題。
我正在嘗試實施SRP(單一責任原則),據我所知,這涉及在單獨的專用類中拆分大多數功能以保持代碼更有條理。 例如,我有這個特定的場景。
一個RecurringProfile
類,它有一個方法.ActivateProfile()
。 此方法的作用是將狀態標記為已激活,並為下一個截止日期創建下一個(第一個)定期付款。 我打算拆分功能,在單獨的類中創建下一個定期付款,例如RecurringProfileNextPaymentCreator
。 我的想法是讓這個類將'RecurringProfile'
作為其構造函數中的參數:
RecurringProfileNextPaymentCreator(IRecurringProfile profile)
但是,我認為這對於單元測試來說會有問題。 我想創建一個測試ActivateProfile
功能的單元測試。 此方法將通過依賴注入(Ninject)獲取IRecurringProfileNextPaymentCreator
的實例,並調用方法.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
}
}
一個樣本單元測試:
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());
}
回到示例代碼,我的問題是將RecurringProfile作為參數傳遞給creator.CreateNextPayment()
方法是否是一個好習慣,或者以某種方式將RecurringProfile
傳遞給DI框架更有意義,在獲取IRecurringProfileNextPaymentCreator
的實例時,考慮到IRecurringProfileNextPaymentCreator
將始終作用於IRecurringProfile
以創建下一個付款。 希望這會使問題更加明確。
在此類單元測試期間,您不應使用DI容器(Ninject)。 在新建測試類時,您將手動注入模擬對象。 然后驗證是否在模擬上進行了調用。
因為你沒有顯示任何代碼,我猜你想要做這樣的事情
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);
}
}
這不夠簡單嗎?
更新
不要濫用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);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.