12

私は SS が大好きですが、ビジネス層の単体テストをしようとして頭を悩ませています。私は単体テストとモッキングが初めてで、NSubstitute を読んでいます。これは楽しいモッキング レイヤーのように見えるからです。

私は大まかに次のようなファイル構造を持っています:

MainAppHostProject*
|
 -AppStart
    -AppHost  <-- standard apphost

DtoProject*
|
 -HelloWorldDto  <-- simple POCO to 


ServiceLayerProject*
|
 -HelloWorldService  <-- service interface that merely passes/sends Dtos to/from business layer


BusinessLayerProject*
|
 -HelloWorldManager <-- logic to construct response and this class extends 'Service' (letting me access Db, session, etc)...sidenote: maybe i shouldve called this a HelloWorldRepository?
 -CustomAuthProvider
 -CustomUserSession


DaoProject*
|
 -HelloWorldDao  <-- POCO of table structure

Apphost は HelloWorldService アセンブリをポイントし、SQL Server データベースを標準として登録します。

すべてが実際にうまく機能し、よりクリーンな方法でロジックを構築することができました。残念ながら、単体テストに着手したいのですが、データベースを分離する方法がわかりません。

メモリ データベースに偽物を登録しようとしましたが、SQL Server と SQLite の方法でコードを使用して ID などを取得する方法に互換性の問題があると思います。

// container.Register<IDbConnectionFactory>(c => new OrmLiteConnectionFactory(":memory:", false, SqliteOrmLiteDialectProvider.Instance));
// container.Register<IDbConnectionFactory>(c => new OrmLiteConnectionFactory(":memory:", false, SqlServerDialect.Provider));

分離して単体テストしたいだけです。アイデアはありますか?

***アップデート

public class UnitTest1
{
    private Container container;

    [TestMethod]
    public void TestMethod1()
    {
        container = new Container();

        // container.Register<IDbConnectionFactory>(new OrmLiteConnectionFactory(":memory:", false, SqliteDialect.Provider));
        // sqlite didnt work so attempting with a real DB for now
        var connectionString = @"Data Source=.\SQLEXPRESS;Initial Catalog=XXX;Integrated Security=True";
        container.Register<IDbConnectionFactory>(c => new OrmLiteConnectionFactory(connectionString, SqlServerDialect.Provider));

        // dependencies are injecting ok 
        container.RegisterAutoWiredAs<FeedbackRepo, IFeedbackRepo>();

        // service is autowiring --> leading to good injections
        container.RegisterAutoWired<FeedbackService>();

        var service = container.Resolve<FeedbackService>();
        service.SetResolver(new BasicResolver(container));

        // unit test is working well  
        var request = new DTO.FeedbackDto { Message = "test" };
        bool result = service.Post(request);
   }
}

現時点では、派生した Service クラスで 'Db' が null になるのをやめようとしています。

4

1 に答える 1

19

ServiceStack サービスを単独で単体テストする場合は、いくつかの異なる方法を使用できます。基本Serviceクラス自体は単純な C# クラスであり、依存関係を手動で、または組み込みの IOC コンテナーを使用して定義および挿入できます。

この単純なサービスをテストするこの単純な単体テストのを使用して、両方のアプローチを説明します。

DTO

public class FindRockstars
{
   public int? Aged { get; set; }
   public bool? Alive { get; set; }
}

public class GetStatus
{
   public string LastName { get; set; }
}

public class RockstarStatus
{
   public int Age { get; set; }
   public bool Alive { get; set; }
}

public class Rockstar
{
   public int Id { get; set; }
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public int? Age { get; set; }
}

実装

public class SimpleService : Service
{
   public IRockstarRepository RockstarRepository { get; set; }

   public List<Rockstar> Get(FindRockstars request)
   {
      return request.Aged.HasValue
          ? Db.Select<Rockstar>(q => q.Age == request.Aged.Value)
          : Db.Select<Rockstar>();
   }

