SQL Server Data Tools (SSDT)のユニットテスト機能では、テスト実行前にソリューション内のデータベースプロジェクトをテスト実行対象のデータベースに自動でデプロイしてくれる機能があります。
これは非常に便利な機能です。しかし、複数のデータベースにまたがるような開発していると、依存関係があるなどの理由で複数のデータベースプロジェクトの内容をデプロイしたいことが出てきたりします。
SSDTの設定を調べて見ましたがそういった機能は用意されていなさそうで、一旦は諦めていました。が、別件で思案してたところでふと思いついたことを試してみた……ら、なんとか実現できなのでメモしておきます。
SSDTがユニットテスト実行前にデプロイしている仕組み
まず、SSDTがユニットテスト実行前にどうやってデプロイしているかを見てみます。
SSDTで作成されるテストプロジェクトでは、テストが実行されるとまずSqlDatabaseSetupクラス
のInitializeAssemblyメソッド
が呼ばれます。 このメソッドはMsTest(Microsoft.VisualStudio.QualityTools.UnitTestFrameworkアセンブリ
)のAssemblyInitialize属性
がついた属性です。
この中で実行されているの処理は二つ。
そのうちのSqlDatabaseTestClass.TestService.DeployDatabaseProject();
がデプロイ処理であることは名前からして疑う余地はなさそうです。
この処理が実装されている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 テスト構成」のダイアログで設定する内容です。
複数のDB・プロジェクトをデプロイする方法(強引)
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