Использование автоматической настройки localdb для модульного тестирования; как очистить?

Я настроил файл .mdf вместе со строкой соединения localdb для использования в модульных тестах, например:

<connectionStrings>
        <add name="TestData" providerName="System.Data.SqlClient" connectionString="Data Source=(localdb)\v11.0; AttachDBFilename='|DataDirectory|\TestData.mdf'; Integrated Security=True"/>
</connectionStrings>

Как только я правильно настроил файлы для развертывания для своего теста, это прекрасно работает: копия .mdf привязана к экземпляру по умолчанию LocalDB, а SqlClient подключается к нему без изменения конфигурации. Он просто работает.

Но как мне очистить потом? В моем локальном ящике я могу периодически использовать SSMS для ручного отсоединения старых тестовых баз данных, но на сервере CI было бы предпочтительно, чтобы unit test очистился.

Существует ли аналогичный способ автоматического создания базы данных localdb для отсоединения от экземпляра?

Ответы

Ответ 1

Вот как я удаляю базу данных localDB. Мне не нравится то, что .mdf также удаляется. Я преодолеваю это, скопировав его в каталог tem сначала и используя копию для создания db.

var sc = new Microsoft.SqlServer.Management.Common.ServerConnection(your localDB SqlConnection here);
var server = new Microsoft.SqlServer.Management.Smo.Server(sc);
server.KillDatabase(dbName here);

Надеюсь, что это поможет Фил

Ответ 2

Если вы говорите об модульных тестах, например, с xUnit, есть атрибуты, которые вы можете добавить к самим тестам, чтобы выполнить откат на любом, касающемся базы данных.

Фактически, он запускает тест в транзакции уровня предприятия. Это оставляет данные стабильными для каждого теста, и вам не нужно будет издеваться над db; он может стать эффективным приемочным тестом.

Например (в xUnit):

[Fact]
[AutoRollback]
public void Should_Insert_Valid_Data()
{
    // Arrange: presume there an object, sut, that supports insert,
    // and you have some data

    // Act:
    int key = sut.Insert(data);

    // Assert:
    Assert.True(key > 0, "Expected a primary key to be returned");
    var inserted = sut.Get(key);
    Assert.Equal<DataClassName>(data, inserted, CompareAllButKey);
}

(fyi: "сут" - это "ситуация под тестом" или "субъект под тестом" )

Возможно, было бы полезно ознакомиться с этой статьей " Стратегии изоляции базы данных в тестах". В конце этой статьи есть и другие интересные ссылки.

Кстати, я использовал xUnit в качестве образца, но другие структуры тестирования имеют аналогичные атрибуты для управления откатами.

Ответ 3

Будет ли используемая вами инфраструктура unit test позволять вам прикрепить метод очистки (т.е. AssemblyAttribute в MS Test, OneTimeTearDown в NUnit 3, общий контекст в XUnit)? Если это так, вы можете подключить что-то вроде ниже, чтобы автоматически отсоединить базу данных в конце тестирования.

public static void CleanUp()
{
    using (var connection = new SqlConnection(ConnectionString))
    {
        connection.Open();

        using (var command = connection.CreateCommand())
        {
            var sql = "DECLARE @dbName NVARCHAR(260) = QUOTENAME(DB_NAME());\n" +
                "EXEC('exec sp_detach_db ' + @dbName + ';');";
            command.CommandText = sql;
            command.ExecuteNonQuery();
         }
    }
}

Ответ 4

Да, но вам это может не понадобиться.

В настоящее время я использую процесс, в котором я прикрепляю прикрепленное db, как wbx911/Tim Post имеет это в своем ответе. Но вам не нужно отвлекать их, если вы прикрепляете копию исходного db (например, копию, созданную сборкой проекта/решения). Если соединение к db не подключено, оно будет превзойдено в следующем тестовом прогоне.

Если у вас есть много копий базы данных (ов) или просто не очищено все, что прикреплено, вы можете использовать хранимую процедуру, такую ​​как следующее, прикрепленную к базе данных master экземпляра localDb ( пример того, что я лично использую):

CREATE PROCEDURE [dbo].[DettachTestDatabases]
AS

BEGIN
DECLARE  @imax INT, 
         @i    INT 
DECLARE  @Name VARCHAR(255)

DECLARE  @DbsToDrop TABLE(RowID INT IDENTITY ( 1 , 1 ), 
                          name VARCHAR(255)
                         ) 

INSERT @DbsToDrop
SELECT [name]
  FROM [master].[sys].[databases]
  WHERE 
    --Remove all local dbs with *SomeText*.mdf or SomeOtherTextin name
    (name like '%SomeText%.mdf' or name like '%SomeOtherText%')
    -- Exclude VS test dbs (add more as required...)
    and (name not like '%TESTS.%');

SET @imax = @@ROWCOUNT 
SET @i = 1 

WHILE (@i <= @imax) 
  BEGIN 
        SELECT @Name = name 
        FROM   @DbsToDrop 
        WHERE  RowID = @i

        EXEC master.dbo.sp_detach_db @dbname = @Name;

        PRINT 'Dettatched ' + @Name;

        SET @i = @i + 1;
  END   --while

END --sp
GO

Я использую соглашение об именах, чтобы определить, какие dbs следует отбрасывать (поэтому похожие утверждения). Visual studio прикрепляет пару db (или, по крайней мере, я думаю, они принадлежат VS) для управления результатами тестов, которые я не хочу отсоединять (поэтому условие "не похоже" ). ther - master.dbo.sp_detach_db, который выделяется по имени, поэтому вам действительно нужно только ссылаться на это имя, но поскольку имена localdb могут быть пустыми, поиск будет полезен.

Я не автоматизировал это еще в конце своих тестов, потому что я не чувствовал необходимости (поскольку перезаписывает работу нормально), но я могу выполнить его в SSMS в любое время, когда захочу или нужно. Но если вы чувствуете необходимость, вам просто нужно будет управлять вызовом SP из вашего очищающего кода.

Прикрепление dbs - несколько дорогой процесс. Я выбрал метод очистки /SP, который удаляет любые переменные данные и обновляет таблицы, чтобы поместить db в согласованное состояние, что довольно быстро, вместо повторной привязки базы данных.

Если вы найдете какие-то улучшения, я бы очень хотел узнать о них!

Ответ 5

Вот так:

 // ProjectPath/bin/Debug/dbfile.mdf

 [TestInitialize]
 public void SetUp()
 {           
    AppDomain.CurrentDomain.SetData(
      "DataDirectory", Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ""));
  }