【问题标题】:Using auto-configured localdb for unit testing; how do I clean up?使用自动配置的 localdb 进行单元测试;我该如何清理?
【发布时间】:2012-09-05 02:04:19
【问题描述】:

我已经配置了一个 .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 服务器上,显然最好让单元测试自行清理。

是否有类似的自动方法使 localdb 数据库从实例中分离出来?

【问题讨论】:

  • 为什么不模拟你的依赖并创建一个真正的单元测试呢?就目前而言,您所拥有的是集成测试。
  • @DanielMann 无可否认,但它的接近程度可以接受。我只是没有时间为数据库构建一个真正的模拟。
  • 有没有人为此想出一个可靠的解决方案?我想使用 localdb 进行集成测试,但事实证明,基于 localDB 可以附加数据库但不能分离它的愚蠢方式,这非常困难。
  • 在不投入大量时间的情况下,使用某些技术(例如实体框架)进行模拟的价值是有限的。

标签: .net sql-server localdb


【解决方案1】:

这就是我删除 localDB 数据库的方式。我不喜欢的是 .mdf 也被删除了。我通过首先将其复制到 tem 目录并使用副本创建数据库来克服这一点。

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

希望这会有所帮助 菲尔

【讨论】:

  • 不,那太好了。我将 .mdf 设置为部署文件,因此每次测试都会将其复制为新的,因此删除 mdf 就可以了。我试试看。
  • @BenCollins 这对你有用吗?自动时如何获取数据库名称?
  • @SimonHartcher 不。获取生成的数据库名称对我来说也是一个阻塞问题。从那以后,我或多或少地放弃了整个机制,转而支持正确模拟的存储库接口,以避免依赖于正在运行的数据库。
  • @BenCollins 我发现您可以从SqlConnection 获取数据库名称,但它并没有真正为我工作。我想使用存储库模式,但由于无法使用的原因。
【解决方案2】:

是的,但您可能不需要它。

我目前正在使用一个过程,我按照 wbx911/Tim Post 在他们的答案中的方式附加附加数据库。但是,如果您要附加原始数据库的副本(例如由项目/解决方案构建创建的副本),则不需要分离它们。如果没有连接到数据库,它将在下一次测试运行时被覆盖。

如果您有很多附加的数据库副本,或者只是不想清理附加的所有内容,那么您可以使用如下所示的存储过程,附加到 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(因此使用 like 语句)。 Visual Studio 附加了几个 db(或者至少我认为它们属于 VS)来管理我不想分离的测试结果(因此是“不喜欢”条件)。有一个 master.dbo.sp_detach_db 按名称分离,因此您实际上只需要引用名称,但由于 localdb 名称可能有点狂野,因此查找将很有用。

在我的测试结束时我还没有自动化这个,因为我觉得没有必要(因为覆盖工作正常),但我可以在我想要或需要的任何时候在 SSMS 中执行它。但是,如果您觉得有必要,您只需要从您的清理代码中管理对 SP 的调用。

附加数据库有点昂贵的过程。我选择了一种清理方法/SP,它删除任何可变数据并重新设置表以使数据库处于一致状态,这非常快,而不是重新附加数据库。

如果您发现任何改进点,我真的很想知道它们!

【讨论】:

    【解决方案3】:

    您使用的单元测试框架是否允许您附加清理方法(即 MS 测试中的 AssemblyAttribute、NUnit 3 中的 OneTimeTearDown、XUnit 中的 shared context)?如果是这样,您可以像下面这样连接,以便在测试结束时自动分离数据库。

    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】:

      如果您谈论的是单元测试,例如 xUnit,您可以将一些属性添加到测试本身,以对任何涉及数据库的内容进行回滚。

      实际上,它在企业级事务中运行测试。这使每个测试的数据保持稳定,您不需要模拟数据库;它可以成为有效的验收测试。

      例如(在 xUnit 中):

      [Fact]
      [AutoRollback]
      public void Should_Insert_Valid_Data()
      {
          // Arrange: presume there's 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);
      }
      

      (供参考:“sut”是“被测情况”或“被测对象”)

      在“Strategies for Isolating the Database in Tests”上查看这篇文章可能会有所帮助。那篇文章的末尾还有一些其他不错的链接。

      顺便说一句,我使用 xUnit 作为示例,但其他测试框架具有类似的属性来控制回滚。

      【讨论】:

        【解决方案5】:

        像这样:

         // ProjectPath/bin/Debug/dbfile.mdf
        
         [TestInitialize]
         public void SetUp()
         {           
            AppDomain.CurrentDomain.SetData(
              "DataDirectory", Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ""));
          }
        

        【讨论】:

        • 这并不能以任何方式回答问题。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-01-10
        • 2016-09-18
        • 1970-01-01
        • 1970-01-01
        • 2013-08-29
        相关资源
        最近更新 更多