我有一个网络服务,我想做一些单元测试,但是我不知道我怎么能这样做。谁能提出任何建议?下面是webservice,它生成一个包含三个字段的对象,但只有在数据库队列中有值时才会生成。
[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;
}
}
答案 0 :(得分:3)
您不需要通过WebService使用您的数据进行单元测试。您可以在解决方案中创建另一个项目,并引用WebService项目并直接调用方法。
答案 1 :(得分:1)
首先,您发布的内容根本无法进行单元测试;根据定义,单元测试只能有一个失败的原因;但是在您的情况下,GetDataLINQ()
(" 受测试系统"或" SUT")的单次测试可能会因为问题而失败与函数中的任何依赖关系 - 即TestProjectLinqSQLDataContext
和usp_dequeueTestProject
。
当您从单元测试中调用此方法时,目前这些依赖项可能无法控制,因为您没有直接创建它们 - 它们很可能是在您的页面类中创建的。构造函数。 (注意:这是我的假设,我可能是错的)
此外,因为这些依赖关系目前是真实的" live"对象存在对实际数据库的硬依赖性,这意味着您的测试无法独立运行,这是单元测试的另一个要求。
(我假设你的页面的类文件是" MyPageClass"从现在开始,我会假装它不是网页代码隐藏或asmx代码-behind;因为正如其他海报所指出的那样,这只在通过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()
时,请考虑此方法失败的一些可能原因:
new TestProjectLinqSQLDataContext()
构造函数中的错误, TestProjectLinqSQLDataContext
导致异常
dc.usp_dequeueTestProject()
导致异常,因为数据库连接失败,或者因为存储过程已更改,或者不存在。
command.Select(...)
导致异常,因为CommandMessage
构造函数中尚未知的某些缺陷
可能还有更多原因(即无法正确执行而不是抛出异常)
由于有多种失败方法,您无法快速可靠地告知出现了什么问题(当然,您的测试运行器会指出抛出的异常类型,但这需要您至少读取堆栈跟踪 - 您不应该为单元测试做这个)
因此,为了做到这一点,你需要能够设置你的SUT - 在这种情况下,GetDataLINQ
函数 - 这样任何和所有的依赖都完全在测试方法的控制之下。
因此,如果您真的想要对此进行单元测试,则必须对代码进行一些调整。如果你不能以任何理由实现这一点,我将概述理想情景,然后是一个替代(很多)。没有错误检查包含在下面的代码中,也没有编译,所以请原谅任何拼写错误等。
抽象依赖项,并将它们注入构造函数中。
请注意,此理想方案将要求您在项目中引入IOC框架(Ninject,AutoFAC,Unity,Windsor等)。它还需要一个Mocking框架(Moq等)。
IDataRepository
,其中包含方法DequeueTestProject
public interface IDataRepository
{
public CommandMessages DequeueTestProject();
}
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框架参与的地方,这样只要您的
IDataRepository
被ASP.NET框架实例化,它就可以注入正确的RealDataRepository
(即MyPageClass
)< / p>
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
如何响应不可见的结果,我们甚至可以抛出异常。 在您的项目中引入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
中存在缺陷,您的测试也可能会失败)但是& #39;一个很好的妥协;但是,如果可能的话,努力实现第一个场景,因为它会带来各种其他好处,让您更接近真正的SOLID代码。
希望有所帮助。
答案 2 :(得分:0)
我会按如下方式更改您的代码:
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服务时找到正确的方向。