当tSQLt faketable不重置表映射时,如何恢复?

时间:2012-08-28 14:08:24

标签: sql-server tdd tsqlt

faketable功能没有重新分配到正常。我现在使用faketable的所有表都包含我在单元测试插入中使用的值的内容。它是很多表,它使我的数据库无用。请帮助解决这个问题或至少是它的原因。这让我非常担心在我们的CI部署过程中使用它,可能更重要的是在我们的本地开发工作中。

5 个答案:

答案 0 :(得分:5)

您的某个测试或代码可能会使事务处于无法回滚的状态。这通常会导致在结果中看到一个或多个带有“错误”(而不是“成功”或“失败”)的测试。

在这些情况下,FakeTable操作不会回滚,并且表格处于伪造状态。

在封面下,FakeTable重命名表并创建它的新副本。重命名发生时,操作将记录在tSQLt.Private_RenamedObjectLog中。

例如,您可以使用以下代码重现tSQLt无法正常回滚的错误:

EXEC tSQLt.NewTestClass 'SOF_Example'
GO

CREATE TABLE SOF_Example.MyTable (i INT);
GO

INSERT INTO SOF_Example.MyTable (i) VALUES (5);
GO

CREATE PROCEDURE SOF_Example.[test fake a table]
AS
BEGIN
    EXEC tSQLt.FakeTable 'SOF_Example.MyTable';

    INSERT INTO SOF_Example.MyTable (i) VALUES (12);

    COMMIT;
END;
GO

EXEC tSQLt.Run 'SOF_Example';

您可以使用此代码查看重命名的表日志:

SELECT OriginalName, SCHEMA_NAME(schema_id) + '.' + name AS [Name of Renamed Table], create_date
FROM tSQLt.Private_RenamedObjectLog
JOIN sys.objects ON ObjectId = object_id;

如果您多次重新执行测试,则每个伪造表的日志中可能有许多条目。您可以使用create_date来帮助确定哪一个包含原始数据。

现在,尽管如此:最好不要在必须保留数据的数据库中编写和执行测试用例。最好的方法是使用不包含用户数据的数据库(最多只包含基本配置数据)。您应该从空白数据库开发和单元测试。填充数据库应该用于其他形式的测试,例如集成,可用性,性能等。

答案 1 :(得分:3)

我遇到了与tSQLt相同的问题,并且能够使用表tSQLt.Private_RenamedObjectLog的内容恢复所有内容

该表由tSQLt框架维护,并证明包含已伪造的原始表的名称,以及临时(即假的)表的SQL ObjectID。使用以下查询生成伪造表的列表,以及它们临时重命名的名称(由tSQLt生成的随机名称,例如 tSQLt_tempobject_3815e077fea84c7c ):

SELECT        
  ObjectId, OriginalName, 
  OBJECT_SCHEMA_NAME(ObjectId) AS SchemaName, 
  OBJECT_NAME(ObjectId) AS TemporaryName
FROM            
  tSQLt.Private_RenamedObjectLog

在SSMS中刷新对象资源管理器显示具有这些随机名称的表确实存在,并且它们确实包含我的原始数据(哇!!)。

然后我做了以下事情:

  1. 尝试了ROLLBACK TRANSACTION,以防将其整理出来。它没有。
  2. 备份数据库(即使它搞砸了)
  3. 丢弃假表(即带有原始名称的表,而不是临时名称)
  4. 将表(带有临时名称)重命名为原始名称,并为每个表使用此名称:

    EXEC sp_rename 'schema.tempname', 'originalname'

  5. 在我知道我的桌子回来后,使用

    清除了表tSQLt.Private_RenamedObjectLog

    DELETE FROM tSQLt.Private_RenamedObjectLog

  6. 创建一个自动生成恢复脚本的过程会很容易!也许tSQLt中已有一个 - 有人知道吗?

答案 2 :(得分:3)

这只能处理将表格放回原位(因为这是OP和我都有的问题)但是使用删除表格的行进行修改可能会使其与其他对象类型一起使用。

DECLARE @cmd nvarchar(MAX) = '';

