C# 我可以模拟SqlConnection.BeginTransaction c吗?
我必须处理一些无法更改的遗留代码。我必须编写实现一个名为“ITask”的接口的类,该接口有一个名为“RunTask”的方法,该方法接受一个名为Schedule的具体类型,例如C# 我可以模拟SqlConnection.BeginTransaction c吗?,c#,unit-testing,C#,Unit Testing,我必须处理一些无法更改的遗留代码。我必须编写实现一个名为“ITask”的接口的类,该接口有一个名为“RunTask”的方法,该方法接受一个名为Schedule的具体类型,例如 public void RunTask(Schedule thisSchedule) { //I have to do stuff with thisSchedule, no I can't fix that name... } 虽然遗留代码不使用单元测试,但我非常希望在工作中使用它,但问题是“这个时间表
public void RunTask(Schedule thisSchedule)
{
//I have to do stuff with thisSchedule, no I can't fix that name...
}
虽然遗留代码不使用单元测试,但我非常希望在工作中使用它,但问题是“这个时间表”。我已经制作了一个假的Schedule版本,它是由Schedule派生的,我正试图控制Schedule中所有方法的工作方式(这可能是一件傻事)。到目前为止,我已经成功地利用了惊人数量的虚拟方法或使用反射,但我已经击中了我的第一个显示停止
我无法连接到真实的数据库,我有一个经常被调用的方法
public void BeginTransaction()
{
MyTransaction = MyConnection.BeginTransaction(IsolationLevel.RepeatableRead);
}
internal SqlConnection MyConnection = new System.Data.SqlClient.SqlConnection();
这会引发一个异常,因为连接已关闭。理想情况下,我希望能够设置一个标志,说明调用了该方法,然后吞下该异常,但我很乐意忽略该异常
任何让我通过这个方法调用的事情,无论多么糟糕,都是可以接受的答案。要么失败,要么失败
我不允许在visual studio enterprise中使用付费服务,如typeMock或Shimmers
编辑
请记住我不能改变课程安排,只要事情能这么简单就好了
ITask接口
interface ITask
{
void RunTask(Schedule thisSchedule);
}
事实上,您正在类中“更新”SQL连接,这使得很难反转控件并使其更易于单元测试
另一种选择是连接到虚拟数据库并使用该连接。虚拟数据库将有足够的数据来允许测试作为集成测试进行
该遗留代码应被视为您无法控制的第三方代码。您不应该浪费时间测试您无法控制的第三部分代码。通常的做法是将第三方代码封装在您所控制和使用的抽象背后。遗留代码表明,由于设计不善,它目前面临的困难正在被利用
考虑到当前的限制,没有什么其他的事情可以做。当涉及到单元测试时,任何外部依赖都需要隔离。不同的框架有不同的隔离外部依赖性的方法,以及它们可以隔离的限制 许多框架允许您创建接口的模拟,以便可以为处于受控状态的成员提供实现。这些框架中的很多都能很好地处理抽象类的抽象成员。但是,很少有框架支持在具体类(非抽象成员)上提供实现。据我所知,仅有两种可用的方法是:
这两个框架都利用.NET Profiler API拦截成员调用,并用您提供的代码替换它们。我不太熟悉TypeMock隔离器,但我非常熟悉Microsoft Fakes。Microsoft Fakes框架支持生成程序集(即System.Fakes.dll),该程序集包含允许您在成员上提供自己的实现的类。它支持针对接口和抽象类(相当于“mock”)创建存根,并针对具体和静态类创建垫片 如果选择使用Microsoft Fakes,则首先需要针对System.Data.dll生成一个伪程序集,该程序集位于
SqlConnection
所在的位置。这将生成ShimSqlConnection
类,该类将包含允许您提供SqlConnection
类成员的替代实现的成员。下面是一个如何做到这一点的示例:
[TestMethod]
public void SampleTest()
{
using (ShimsContext.Create())
{
// Arrange
SqlConnection.AllInstances.BeginTransactionIsolationLevel =
(instance, iso) => { ... };
// Act
// Assert
}
}
当然,不管是模拟、填隙、存根还是其他什么。。。您应该提供模拟所需行为的替代实现。由于您要替换该成员,因此您仍必须遵守其合同和预期行为;您不应该返回实际实现不会返回的值或引发异常。在我的公司,我们利用Microsoft Fakes模拟以下场景:
- 当我们从
流中读取时,如果该流关闭,会发生什么情况
- 如果HttpWebRequest的用户/密码错误,会发生什么情况
- 如果希望数据库返回两行(即控制
成员),会发生什么情况SqlDataReader.Read()
更新:
我刚刚注意到原始海报上写着“我不允许在visual studio enterprise中使用typeMock或Shimmers等付费服务。”。如果这是一个限制,那么您必须找到另一个我不知道的工具,它使用.NET Profiler API或自己使用框架 我能得到的最接近的方法是创建一个名为IFake的接口,我将应用于FakeSchedule
public interface IFake
{
Action GetFakeAction(Action action);
}
public class FakeSchedule : Schedule, IFake
{
public bool TransactionStarted { get; set; }
public Action GetFakeAction(Action action)
{
return () => TransactionStarted = true;
}
}
然后我创建了以下扩展方法
public static void Testable<T>(this T obj, Action action)
{
if(obj is IFake)
{
action = ((IFake)obj).GetFakeAction(action);
}
action();
}
显然,这个解决方案远非理想,但它是有效的,如果我正在运行一个单元测试,那么开始事务的调用就偏离了真正的实现
我实现的版本将需要一些工作(我的GetFakeAction实现太愚蠢了),但这是我认为我将要得到的最接近的版本,我能看到的唯一其他解决方案是使用或遵循并连接到虚拟数据库。您可以使用免费的Moq。还要研究如何使用接口。这将使单元测试这类东西更容易。在事务处理的情况下,也许您可以将IDbTransaction.Moq传递给win。当谈到.Net中的模拟时,它是一个神奇的子弹。请在一个可以用来重现问题的文档中提供更多细节。那么可能是一个解决方案/解决方案b
public static void Testable<T>(this T obj, Action action)
{
if(obj is IFake)
{
action = ((IFake)obj).GetFakeAction(action);
}
action();
}
[TestMethod]
public void BeginTransaction_WhenCalled_SetsTransactionStartedToTrue()
{
Schedule schedule = new FakeSchedule();
var connection = new SqlConnection();
schedule.Testable(schedule.BeginTransaction);
Assert.IsTrue(((FakeSchedule)schedule).TransactionStarted);
}