faketable 函数没有重新分配为正常。我使用 faketable 的所有表现在都包含我在插入单元测试时使用的值的内容。它有很多表,它使我的数据库毫无用处。请帮助解决此问题或至少解决其原因。这让我对在我们的 CI 部署过程中使用它感到非常紧张,也许更重要的是在我们的本地开发工作中。
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 来帮助确定哪一个包含原始数据。
现在,说了这么多:最好不要在必须保存数据的数据库中编写和执行测试用例。最好的方法是使用不包含用户数据(最多只包含基本配置数据)的数据库。您应该在空白数据库中进行开发和单元测试。填充的数据库应该用于其他形式的测试,例如集成、可用性、性能等。
这仅处理将表放回原位(因为这是我和 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);
我对 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 中的对象资源管理器表明确实存在具有这些随机名称的表,并且它们确实包含我的原始数据(哇!!)。
然后我做了以下事情:
- 试了一个
ROLLBACK TRANSACTION
以防万一。它没有。 - 备份数据库(即使它搞砸了)
- 删除了假表(即具有原始名称的表,而不是临时名称)
将表(使用临时名称)重命名为原始名称,对每个表使用以下名称:
EXEC sp_rename 'schema.tempname', 'originalname'
在我知道我的表回来后清除了表 tSQLt.Private_RenamedObjectLog ,使用
DELETE FROM tSQLt.Private_RenamedObjectLog
制作一个自动生成恢复脚本的程序很容易!也许 tSQLt 中已经有一个 - 有人知道吗?
我知道我以前回答过这个问题!希望这个答案更有用。而且这个问题很老了。没有其他人使用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 可以使用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
时才执行最后一条语句。
我很想知道这是否对您有用,或者是否遇到任何问题以便改进代码。
请注意,每个 tSQLt 测试都只是一个存储过程。它们可以通过简单的 EXEC 执行。未能使用 tSQLt.Run 可能会在不创建事务的情况下执行测试过程。这意味着 FakeTable 等的效果不会在测试完成时回滚。由于 SQL Server 中的事务操作是 tSQLt 功能的核心原则,因此应注意一些。
在我们的组织中,我们提供了一个所有开发人员都应该使用的测试程序模板。模板做的第一件事是检查代码是否在事务中运行。如果不是,它会以适当的消息中止。这不会解决所有与事务相关的问题,但可以确保测试不会裸运行。