久しぶりに C# での開発現場に戻ってからテストコードを書く時にやっていること - EF Core で自動生成 -

dany1468.hatenablog.com

上記の記事の続きです。

SQL を書かずにテストコードを書いていきたい

これは環境によると思いますが、私が担当したアプリケーションはメインのプロダクトからは切り離されていて、そちらで定義されているであろう Entity や Repository を利用できないという状態でした。

もちろん上記を是正していくのが本来の筋ではありますが、そんな事がさっと聞き入れられれば苦労はしないというのがよくある話で、こちらも例外ではありませんでした。

かといって、テスト用のデータを作るために何十とあるテーブルの INSERT や UPDATE を書くのは長らく Rails で開発していた人間としては指の筋力が足りません。。それにそれを読むための眼の筋肉も。

Entity Framework Core を使う

Entity Framework を使えば、RailsActiveRecord のように Model class を主にしてデータ投入等ができるので、記述量や読みやすさが向上します。速度が重視される現場であれば、「Dapper でしょ」みたいな圧を受けるかもしれませんが、テストコードであれば許容できると思います。(もちろん、Dapper を使っていても Repository 等で wrap されているので、それを使えればテストデータの作成に困ることはないと思うので、私の環境は特殊だとは思いますが)

www.npgsql.org

私の担当したアプリケーションは Npgsql を利用していたため、上記の記事を見つけました。既存の RDB から Model class を生成できるというものです。(もちろん Npgsql に限った話しではありません。)

私は、この EF Core で作成した Model class のみを保持する project を作成し、それをテスト用の project で参照し、前の記事で触れた Bogus と組み合わせて Factory から生成するようにしています。

Scaffold で自動生成

まずは必要なパッケージを追加して以下のように csproj を整えます。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.3" />
    <PackageReference Include="Npgsql" Version="4.0.2" />
    <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.1.1.1" />
  </ItemGroup>

</Project>

.NET CLI に Entity Framework 用のコマンドが使えるようになっているので、以下のようにして実行します。
参考: dotnet ef dbcontext スキャフォールディング - Microsoft Docs

% dotnet ef dbcontext scaffold "Host=***;Database=***;Username=***;Password=***;Port=***;" Npgsql.EntityFrameworkCore.PostgreSQL --context 名前空間

これで Model class が生成されます。

Scaffold された Model から migration を行う

本来の使い方とは異なりますが、Scaffold で生成された Model class から migration を作成し、それを使って CI 環境用の RDB 環境を作ることに利用できます。

% dotnet ef migrations add InitialCreate --context MigrationContext

この場合、 scaffold と migration の向き先が異なるため以下のように migration 用に以下のように DbContext を定義して利用しています。

namespace Sample
{
    public class MigrationContext : SampleDataContext // scaffold 時に指定したもの
    {
        public MigrationContext() : base(Environment.GetEnvironmentVariable("MIGRATION_CONNECTION"))
        {
        }
    }
}

この場合は以下のように migration を実行します。

% MIGRATION_CONNECTION="Host=***;Database=***;Username=***;Password=***;Port=****;" dotnet ef migrations add InitialCreate --context MigrationContext

以降は、必要なタイミングで scaffold と migration を実行し、テストを健全に保っていきます。

が、まだ私はこの辺りを完全には自動化できておらず微妙な感じにはなっています。