c# - 我可以模拟 SqlConnection.BeginTransaction c# 吗?

标签 c# unit-testing

我必须使用一些我无法更改的遗留代码。我必须编写实现一个名为“ITask”的接口(interface)的类,该接口(interface)有一个名为“RunTask”的方法,该方法接受名为 Schedule 的具体类型,例如;

 public void RunTask(Schedule thisSchedule)
 {
     //I have to do stuff with thisSchedule, no I can't fix that name...
 }

虽然遗留代码不使用单元测试,但我非常想将它用于我的工作,但问题是“thisSchedule”。我制作了一个从它派生的 Schedule 的假版本,并试图控制其中的所有方法如何运行(这可能是一个傻瓜的差事)。到目前为止,我已经通过利用数量惊人的虚拟方法或使用反射取得了成功,但我已经遇到了第一个阻碍

我无法连接到真实的数据库,并且我有一个经常被调用的方法;

public void BeginTransaction()
{
    MyTransaction = MyConnection.BeginTransaction(IsolationLevel.RepeatableRead);
}

internal SqlConnection MyConnection = new System.Data.SqlClient.SqlConnection();

这会引发异常,因为连接已关闭,理想情况下,我希望能够设置一个标志,表明该方法已被调用,然后只是吞下该异常,但我很乐意简单地忽略该异常。

任何事情,无论多么令人讨厌,只要能让我通过这个方法调用,都是可以接受的答案。要么失败,要么失败。

我不允许在 Visual Studio Enterprise 中使用 typeMock 或 shim 等付费服务。

编辑

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Data;
using System.Data.SqlClient;

namespace PredictionServicesTests
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void BeginTransaction_WhenCalled_SetsTransactionStartedToTrue()
        {
            Schedule schedule = new FakeSchedule();

            schedule.BeginTransaction(); //It would be enough to simply get past this line to the Assert

            Assert.IsTrue(((FakeSchedule)schedule).TransactionStarted); //This would be nice, but isn't vital
        }

        class Schedule
        {
            private SqlTransaction MyTransaction;
            internal SqlConnection MyConnection = new System.Data.SqlClient.SqlConnection();
            public void BeginTransaction()
            {
                MyTransaction = MyConnection.BeginTransaction(IsolationLevel.RepeatableRead);
            }
        }

        class FakeSchedule : Schedule
        {
            public bool TransactionStarted { get; set; }
        }
    }
}

请记住,我无法更改 Schedule 类,但愿事情能这么简单!

ITask 接口(interface);

    interface ITask
    {
        void RunTask(Schedule thisSchedule);
    }

最佳答案

当涉及到单元测试时,任何外部依赖都需要隔离。不同的框架有不同的隔离外部依赖的方式,以及它们可以隔离的限制。

许多框架允许您创建接口(interface)的模拟,以便您可以为处于受控状态的成员提供实现。许多这样的框架也可以很好地与抽象类的抽象成员配合使用。然而,很少有框架支持在具体类(非抽象成员)上提供实现的能力。我知道的唯一两个可用的是:


这两个框架都利用 .NET Profiler API 来拦截成员调用,并用您提供的代码替换它们。我对 TypeMock Isolator 不太熟悉,但对 Microsoft Fakes 非常熟悉。 Microsoft Fakes 框架支持生成程序集(即 System.Fakes.dll),其中包含允许您在成员上提供自己的实现的类。它支持针对接口(interface)和抽象类(相当于“模拟”)创建 Stub,以及针对具体类和静态类创建 Shims。

如果您选择使用 Microsoft Fakes,您首先需要针对 System.Data.dll 生成一个伪造的程序集,这是 SqlConnection 所在的位置。这将生成 ShimSqlConnection 类,该类将包含允许您提供 SqlConnection 类成员的替代实现的成员。以下是如何做到这一点的示例:

[TestMethod]
public void SampleTest()
{
    using (ShimsContext.Create())
    {
        // Arrange
        SqlConnection.AllInstances.BeginTransactionIsolationLevel =
            (instance, iso) => { ... };

        // Act

        // Assert
    }
}

当然,无论是模拟、垫片、 stub ,还是无论什么......您都应该提供模拟您想要的行为的替代实现。由于您要替换 SqlConnection.BeginTransaction(IsolationLevel)成员(member),您仍必须遵守其契约(Contract)和预期行为;您不应该返回实际实现不会执行的值或抛出异常。在我的公司,我们利用 Microsoft Fakes 来模拟以下场景:

  • 如果我们读取时 Stream 被关闭,会发生什么?
  • 如果HttpWebRequest的用户/密码错误会发生什么?
  • 如果我们希望数据库返回两行(即控制 SqlDataReader.Read() 成员),会发生什么?

在所有这些场景中,我们的目标是隔离这些成员的已实现行为,并将其替换为在受控实验中实际发生的实现。


更新:

我刚刚注意到原始发帖者声明“我不允许在 Visual Studio Enterprise 中使用 typeMock 或 shim 等付费服务。”。如果这是一个限制,那么您将不得不找到另一个我不知道的使用 .NET Profiler API 的工具或自己使用该框架。 Profiling (Unmanaged API Reference) .

关于c# - 我可以模拟 SqlConnection.BeginTransaction c# 吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43350993/

相关文章:

c# - 过滤匿名类型集合

c# - Generic Get 方法不包括 FK 的?

c# - 我如何使用 C# 将逗号分隔的字符串转换为小写

php - PHP 和 SQL 中的单元测试

c++ - 是否有与 VS2008 或 Eclipse CDT 集成到 CPPUnit 的插件?

c# - Selenium中使用FireFoxDriver时,如何知道页面Url?

java - 编写用于下载文件的单元测试

c# - DynamicActivity - 如何调用存储在数据库中的工作流?

c# - UTF-8 在 C# 中用双引号编码字符串

java - 如何对使用 ProcessBuilder 和 Process 的 Java 方法进行单元测试?