タスクステートメント
単体テストがデータベースで動作し、それを変更する場合-実行の結果が再現可能になるように何をする必要がありますか?
答えは、テストの前にベースをきれいにすることです。 ただし、データベースに何らかの種類のデータセットを保持して、テストからデータが書き込まれないようにします(このようなデータを「基本セット」と呼びます)。 したがって、テスト自体を単純化し、何度も設定します。
決定のレビュー
これはどのように実装できますか? いくつかのオプションが思い浮かびます:
- データベースを完全にクリアし、コードまたはスクリプトから毎回基本セットのデータをデータベースに挿入します。
- 新しいエンティティを削除するクリーニングスクリプト。 しかし、基本セットの本質を変化から何らかの形で分離する必要があります。 また、基本的なテストセットのエンティティを変更する危険性もあります。
- テスト前にデータベースをバックアップにロールバックします。
- 同じですが、バックアップではなくスナップショット。
(他にどのようなオプションを提案しますか?)
決定の選択
「クリーニングスクリプト」に基づいた同様のソリューションでしばらく作業を行った後、何か新しいことを試してみることにしました。 この新しいオプションは「バックアップ」にありました。
私はスナップショットがより好きであることに注意しますが、それらはMS Sql Expressにはありません。
全体のアプローチは、.NetプラットフォームとMS Sqlサーバー用に開発されました。
実装
最初のテストでは、次の問題が明らかになりました。
- 基本的な雇用問題。 RESTOREチームを実行するには、データベースへの排他的アクセスが必要です。 このデータベースへの他のアクティブな接続がある場合、実行は失敗します。
- テストによって接続が閉じられない場合があります。 したがって、これを閉じて監視する必要があります。 または-ロールバックの前に強制的にぶら下がっているアドオンをカバーします。
- .NetにはConnectionPoolがあります。 パフォーマンスを向上させるために閉じた後でも接続を保持します。 解決策はSqlConnection.ClearAllPools()です。
- 誰かがデータベースに接続できます。 たとえば、Managment Studioを使用して、テストのアセンブリを台無しにします。 解決策は、接続を閉じて、これらの数値をテストベースからスローすることです。
- RESTOREチームは、復旧のソースを受け入れる必要があります。 これは通常、MS Sqlサーバー上のファイル名です。 もちろん、DBMS自体にはこの詳細を隠したいと思います。 しかし、うまくいきません。 解決策はSNAPSHOTである可能性があります(スナップショットデータベースの名前のみをコード/スクリプトに組み込む必要がありますが、これは受け入れられます)。 しかし、彼らは特急ではありません。
- バックアップの使用速度。 ロールバックは各テストの前に理想的に行われるため、その速度は非常に重要です。 11 MBの小さなベースが0.216秒で復元されましたが、これは許容範囲です。 このパラメーターの成長特性は、ベースのサイズに応じて、適切に研究されていません。
- 私の経験では、基本セットのサイズは、プロジェクトの発展に伴い大きくなる傾向はありません。
- 基本セットをコンパイルするときは、サイズをメガバイト単位で最小化することを検討する必要があります。
それでは、実装の時間です。
テスト前に、データベースへの不要な接続を削除するためにSetUpスクリプトが実行され、その後バックアップから復元されることが判明しました。
ユーザー切断スクリプト:
DECLARE @twho TABLE( SPID int , ecid int , [STATUS] NVARCHAR(64) , [Loginame] NVARCHAR(64) , [HostName] NVARCHAR(64) , [Blk] int , [DBName] NVARCHAR(64) , cmd NVARCHAR(64), request_id INT) INSERT INTO @twho EXEC SP_WHO DECLARE spid_cursor CURSOR FOR SELECT SPID FROM @twho WHERE DBName = @dbname OPEN spid_cursor DECLARE @SpidToClose INT FETCH NEXT FROM spid_cursor INTO @SpidToClose WHILE @@FETCH_STATUS = 0 BEGIN IF @@SPID <> @SpidToClose BEGIN -- kill . declare @str varchar(32) set @str='KILL ' + cast(@SpidToClose as varchar(16)) exec(@str) END FETCH NEXT FROM spid_cursor INTO @SpidToClose END CLOSE spid_cursor; DEALLOCATE spid_cursor;
データベースをロールバックするスクリプト:
USE master RESTORE DATABASE [FSID_test] FROM DISK = N'c:\BackupPathHere\BackupNameHere.bak' WITH FILE = 2
SetUpテストから呼び出すコード
public static void RevertDb() { // - . var sb = new SqlConnectionStringBuilder(Utilities.ConnectionDb) { ConnectTimeout = 2, ApplicationName = "FSID Tests, clearing" }; using (var con = new SqlConnection(sb.ToString())) { con.Open(); using (var cmd = con.CreateCommand()) { cmd.CommandText = Utilities.CommandKillAllConectionsToDb; cmd.Parameters.AddWithValue("@ dbname", sb.InitialCatalog); // var result = cmd.ExecuteScalar(); } } // - , . . . SqlConnection.ClearAllPools(); using (var con = new SqlConnection(sb.ToString())) { con.Open(); using (var cmd = con.CreateCommand()) { cmd.CommandText = Utilities.CommandRevertTestDb; cmd.ExecuteScalar(); } } }
今ではすべてが非常に単純に見えますが、その過程で、以前に遭遇したいくつかの小さな問題を解決する必要がありました。
- ConnectionPoolは近くにあります。 どんな種類の感染が接続を維持しているのか、長い間探しました。
- KILLパラメーター-KILLをパラメーターで呼び出すことはできません。 EXECを介したバイパスがあります
- SELECT FROM SP()-ストアドプロシージャの結果をクエリする方法を知りませんでした。 読む必要がありましたが、解決策が好きではありません。
したがって、これらすべてを実際に試した結果、このアプローチは実行可能で便利であると安全に言えます。 機能:
- テストによるコード内のダングリング接続の検出。 (強制的にクリーニングしたり、クリーニングにさらに複雑なロジックを追加したりしない場合)
- 基本セットは明示的であり、テストによる変更の影響を受けません。 必要なときに簡単に変更できます。
- 継続的インテグレーションサーバーでのビルド中にDBテストを使用する試みを排除します。
UPD:
コメントには2つのことが提案されています。
- テスト実行ごとに新しいデータベースを作成します-残ったユーザーを切断する必要を回避します。
- バックアップではなくトランザクションをロールバックするために使用します
私が提案したアプローチのようなこれらのアプローチには欠点がないわけではありません(作成されたデータベースを削除する必要があり、トランザクションは常に高速にロールバックされず、ストアドプロシージャに制限を課します)が、真剣に検討する価値があります。