   public RockstarStatus Get(GetStatus request)
   {
      var rockstar = RockstarRepository.GetByLastName(request.LastName);
      if (rockstar == null)
          throw HttpError.NotFound("'{0}' is not a Rockstar".Fmt(request.LastName));

      var status = new RockstarStatus
      {
          Alive = RockstarRepository.IsAlive(request.LastName)
      }.PopulateWith(rockstar); //Populates with matching fields

      return status;
   }
}

このサービスはFindRockstars、サービス クラス自体で直接 db クエリを作成する操作と、GetStatusすべてのデータ アクセスの代わりにリポジトリを使用する操作の 2 つを提供します。

インメモリ データベースの使用

サービス実装内から直接アクセスしている場合は、 ADO.NET IDbConnectionをモックするために多くの労力が必要なためDb、実際の DB を利用したいと思うでしょう。これは、組み込みの IOC を使用して、ServiceStack 自体に依存関係を登録するのと同じ方法で行うことができます。単体テストの場合、AppHost を使用せずに newを使用するだけでこれを行うことができます。ContainerTestFixtureSetup

テスト設定

private ServiceStackHost appHost;

[TestFixtureSetUp]
public void TestFixtureSetUp()
{
    appHost = new BasicAppHost().Init();
    var container = appHost.Container;

    container.Register<IDbConnectionFactory>(
        new OrmLiteConnectionFactory(":memory:", SqliteDialect.Provider));

    container.RegisterAutoWiredAs<RockstarRepository, IRockstarRepository>();

    container.RegisterAutoWired<SimpleService>();

    using (var db = container.Resolve<IDbConnectionFactory>().Open())
    {
        db.DropAndCreateTable<Rockstar>();
        db.InsertAll(SeedData);
    }
}

[TestFixtureTearDown]
public void TestFixtureTearDown()
{
    appHost.Dispose();
}

すべてのセットアップが完了したら、ServiceStack 自体とは独立して、通常の C# クラスと同じようにサービスをテストできます。

[Test]
public void Using_in_memory_database()
{
    //Resolve the autowired service from IOC and set Resolver for the base class
    var service = appHost.Container.Resolve<SimpleService>(); 

    var rockstars = service.Get(new FindRockstars { Aged = 27 });

    rockstars.PrintDump(); //Print a dump of the results to Console

    Assert.That(rockstars.Count, Is.EqualTo(SeedData.Count(x => x.Age == 27)));

    var status = service.Get(new GetStatus { LastName = "Vedder" });
    Assert.That(status.Age, Is.EqualTo(48));
    Assert.That(status.Alive, Is.True);

    status = service.Get(new GetStatus { LastName = "Hendrix" });
    Assert.That(status.Age, Is.EqualTo(27));
    Assert.That(status.Alive, Is.False);

    Assert.Throws<HttpError>(() =>
        service.Get(new GetStatus { LastName = "Unknown" }));
}

依存関係を手動で注入する

単体テストでメモリ内データベースを使用したくない場合は、代わりに依存関係をモックすることを選択できます。この例では、スタンドアロンの Mock を使用しますが、代わりにMoqのようなモック ライブラリを使用することでボイラープレートを減らすことができます。

public class RockstarRepositoryMock : IRockstarRepository
{
    public Rockstar GetByLastName(string lastName)
    {
        return lastName == "Vedder"
            ? new Rockstar(6, "Eddie", "Vedder", 48)
            : null;
    }

    public bool IsAlive(string lastName)
    {
        return lastName == "Grohl" || lastName == "Vedder";
    }
}

[Test]
public void Using_manual_dependency_injection()
{
    var service = new SimpleService
    {
        RockstarRepository = new RockstarRepositoryMock()
    };

    var status = service.Get(new GetStatus { LastName = "Vedder" });
    Assert.That(status.Age, Is.EqualTo(48));
    Assert.That(status.Alive, Is.True);

    Assert.Throws<HttpError>(() =>
        service.Get(new GetStatus { LastName = "Hendrix" }));
}

この例では、すべての依存関係を手動で注入しているため、コンテナーは必要ありません。この例をTesting wikiドキュメントにも追加しました。

于 2013-09-25T09:54:54.937 に答える