きよくらの備忘録

「三日坊主と呼ばせない!日記」改め。主にソフトウェア開発関連の話題。

SQL Server Data Toolsのユニットテスト実行前に複数DBにデプロイする

SQL Server Data Tools (SSDT)のユニットテスト機能では、テスト実行前にソリューション内のデータベースプロジェクトをテスト実行対象のデータベースに自動でデプロイしてくれる機能があります。

これは非常に便利な機能です。しかし、複数のデータベースにまたがるような開発していると、依存関係があるなどの理由で複数のデータベースプロジェクトの内容をデプロイしたいことが出てきたりします。

SSDTの設定を調べて見ましたがそういった機能は用意されていなさそうで、一旦は諦めていました。が、別件で思案してたところでふと思いついたことを試してみた……ら、なんとか実現できなのでメモしておきます。

 

SSDTがユニットテスト実行前にデプロイしている仕組み

まず、SSDTがユニットテスト実行前にどうやってデプロイしているかを見てみます。

SSDTで作成されるテストプロジェクトでは、テストが実行されるとまずSqlDatabaseSetupクラスInitializeAssemblyメソッドが呼ばれます。 このメソッドはMsTest(Microsoft.VisualStudio.QualityTools.UnitTestFrameworkアセンブリ)のAssemblyInitialize属性がついた属性です。

この中で実行されているの処理は二つ。 そのうちのSqlDatabaseTestClass.TestService.DeployDatabaseProject();がデプロイ処理であることは名前からして疑う余地はなさそうです。

f:id:kiyokura:20161213232318p:plain

この処理が実装されているMicrosoft.Data.Tools.Schema.Sql.UnitTestingアセンブリのソースは公開されていませんので具体的にどのような処理をしているのかはわかりませんが、MSDNには一応載っていました。

SqlDatabaseTestService.DeployDatabaseProject Method

この DeployDatabaseProjectメソッドは、上記のMSDN

Deploys the database project by using the settings of the user in the app.config file.

と記載されている通り、app.confgのSqlUnitTestingセクションに記載された設定に従ってDBをデプロイします。この記載内容は、テストプロジェクト作成時や「SQL Server テスト構成」のダイアログで設定する内容です。

f:id:kiyokura:20161213232418p:plain

 

複数のDB・プロジェクトをデプロイする方法(強引)

追記:もっと適切だと思う方法がありましたので、ぜひそちらを参照してください kiyokura.hateblo.jp

SSDTが生成するユニットテストプロジェクトで自動デプロイが行われている仕組みをもう一度整理すると、こういうことです。

  • AssemblyInitialize属性の追加メソッド内でDeployDatabaseProjectメソッドでデプロイが実施される
  • DeployDatabaseProjectメソッド`はapp.configの設定に従って処理を行う

 

……ここまでくればピンと来たかもおられるでしょう。 そうです。

app.configを書き換えて DeployDatabaseProjectを呼ぶ

ようにすればいくらでもデプロイできそうじゃありませんか(

 

……ということで、例えば以下のようにapp.configを書き換えて実行してやることで、複数のデータベース/プロジェクトをすんなりデプロイすることができました。

using System;
using System.Configuration;
using Microsoft.Data.Tools.Schema.Sql.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Xml;

namespace BletillaTestsNext
{
  [TestClass()]
  public class SqlDatabaseSetup
  {
    // ターゲットデータベース
    const string TARGET_DB_PROJECT = "..\\..\\..\\ MainDb\\MainDb.sqlproj";
    const string TARGET_DB_CONNECTION_STRING = "<デプロイ先のDBの接続文字列>";
    
    // 依存データベース
    const string DEPEND_DB_PROJECT = "..\\..\\..\\SubDb\\SubDb.sqlproj";
    const string DEPEND_DB_CONNECTION_STRING = "<デプロイ先のDBの接続文字列> ";

    [AssemblyInitialize()]
    public static void InitializeAssembly(TestContext ctx)
    {
      // テスト実行ターゲットをデプロイ
      RewriteConfig(TARGET_DB_PROJECT, TARGET_DB_CONNECTION_STRING);
      SqlDatabaseTestClass.TestService.DeployDatabaseProject();
 
      // 依存DBをデプロイ
      RewriteConfig(DEPEND_DB_PROJECT, DEPEND_DB_CONNECTION_STRING);
      SqlDatabaseTestClass.TestService.DeployDatabaseProject();
 
      // テスト実行用に元に戻しておく
      RewriteConfig(TARGET_DB_PROJECT, TARGET_DB_CONNECTION_STRING);


      SqlDatabaseTestClass.TestService.GenerateData();
    }
    
    private static void RewriteConfig(string projectFile, string connectionString)
    {
      // デプロイは特権コンテキスト(PrivilegedContext)で行われるのでそちらだけ書き換えればよい
      var xmlDoc = new XmlDocument();
      xmlDoc.Load(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
      xmlDoc.SelectSingleNode("//SqlUnitTesting/DatabaseDeployment").Attributes["DatabaseProjectFileName"].Value = projectFile;
      xmlDoc.SelectSingleNode("//SqlUnitTesting/PrivilegedContext").Attributes["ConnectionString"].Value = connectionString;
      xmlDoc.Save(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
      ConfigurationManager.RefreshSection("SqlUnitTesting");
    }
  }
}

見てのとおり、app.configを読み込んで、

  • SqlUnitTesting/DatabaseDeploymentノードDatabaseProjectFileName属性に対象のプロジェクトファイルを
  • SqlUnitTesting/PrivilegedContextノードConnectionString属性にでデプロイ先のプロジェクトファイルを

書き込んで反映、念のためリフレッシュしています。

なお、SqlUnitTestingセクションで接続文字列を宣言してる箇所としてPrivilegedContextとExecutionContextの二つがありますが、この最初のでデプロイで利用されるのはPrivilegedContextですのでそこは間違えないようにする必要があります。 またプロジェクトファイルのパスは、このテストプロジェクトのアセンブリ出力パス(bin\Debug)からの相対パスで指定しています。

そのほかこの例ではプロジェクトのパスや接続文字列を直書きしてますが、実際にはapp.configにAppSettingsセクションを追加したりして管理するといいかもしれません。

 

まとめ

多少強引感もありますが、割とすんなり動いてる感があります。というか標準で複数デプロイ先へのデプロイに標準で対応してくれればもっと嬉しいのですが:p