我必须处理一些我无法改变的遗留代码。我必须编写实现名为" ITask"的接口的类。有一个名为" 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或shims这样的付费服务。
修改
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 ITask
{
void RunTask(Schedule thisSchedule);
}
答案 0 :(得分:1)
你是" newing"在类中的SQL连接使得很难反转控件并使其更加单元测试友好。
另一个选择是连接到虚拟数据库并使用该连接。虚拟数据库就足以允许将测试作为集成测试来运行。
应将遗留代码视为您无法控制的第三方代码。而且你不应该浪费时间测试你无法控制的第三部分代码。通常的做法是将第三方代码封装在您控制并使用它的抽象之后。遗留代码演示technical debt由于设计不佳而正在兑现当前遇到的困难。
鉴于目前的限制,其他任何事情都无法完成。
答案 1 :(得分:1)
在单元测试方面,任何外部依赖都需要隔离。不同的框架有不同的隔离外部依赖关系的方式,以及它们可以隔离的限制。
许多框架允许您创建接口的模拟,以便您可以为受控状态的成员提供实现。很多这些框架也适用于抽象类的抽象成员。但是,很少有框架支持在具体类(非抽象成员)上提供实现的能力。我知道的唯一两个可用的是:
这两个框架都使用.NET Profiler API来拦截成员调用,以使用您提供的代码替换它们。我对TypeMock Isolator不太熟悉,但我对Microsoft Fakes非常熟悉。 Microsoft Fakes框架支持生成程序集(即System.Fakes.dll),该程序集包含允许您在成员上提供自己的实现的类。它支持创建针对接口和抽象类的Stub(相当于“mocks”),以及针对具体和静态类的Shims。
如果您选择使用Microsoft Fakes,则首先需要针对System {Data.dll生成假装配,这是SqlConnection
所在的位置。这将生成ShimSqlConnection
类,此类将包含允许您提供SqlConnection
类'成员的替代实现的成员。以下是如何做到这一点的示例:
[TestMethod]
public void SampleTest()
{
using (ShimsContext.Create())
{
// Arrange
SqlConnection.AllInstances.BeginTransactionIsolationLevel =
(instance, iso) => { ... };
// Act
// Assert
}
}
当然,无论是模拟,填充,存根还是其他......你应该提供模拟你想要的行为的替代实现。由于您要替换SqlConnection.BeginTransaction(IsolationLevel)
成员,您仍必须遵守合同和预期行为;您不应该返回值或抛出实际实现不会执行的异常。在我的公司,我们利用Microsoft Fakes来模拟以下场景:
Stream
时会发生什么? HttpWebRequest
的用户/通行证有误,会怎样? SqlDataReader.Read()
成员)会怎样? 在所有这些场景中,我们的目标是隔离这些成员的实现行为,并将其替换为在受控实验中可能实际发生的实现。
更新
我刚刚注意到原始海报上说“我不允许在视觉工作室企业中使用像typeMock或垫片这样的付费服务。”。如果这是一个限制,那么你将不得不找到另一个我不知道利用.NET Profiler API或自己使用框架的工具。 Profiling (Unmanaged API Reference)
答案 2 :(得分:0)
我最接近的是创建一个名为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();
}
这允许我像这样调用BeginTransaction方法;
[TestMethod]
public void BeginTransaction_WhenCalled_SetsTransactionStartedToTrue()
{
Schedule schedule = new FakeSchedule();
var connection = new SqlConnection();
schedule.Testable(schedule.BeginTransaction);
Assert.IsTrue(((FakeSchedule)schedule).TransactionStarted);
}
显然这个解决方案远非理想,但它可行,如果我正在运行单元测试,那么开始交易的调用就会转移到真正的实现之外。
我实现的版本需要一些工作(我的GetFakeAction实现太愚蠢)但它是我认为最接近的,我能看到的唯一其他解决方案是使用{{ 3}}或跟随Prig并连接到虚拟数据库。