通过回滚NUnit、SqlServer和UI测试中的事务来维护数据库已知状态的正确方法



我正在尝试为UI测试自动化做以下工作:

[SetUp]
public void TestSetUp()
{
    _scope = new TransactionScope();
}
[TearDown]
public void TearDown()
{
    _scope.Dispose();
}
[Test]
public void SomeTest()
{
    Utilities.SomeDeleteTransaction(companyCode);    
}

我正试图在[Test]中执行一个更新查询,并对UI做一些操作,并在测试后运行的[TearDown]滚同一事务,以便我的DB保持不变。我可以使用TransactionScope或其他类来实现这一点吗?

编辑

这个问题主要是为了处理硒测试的数据库已知状态。由于我的数据库每月都会从生产中删除,我希望能够在测试前执行一些insert/update/delete sql脚本来修改数据库,然后用Selenium进行一些UI测试,然后在Teardown中回滚(测试是用NUnit编写的),以确保数据库不会对测试产生任何影响,并且在测试后保持不变。

数据库快照!

保存此脚本并将其命名为"create_db_snapshot.sql"

/* Create a database snapshot */
USE master;
CREATE DATABASE Your_Database_Snapshot ON
( 
    NAME = Your_Database, 
    FILENAME = 'C:SnapshotsYour_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文件的代码信用:https://stackoverflow.com/a/1728859/3038677

在执行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')

我使用数据库快照对web应用程序进行Selenium测试。在Setup方法中,我将数据库回滚到刚从生产中恢复数据库后拍摄的快照。这保证了数据库在每次测试运行时都处于相同的状态,但仍然允许您测试UI。我创建了几个临时存储过程,例如#usp_store_snapshot,以避免将数据库和单元测试与刚才用于测试的SQL代码一起丢弃。。

一种方法是使用https://msdn.microsoft.com/en-us/library/system.transactions.transactionscope.aspx它将自动回滚所有内容,即使在测试失败时也是如此,因为没有提交语句。

类似:

[Test]
public void SomeTest() 
{
    using (TransactionScope scope = new TransactionScope())
    {
        // here comes your test
    }
}

一种"更好"one_answers"更安全"的方式是你在问题中告诉你的通过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是你的garanty,所以TearDown会被调用。

如果您可以保证数据库中只有一个线程,那么TearDown就可以恢复该数据库的已知良好备份。我想,对于非常大的数据库来说,这可能会很麻烦。

我们采用了两种方法之一:

  1. 拆卸时分离/重新附加原始数据库。。。这听起来确实很贵,但如果有很多东西可以回滚,它可能会更便宜。确实需要一些管道来保存原始数据库文件的副本,并四处复制,等等
  2. 在安装程序中启动DTC事务并在拆解时处理。。。如果测试设置相对较轻,则可以正常工作

为了让#1更快,我们用RAM磁盘进行了实验,使磁盘拷贝速度闪电般快。如果你走那条路,这会产生巨大的不同。DTC事务范围是最自然的方法,但如果你在设置中创建1000条记录,然后回滚,你的测试可能会变得很慢,这不是一件好事。

附加/分离的一些示例代码:

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,然后附加。

附加/分离是用简单的命令完成的。。。

exec sp_attach_db @dbname = '{0}'"
exec sp_detach_db @dbname = '{0}'"

由于我们使用多个数据库来封装所有这些,因此我们有一个小的助手类。

拥有非常大或长时间运行的事务可能会隐藏或创建错误,并可能导致其他不必要的副作用。

如前所述,SNAPSHOT(我投了赞成票:)

在开始时创建快照,在结束时返回

或者。。。你能在数据库发生变化时创建一个快照吗?例如,当你需要的时候,然后继续恢复。在这种情况下,快照就像一个非常轻量级的备份。

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

https://msdn.microsoft.com/en-us/library/ms189281.aspx

https://msdn.microsoft.com/en-us/library/ms175876.aspx

我不知道你是被C#代码还是SQL代码打动了。有一种方法可以在SQL中实现。开始事务,更新数据,读取未提交的数据,然后回滚。

Begin Tran
Update table1 
set col1=val2
where col1=val1
select col1 from table1 WITH (NOLOCK);
rollback