通过在 NUnit、Sql 服务器和 UI 测试中回滚事务来维护数据库已知状态的正确方法
Proper way to maintain database known state by rolling back transactions in NUnit, Sql Server and UI Testing
我正在尝试为 UI 测试自动化执行以下操作:
[SetUp]
public void TestSetUp()
{
_scope = new TransactionScope();
}
[TearDown]
public void TearDown()
{
_scope.Dispose();
}
[Test]
public void SomeTest()
{
Utilities.SomeDeleteTransaction(companyCode);
}
我正在尝试在 [Test] 中执行一个更新查询并使用 UI 执行一些操作并在 [TearDown 中回滚该事务] 在测试后运行。我不确定我是否做对了。但是,我正在尝试(可能提交)该事务,以便我可以看到它对 UI 和 rollback 相同事务的影响,因此我的数据库保持不变。我可以使用 TransactionScope 或其他一些 class 来完成吗?
编辑
这题主要是为了selenium测试处理数据库已知状态。因为,我的数据库每月从生产中关闭,我希望能够在测试前执行一些 insert/update/delete sql 脚本来修改数据库,然后用 Selenium 进行一些 UI 测试,然后回滚在 Teardown 中(测试是使用 NUnit 编写的)以确保数据库对测试没有任何影响并在测试后保持不变。
不知道你是被 C# 代码还是 SQL 代码打动了。在 SQL 中有一种方法可以做到这一点。您开始事务,更新数据并读取未提交的数据,然后回滚。
Begin Tran
Update table1
set col1=val2
where col1=val1
select col1 from table1 WITH (NOLOCK);
rollback
一种方法是使用 https://msdn.microsoft.com/en-us/library/system.transactions.transactionscope.aspx 它会自动回滚所有内容,即使在测试失败时也是如此,因为没有提交语句。
喜欢:
[Test]
public void SomeTest()
{
using (TransactionScope scope = new TransactionScope())
{
// here comes your test
}
}
"better" 和 "safer" 方式是您在问题中通过 TearDown 告诉我们的方式:
[TestFixture]
public class YourFixture
{
private TransactionScope scope;
[SetUp]
public void TestSetUp()
{
scope = new TransactionScope();
}
[TearDown]
public void TearDown()
{
scope.Dispose();
}
[Test]
public void SomeTest()
{
// here comes your test
}
}
为什么?因为 NUnit 是您保证将调用 TearDown。
如果你能保证只有一个线程通过数据库,TearDown 就可以恢复该数据库的已知良好备份。我想对于非常大的数据库,这可能很麻烦。
我使用数据库快照对我的 Web 应用程序进行 Selenium 测试。在 Setup 方法中,我将数据库回滚到刚从生产中恢复数据库后拍摄的快照。这保证数据库在每个测试 运行 中处于相同状态,但仍然允许您测试 UI。我已经创建了几个临时存储过程,例如#usp_restore_snapshot,以避免用刚刚用于测试的 SQL 代码乱扔数据库和单元测试..
我们已通过以下两种方式之一完成:
- Detach/Reattach 拆解原始数据库...听起来很贵,但如果要回滚的东西很多,它可能会更便宜。确实需要一些管道来保留原始数据库文件的副本,并将它们复制等。
- 在设置中启动 DTC 事务并在拆解时处理...如果测试设置相对较轻,则此操作正常。
为了让#1 更快,我们试验了 RAM 磁盘来使磁盘复制速度快如闪电。如果你走那条路,这会产生很大的不同。 DTC 事务范围是执行此操作的最自然方式,但如果您在设置中创建 1000 条记录然后回滚,您的测试可能会变得非常缓慢,这不是一件好事。
attach/detach的一些示例代码:
public void Reset()
{
if (!this.initialized || !this.connectionString.Contains("(local)"))
return;
TestDbManager.CopyNewFiles(this.remoteDatabaseSourceFolder, this.localDatabaseFilesCacheFolder);
this.Detach(this.database);
TestDbManager.CopyNewFiles(this.localDatabaseFilesCacheFolder, this.localSqlServerWorkingFolder);
this.ReAttach(this.database, this.localSqlServerWorkingFolder);
}
因此您必须跟踪 (a) 原始数据库文件和 (b) 实时数据库文件的文件夹。每次分离测试后,从 a 复制到 b,然后附加。
Attach/Detach 用简单的命令完成...
exec sp_attach_db @dbname = '{0}'"
exec sp_detach_db @dbname = '{0}'"
我们有一个小帮手 class 解决所有这些问题,因为我们使用多个数据库来封装它。
拥有非常大或很长的 运行 交易可能会隐藏或产生错误,并可能导致其他不必要的副作用。
如前所述,快照(我投了赞成票:)
在开始时创建一个快照,在结束时返回到它
或者...您能否在数据库更改时创建快照,例如,当您需要时,然后继续恢复。在这种情况下,快照就像一个非常轻量级的备份。
CREATE DATABASE database_snapshot_name
ON
(
NAME = logical_file_name,
FILENAME = 'os_file_name'
) [ ,...n ]
AS SNAPSHOT OF source_database_name
恢复到 SNAPSHOT
USE master;
-- Reverting AdventureWorks to AdventureWorks_dbss1800
RESTORE DATABASE AdventureWorks from
DATABASE_SNAPSHOT = 'AdventureWorks_dbss1800';
GO
均来自 MSDN
数据库快照!
保存此脚本并将其命名为 "create_db_snapshot.sql"
/* Create a database snapshot */
USE master;
CREATE DATABASE Your_Database_Snapshot ON
(
NAME = Your_Database,
FILENAME = 'C:\Snapshots\Your_Database_Snapshot.ss'
)
AS SNAPSHOT OF Your_Database;
GO
此外,保存此脚本并将其命名为"restore_db_from_snapshot.sql"
USE master;
RESTORE DATABASE Your_Database from
DATABASE_SNAPSHOT = 'Your_Database_Snapshot';
GO
示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Common;
using System.IO;
using System.Data.SqlClient;
[SetUp]
public void TestSetUp()
{
string sqlConnectionString = @"server=test.database.com;uid=your_db_username;pwd=your_db_password;database=Your_Database;";
string script = File.ReadAllText(@"~/create_db_snapshot.sql");
SqlConnection conn = new SqlConnection(sqlConnectionString);
Server server = new Server(new ServerConnection(conn));
server.ConnectionContext.ExecuteNonQuery(script);
}
[TearDown]
public void TearDown()
{
string sqlConnectionString = @"server=test.database.com;uid=your_db_username;pwd=your_db_password;database=Your_Database;";
string script = File.ReadAllText(@"~/restore_db_from_snapshot.sql");
SqlConnection conn = new SqlConnection(sqlConnectionString);
Server server = new Server(new ServerConnection(conn));
server.ConnectionContext.ExecuteNonQuery(script);
}
快照文档:https://msdn.microsoft.com/en-us/library/ms175158.aspx
执行 .sql 文件的代码信用:
在执行 restore_db_from_snapshot.sql...
之前,您可能还需要 运行 此脚本
/* Kill all current connections to Your_Database */
use master;
DECLARE @kill varchar(8000) = '';
SELECT @kill = @kill + 'kill ' + CONVERT(varchar(5), spid) + ';'
FROM master..sysprocesses
WHERE dbid = db_id('Your_Database')
我正在尝试为 UI 测试自动化执行以下操作:
[SetUp]
public void TestSetUp()
{
_scope = new TransactionScope();
}
[TearDown]
public void TearDown()
{
_scope.Dispose();
}
[Test]
public void SomeTest()
{
Utilities.SomeDeleteTransaction(companyCode);
}
我正在尝试在 [Test] 中执行一个更新查询并使用 UI 执行一些操作并在 [TearDown 中回滚该事务] 在测试后运行。我不确定我是否做对了。但是,我正在尝试(可能提交)该事务,以便我可以看到它对 UI 和 rollback 相同事务的影响,因此我的数据库保持不变。我可以使用 TransactionScope 或其他一些 class 来完成吗?
编辑
这题主要是为了selenium测试处理数据库已知状态。因为,我的数据库每月从生产中关闭,我希望能够在测试前执行一些 insert/update/delete sql 脚本来修改数据库,然后用 Selenium 进行一些 UI 测试,然后回滚在 Teardown 中(测试是使用 NUnit 编写的)以确保数据库对测试没有任何影响并在测试后保持不变。
不知道你是被 C# 代码还是 SQL 代码打动了。在 SQL 中有一种方法可以做到这一点。您开始事务,更新数据并读取未提交的数据,然后回滚。
Begin Tran
Update table1
set col1=val2
where col1=val1
select col1 from table1 WITH (NOLOCK);
rollback
一种方法是使用 https://msdn.microsoft.com/en-us/library/system.transactions.transactionscope.aspx 它会自动回滚所有内容,即使在测试失败时也是如此,因为没有提交语句。
喜欢:
[Test]
public void SomeTest()
{
using (TransactionScope scope = new TransactionScope())
{
// here comes your test
}
}
"better" 和 "safer" 方式是您在问题中通过 TearDown 告诉我们的方式:
[TestFixture]
public class YourFixture
{
private TransactionScope scope;
[SetUp]
public void TestSetUp()
{
scope = new TransactionScope();
}
[TearDown]
public void TearDown()
{
scope.Dispose();
}
[Test]
public void SomeTest()
{
// here comes your test
}
}
为什么?因为 NUnit 是您保证将调用 TearDown。
如果你能保证只有一个线程通过数据库,TearDown 就可以恢复该数据库的已知良好备份。我想对于非常大的数据库,这可能很麻烦。
我使用数据库快照对我的 Web 应用程序进行 Selenium 测试。在 Setup 方法中,我将数据库回滚到刚从生产中恢复数据库后拍摄的快照。这保证数据库在每个测试 运行 中处于相同状态,但仍然允许您测试 UI。我已经创建了几个临时存储过程,例如#usp_restore_snapshot,以避免用刚刚用于测试的 SQL 代码乱扔数据库和单元测试..
我们已通过以下两种方式之一完成:
- Detach/Reattach 拆解原始数据库...听起来很贵,但如果要回滚的东西很多,它可能会更便宜。确实需要一些管道来保留原始数据库文件的副本,并将它们复制等。
- 在设置中启动 DTC 事务并在拆解时处理...如果测试设置相对较轻,则此操作正常。
为了让#1 更快,我们试验了 RAM 磁盘来使磁盘复制速度快如闪电。如果你走那条路,这会产生很大的不同。 DTC 事务范围是执行此操作的最自然方式,但如果您在设置中创建 1000 条记录然后回滚,您的测试可能会变得非常缓慢,这不是一件好事。
attach/detach的一些示例代码:
public void Reset()
{
if (!this.initialized || !this.connectionString.Contains("(local)"))
return;
TestDbManager.CopyNewFiles(this.remoteDatabaseSourceFolder, this.localDatabaseFilesCacheFolder);
this.Detach(this.database);
TestDbManager.CopyNewFiles(this.localDatabaseFilesCacheFolder, this.localSqlServerWorkingFolder);
this.ReAttach(this.database, this.localSqlServerWorkingFolder);
}
因此您必须跟踪 (a) 原始数据库文件和 (b) 实时数据库文件的文件夹。每次分离测试后,从 a 复制到 b,然后附加。
Attach/Detach 用简单的命令完成...
exec sp_attach_db @dbname = '{0}'"
exec sp_detach_db @dbname = '{0}'"
我们有一个小帮手 class 解决所有这些问题,因为我们使用多个数据库来封装它。
拥有非常大或很长的 运行 交易可能会隐藏或产生错误,并可能导致其他不必要的副作用。
如前所述,快照(我投了赞成票:)
在开始时创建一个快照,在结束时返回到它
或者...您能否在数据库更改时创建快照,例如,当您需要时,然后继续恢复。在这种情况下,快照就像一个非常轻量级的备份。
CREATE DATABASE database_snapshot_name
ON
(
NAME = logical_file_name,
FILENAME = 'os_file_name'
) [ ,...n ]
AS SNAPSHOT OF source_database_name
恢复到 SNAPSHOT
USE master;
-- Reverting AdventureWorks to AdventureWorks_dbss1800
RESTORE DATABASE AdventureWorks from
DATABASE_SNAPSHOT = 'AdventureWorks_dbss1800';
GO
均来自 MSDN
数据库快照!
保存此脚本并将其命名为 "create_db_snapshot.sql"
/* Create a database snapshot */
USE master;
CREATE DATABASE Your_Database_Snapshot ON
(
NAME = Your_Database,
FILENAME = 'C:\Snapshots\Your_Database_Snapshot.ss'
)
AS SNAPSHOT OF Your_Database;
GO
此外,保存此脚本并将其命名为"restore_db_from_snapshot.sql"
USE master;
RESTORE DATABASE Your_Database from
DATABASE_SNAPSHOT = 'Your_Database_Snapshot';
GO
示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Common;
using System.IO;
using System.Data.SqlClient;
[SetUp]
public void TestSetUp()
{
string sqlConnectionString = @"server=test.database.com;uid=your_db_username;pwd=your_db_password;database=Your_Database;";
string script = File.ReadAllText(@"~/create_db_snapshot.sql");
SqlConnection conn = new SqlConnection(sqlConnectionString);
Server server = new Server(new ServerConnection(conn));
server.ConnectionContext.ExecuteNonQuery(script);
}
[TearDown]
public void TearDown()
{
string sqlConnectionString = @"server=test.database.com;uid=your_db_username;pwd=your_db_password;database=Your_Database;";
string script = File.ReadAllText(@"~/restore_db_from_snapshot.sql");
SqlConnection conn = new SqlConnection(sqlConnectionString);
Server server = new Server(new ServerConnection(conn));
server.ConnectionContext.ExecuteNonQuery(script);
}
快照文档:https://msdn.microsoft.com/en-us/library/ms175158.aspx
执行 .sql 文件的代码信用:
在执行 restore_db_from_snapshot.sql...
之前,您可能还需要 运行 此脚本/* Kill all current connections to Your_Database */
use master;
DECLARE @kill varchar(8000) = '';
SELECT @kill = @kill + 'kill ' + CONVERT(varchar(5), spid) + ';'
FROM master..sysprocesses
WHERE dbid = db_id('Your_Database')