7

単体テストに適したアプリケーションをどのように設計するかについて問題があります。

私はSRP(Single-Responsibility Principle)を実装しようとしていますが、これには、コードをより整理された状態に保つために、ほとんどの機能を個別の専用クラスに分割することが含まれると理解しました。たとえば、私はこの特定のシナリオを持っています。

RecurringProfileメソッドを持つクラス.ActivateProfile()。この方法では、ステータスをアクティブとしてマークし、次の期日の次の(最初の)定期支払いを作成します。たとえば、別のクラスで次の定期支払いを作成するために、機能を分割するつもりでしたRecurringProfileNextPaymentCreator'RecurringProfile'私の現在のアイデアは、このクラスにコンストラクターのパラメーターとしてをとらせることです。

RecurringProfileNextPaymentCreator(IRecurringProfile profile)

ただし、これは単体テストでは問題になると思います。機能をテストする単体テストを作成したいと思いActivateProfileます。IRecurringProfileNextPaymentCreatorこのメソッドは、依存性注入(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 
    }
}

そして、サンプルの単体テスト:

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を渡すのが良い習慣であるかどうか、またはインスタンスを取得するときに何らかの方法でDIフレームワークにcreator.CreateNextPayment()渡す方が理にかなっているのかどうかです。 、は常に次の支払いを作成するために行動することを考慮して。これにより、質問がもう少し明確になることを願っています。RecurringProfileIRecurringProfileNextPaymentCreatorIRecurringProfileNextPaymentCreatorIRecurringProfile

4

2 に答える 2

6

このような単体テストでは、DI コンテナー (Ninject) を使用しないでください。テスト対象のクラスを新しくするときに、モック オブジェクトを手動で挿入します。次に、呼び出しがモックで行われたことを確認します。

于 2012-10-16T07:22:58.200 に答える
4

コードを表示しなかったので、このようなことをしたいと思います

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);
}
于 2012-10-16T11:53:55.847 に答える