WITH x AS (
    SELECT TOP 10000 
           PL.Id                                            AS Id
          ,PARSENAME(PL.OriginalName,1)                     AS OriginalName
          ,ISNULL(SO.name,'')                               AS name
          ,QUOTENAME(SCHEMA_NAME(ISNULL(SO.schema_id,1)))   AS SchemaName
          ,ISNULL(SEP.major_id,-1)                          AS major_id
      FROM tSQLt.Private_RenamedObjectLog PL
      LEFT JOIN sys.objects SO
        ON ObjectId = object_id
      LEFT JOIN sys.extended_properties SEP
        ON SEP.major_id = SO.object_id
       AND SEP.name = 'tSQLt.FakeTable_OrgTableName'
     ORDER BY SO.create_date DESC
)
SELECT @cmd = @cmd 
       + CASE WHEN x.name = '' OR OriginalName = x.name 
              THEN N'DELETE tSQLt.Private_RenamedObjectLog WHERE Id = ' + CAST(x.Id AS nvarchar) + N';'
              ELSE N'DROP ' 
                 + N'TABLE'   --Replace this with a CASE statement to deal with other object types
                 + N' ' + SchemaName + '.' + QUOTENAME(x.OriginalName) + '; ' 
                 + NCHAR(13) + NCHAR(10) + N'EXEC sp_rename ''' + SchemaName + N'.' 
                                         + QUOTENAME(x.name) + N''',''' + OriginalName + N''';'
                 + NCHAR(13) + NCHAR(10) + N'IF OBJECT_ID('''+SchemaName + N'.' + QUOTENAME(x.name)+N''') IS NULL'
                 + NCHAR(13) + NCHAR(10) + N'BEGIN'
                 + CASE WHEN x.major_id != -1 
                        THEN NCHAR(13) + NCHAR(10) + N'    EXEC sp_dropextendedproperty ''tSQLt.FakeTable_OrgTableName'',''SCHEMA'',''' 
                           + PARSENAME(SchemaName,1) + N''',''TABLE'',''' + OriginalName + N''';'
                        ELSE ''
                   END
                 + NCHAR(13) + NCHAR(10) + N'    DELETE tSQLt.Private_RenamedObjectLog WHERE Id = ' + CAST(x.Id AS nvarchar) + N';'
                 + NCHAR(13) + NCHAR(10) + N'END'
         END
       + NCHAR(13) + NCHAR(10)
       + NCHAR(13) + NCHAR(10)
  FROM x;

--/*   <-Remove leading dashes to execute
PRINT @cmd;
--*/EXEC (@cmd);

答案 3 :(得分:1)

我知道我已经回答了这个问题!希望这个答案更有用。同样,这个问题是古老的。没有人使用SpyProcedure处理伪造功能或过程。

是的,此问题很容易由在事务外部运行tSQLt测试的代码引起。 tSQLt在事务中运行每个测试,因此所有伪造品等都会在测试结束时回滚。 tSQLt重命名表等以伪造它们,通常会回滚。运行SpyProcedure还会创建一个日志表,当您尝试再次运行SpyProcedure时可能会发生冲突。因此,如果您不在事务中运行测试,则这些更改不会回滚。幸运的是,tSQLt包含还原所有内容所需的所有信息。

首先尝试使用ROLLBACK TRANSACTION,以防万一。如果不是,请检查您的原始表等是否记录在tSQLt的表中,以查找伪造/重命名的内容:

SELECT ObjectId, OriginalName,
  OBJECT_SCHEMA_NAME(ObjectId) AS SchemaName, OBJECT_NAME(ObjectId) AS TemporaryName
FROM tSQLt.Private_RenamedObjectLog

如果返回任何记录,则很有可能可以恢复所有内容。但是请先备份您的数据库!

现在,我们生成SQL代码以删除伪造的对象(在测试原始对象仍然存在之后),并将其重命名为真实名称。据我所知,tSQLt可以伪造tables, views and functions以及使用SpyProcedure的过程,因此这在几乎所有情况下都应适用。 SpyProcedure创建的日志表也将被删除:

DECLARE @SQL_work NVARCHAR(MAX) = '';
DECLARE @template NVARCHAR(MAX) = 
'IF OBJECT_ID(''%s.%s'') IS NOT NULL BEGIN -- check original still exists
  DROP %s %s.%s   -- deleted faked object
  EXEC sp_rename ''%s.%s'', ''%s''
END
';
SELECT
  @SQL_work = @SQL_work + 
    FORMATMESSAGE (CAST (@template AS NVARCHAR (MAX)),
      QUOTENAME(OBJECT_SCHEMA_NAME(r.ObjectId)), OBJECT_NAME(ObjectId),
      CASE WHEN type IN ('U', 'V') THEN 'TABLE'
           WHEN type IN ('FN','FS','TF','IF','FT') THEN 'FUNCTION'
           WHEN type = 'P' THEN 'PROCEDURE'
      END, 
      QUOTENAME(OBJECT_SCHEMA_NAME(r.ObjectId)), OriginalName,
      QUOTENAME(OBJECT_SCHEMA_NAME(r.ObjectId)), OBJECT_NAME(ObjectId),
      PARSENAME (OriginalName, 1)
    ) +
    CASE WHEN type = 'P' 
         THEN FORMATMESSAGE ('DROP TABLE %s.%s_SpyProcedureLog
',              QUOTENAME(OBJECT_SCHEMA_NAME(r.ObjectId)),
                PARSENAME (OriginalName, 1)
              )
         ELSE ''
    END +CHAR(13)+CHAR(10)
FROM
  tSQLt.Private_RenamedObjectLog AS r
JOIN 
  sys.objects AS o ON r.ObjectId = o.object_id
PRINT @SQL_work
PRINT 'DELETE FROM tSQLt.Private_RenamedObjectLog'

上面的代码生成并打印代码以将其放回原处。我建议谨慎执行生成的脚本,即一步一步地执行各个部分,并且仅在执行其他所有操作时才执行最后的DELETE语句。

我很想知道这是否对您有用,或者是否遇到任何问题,以便可以改进代码。

答案 4 :(得分:0)

请注意,每个tSQLt测试都只是一个存储过程。可以使用简单的EXEC执行它们。未能使用tSQLt.Run可能会执行测试过程而不创建事务。这意味着FakeTable等的影响不会在测试完成时回滚。由于SQL Server中的事务操作是tSQLt功能的核心原则,因此应格外小心。

在我们的组织中,我们提供了所有开发人员都希望使用的测试过程模板。模板要做的第一件事是检查代码是否在事务内运行。如果不是,它将中止并显示一条适当的消息。那不会解决所有与交易相关的问题,但可以确保测试不会裸奔。