[英]How to unit test web service with Linq
我有一个Web服务,我想对它进行一些单元测试,但是我不确定如何做到这一点。 任何人都可以提出建议吗? 下面是Web服务,它产生一个具有三个字段的对象,但仅当数据库队列中有值时才产生。
[WebMethod]
public CommandMessages GetDataLINQ()
{
CommandMessages result;
using (var dc = new TestProjectLinqSQLDataContext())
{
var command = dc.usp_dequeueTestProject();
result = command.Select(c => new CommandMessages(c.Command_Type, c.Command, c.DateTimeSent)).FirstOrDefault();
return result;
}
}
您无需通过WebService消耗数据即可对其进行单元测试。 您可以仅在解决方案中创建一个引用WebService项目的项目,然后直接调用方法。
首先,您发布的内容根本无法进行单元测试。 根据定义,单元测试只能有一个失败原因。 但是,在您的情况下,由于该函数中任何依赖项(即TestProjectLinqSQLDataContext
和usp_dequeueTestProject
)存在问题,单个GetDataLINQ()
测试GetDataLINQ()
“被测系统 ”或“ SUT”)可能会失败。
当您从单元测试中调用此方法时,当前这些依赖关系可能超出了您的控制范围,因为您没有直接创建它们-它们很可能是在页面类的构造函数中创建的。 (注意:这是我的假设,我可能是错的)
另外,由于这些依赖关系目前是真实的“活动”对象,对存在的实际数据库具有严格的依赖关系,因此这意味着您的测试无法独立运行,这是单元测试的另一项要求。
(我假设您页面的类文件从现在开始就是“ MyPageClass”,并且我假装它不是网页代码背后或asmx代码背后;因为正如其他发布者所指出的那样,这仅在通过HTTP访问代码,我们不在这里进行操作)
var sut = new MyPageClass(); //sut now contains a DataContext over which the Test Method has no control.
var result = sut.GetDataLINQ(); //who know what might happen?
在调用sut.GetDataLINQ()
时,请考虑此方法失败的一些可能原因:
由于TestProjectLinqSQLDataContext
的构造函数中的错误, new TestProjectLinqSQLDataContext()
导致异常
dc.usp_dequeueTestProject()
导致异常,因为数据库连接失败,或者因为存储过程已更改或不存在。
由于CommandMessage
构造函数中某些未知的缺陷, command.Select(...)
导致异常
可能还有更多原因(例如,与抛出异常相反,无法正确执行 )
由于失败的多种方式,您无法快速而可靠地指出出了什么问题(确定您的测试运行器会指出抛出了哪种类型的异常,但这至少需要您读取堆栈跟踪信息-您不需要为此进行单元测试)
因此,为了做到这一点,您需要能够设置您的SUT(在本例中为GetDataLINQ
函数),以便所有依赖项都完全在测试方法的控制之下。
因此,如果您真的要对此进行单元测试,则必须对代码进行一些调整。 我将概述理想的情况,然后(如果有多种原因)不能实现此目标,则是其中的一种。 以下代码中没有错误检查,也没有进行编译,因此请原谅任何错别字等。
抽象依赖项,并将其注入到构造函数中。
请注意,这种理想情况将需要您在项目中引入IOC框架(Ninject,AutoFAC,Unity,Windsor等)。 它还需要一个模拟框架(Moq等)。
IDataRepository
,其中包含方法DequeueTestProject
public interface IDataRepository
{
public CommandMessages DequeueTestProject();
}
IDataRepository
声明为IDataRepository
的依赖MyPageClass
public class MyPageClass
{
readonly IDataRepository _repository;
public MyPageClass(IDataRepository repository)
{
_repository=repository;
}
}
IDataRepository
的实际实现,该实现将在“现实生活”中使用,但不会在单元测试中使用 public class RealDataRepository: IDataRepository
{
readonly MyProjectDataContext _dc;
public RealDataRepository()
{
_dc = new MyProjectDataContext(); //or however you do it.
}
public CommandMessages DequeueTestProject()
{
var command = dc.usp_dequeueTestProject();
result = command.Select(c => new CommandMessages(c.Command_Type, c.Command, c.DateTimeSent)).FirstOrDefault();
return result;
}
}
这是您需要包含IOC框架的地方,以便每当ASP.NET框架实例化
MyPageClass
时,它就可以注入正确的IDataRepository
(即RealDataRepository
)。
GetDataLINQ()
您的GetDataLINQ()
方法以使用_ repository
成员 public CommandMessages GetDataLINQ()
{
CommandMessages result;
return _repository.DequeueTestProject();
}
那么,这给我们带来了什么? 好吧,现在考虑如何针对GetDataLINQ的以下规范进行测试:
DequeueTestProject
NULL
CommandMessages
实例。 DequeueTestProject
public void GetDataLINQ_AlwaysInvokesDequeueTestProject()
{
//create a fake implementation of IDataRepository
var repo = new Mock<IDataRepository>();
//set it up to just return null; we don't care about the return value for now
repo.Setup(r=>r.DequeueTestProject()).Returns(null);
//create the SUT, passing in the fake repository
var sut = new MyPageClass(repo.Object);
//call the method
sut.GetDataLINQ();
//Verify that repo.DequeueTestProject() was indeed called.
repo.Verify(r=>r.DequeueTestProject(),Times.Once);
}
public void GetDataLINQ_ReturnsNULLIfDatabaseEmpty()
{
//create a fake implementation of IDataRepository
var repo = new Mock<IDataRepository>();
//set it up to return null;
repo.Setup(r=>r.DequeueTestProject()).Returns(null);
var sut = new MyPageClass(repo.Object);
//call the method but store the result this time:
var actual = sut.GetDataLINQ();
//Verify that the result is indeed NULL:
Assert.IsNull(actual);
}
CommandMessages
实例。 public void GetDataLINQ_ReturnsNCommandMessagesIfDatabaseNotEmpty()
{
//create a fake implementation of IDataRepository
var repo = new Mock<IDataRepository>();
//set it up to return null;
repo.Setup(r=>r.DequeueTestProject()).Returns(new CommandMessages("fake","fake","fake");
var sut = new MyPageClass(repo.Object);
//call the method but store the result this time:
var actual = sut.GetDataLINQ();
//Verify that the result is indeed NULL:
Assert.IsNotNull(actual);
}
IDataRepository
接口,所以我们可以完全控制它的行为。 GetDataLINQ
如何响应无法GetDataLINQ
结果,我们甚至可以使它抛出异常。 在您的项目中引入IOC框架可能是一个失败的尝试,因此这是一个折衷方案。 还有其他方法,这只是想到的第一个方法。
IDataRepository
接口 RealDataRepository
类 IDataRepository
其他实现,以模仿我们在前面的示例中动态创建的行为。 这些被称为存根,并且基本上它们只是具有单个预定义行为且永不改变的类 。 这使之成为测试的理想选择,因为您始终知道调用它们时会发生什么。 public class FakeEmptyDatabaseRepository:IDataRepository
{
public CommandMessages DequeueTestProject(){CallCount++;return null;}
//CallCount tracks if the method was invoked.
public int CallCount{get;private set;}
}
public class FakeFilledDatabaseRepository:IDataRepository
{
public CommandMessages DequeueTestProject(){CallCount++;return new CommandMessages("","","");}
public int CallCount{get;private set;}
}
现在,按照第一个方法修改MyPageClass
,除了不要在构造函数上声明IDataRepository
,而是这样做:
public class MyPageClass
{
private IDataRepository _repository; //not read-only
public MyPageClass()
{
_repository = new RealDataRepository();
}
//here is the compromise; this method also returns the original repository so you can restore it if for some reason you need to during a test method.
public IDataRepository SetTestRepo(IDataRepository testRepo)
{
_repository = testRepo;
}
}
最后,适当地修改单元测试以使用FakeEmptyDatabaseRepository
或FakeFilledDatabaseRepository
:
public void GetDataLINQ_AlwaysInvokesDequeueTestProject()
{
//create a fake implementation of IDataRepository
var repo = new FakeFilledDatabaseRepository();
var sut = new MyPageClass();
//stick in the stub:
sut.SetTestRepo(repo);
//call the method
sut.GetDataLINQ();
//Verify that repo.DequeueTestProject() was indeed called.
var expected=1;
Assert.AreEqual(expected,repo.CallCount);
}
请注意,第二种情况不是象牙塔式的理想情况,并且不会导致严格的纯单元测试(即,如果FakeEmptyDatabaseRepository
存在缺陷,您的测试也可能会失败),但这是一个很好的折衷方案。 但是,如果可能的话,请努力实现第一个方案,因为它可以带来其他各种好处,并使您更接近真正的SOLID代码。
希望能有所帮助。
我将更改您的代码,如下所示:
public class MyRepository
{
public CommandMessage DeQueueTestProject()
{
using (var dc = new TestProjectLinqSQLDataContext())
{
var results = dc.usp_dequeueTestProject().Select(c => new CommandMessages(c.Command_Type, c.Command, c.DateTimeSent)).FirstOrDefault();
return results;
}
}
}
然后将您的Web方法编码为:
[WebMethod]
public CommandMessages GetDataLINQ()
{
MyRepository db = new MyRepository();
return db.DeQueueTestProject();
}
然后编写单元测试代码:
[Test]
public void Test_MyRepository_DeQueueTestProject()
{
// Add your unit test using MyRepository
var r = new MyRepository();
var commandMessage = r.DeQueueTestProject();
Assert.AreEqual(commandMessage, new CommandMessage("What you want to compare"));
}
这使您的代码可重用,并且是具有数据存储库的常见设计模式。 现在,您可以在需要的任何地方使用您的存储库,并仅在一个地方对其进行测试,并且在您使用它的任何地方都应该很好。 这样,您不必担心调用WCF服务的复杂测试。 这是测试Web方法的好方法。
这只是一个简短的解释,可以进行更多改进,但这可以使您正确地构建Web服务。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.