⬆️ ⬇️

Tests and DB. How to roll back the changes made by the test?

Task statement



If unit tests work with the base and change it - what to do to make the results of the run repeatable?

The answer is to clean the base before the dough. But I want to have some kind of data set in the database so that each time it is not recorded there from the tests (we will call such data the “base set”). Thus, we simplify the tests themselves and set them up many times.



Inspection solutions



How can this be implemented? Several options come to mind:



(what other options will you offer?)



Decision making



Having worked for some time with a similar solution based on the “cleanup script”, it was decided to try something new. This is the new version with “backup”.

I note that I like snapshots more, but they are not in MS Sql Express, and I work with him.

The whole approach was developed for the .Net platform and MS Sql server.



Implementation



The first tests revealed the following problems:



')

So, it's time to implement.

It turns out, before the test, in SetUp, scripts will be executed to remove unnecessary connections to the database, and then restore from a backup.



User Disable Script:

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; 




Script for rolling back the database:

 USE master RESTORE DATABASE [FSID_test] FROM DISK = N'c:\BackupPathHere\BackupNameHere.bak' WITH FILE = 2 




Code to call from SetUp test

 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(); } } } 




Now everything looks quite simple, but in the process I had to solve several minor problems that I had not encountered before:





So, having tried all this in practice, we can safely say that the approach is viable and convenient. Features:





UPD:

Two things are suggested in the comments:



These approaches, like the prepositional proposal by me, are not without their shortcomings (the created database must be deleted, the transaction is not always rolled back faster and imposes restrictions on the stored procedures), but you should seriously consider them.

Source: https://habr.com/ru/post/145708/



All Articles