繁体   English   中英

如何使用Linq单元测试Web服务

[英]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项目的项目,然后直接调用方法。

首先,您发布的内容根本无法进行单元测试。 根据定义,单元测试只能有一个失败原因。 但是,在您的情况下,由于该函数中任何依赖项(即TestProjectLinqSQLDataContextusp_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等)。

1.创建一个接口IDataRepository ,其中包含方法DequeueTestProject

public interface IDataRepository
{
     public CommandMessages DequeueTestProject();
}

2.将IDataRepository声明为IDataRepository的依赖MyPageClass

 public class MyPageClass
    {
        readonly IDataRepository _repository;
        public MyPageClass(IDataRepository repository)
        {
             _repository=repository;
        }
    }

3.创建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 )。

4.重新GetDataLINQ()您的GetDataLINQ()方法以使用_ repository成员

public CommandMessages GetDataLINQ()
    {
        CommandMessages result;
        return _repository.DequeueTestProject();
    }

那么,这给我们带来了什么? 好吧,现在考虑如何针对GetDataLINQ的以下规范进行测试:

  1. 必须始终调用DequeueTestProject
  2. 如果数据库中没有数据,则必须返回NULL
  3. 如果数据库中有数据,则必须返回有效的CommandMessages实例。

测试1-必须始终调用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);
}

测试2-如果数据库中没有数据,则必须返回NULL

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);
}

测试3-如果数据库中有数据,则必须返回有效的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;
       }
    }

最后,适当地修改单元测试以使用FakeEmptyDatabaseRepositoryFakeFilledDatabaseRepository

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM