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

时间:2014-05-22 15:46:24

标签: c# asp.net linq unit-testing

我有一个网络服务,我想做一些单元测试,但是我不知道我怎么能这样做。谁能提出任何建议?下面是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;
        }
    }

3 个答案:

答案 0 :(得分:3)

您不需要通过WebService使用您的数据进行单元测试。您可以在解决方案中创建另一个项目,并引用WebService项目并直接调用方法。

答案 1 :(得分:1)

首先,您发布的内容根本无法进行单元测试;根据定义,单元测试只能有一个失败的原因;但是在您的情况下,GetDataLINQ()(" 受测试系统"或" SUT")的单次测试可能会因为问题而失败与函数中的任何依赖关系 - 即TestProjectLinqSQLDataContextusp_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等)。

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

public interface IDataRepository
{
     public CommandMessages DequeueTestProject();
}

2。将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框架参与的地方,这样只要您的IDataRepository被ASP.NET框架实例化,它就可以注入正确的RealDataRepository(即MyPageClass)< / p>

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

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

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

  1. 必须始终调用DequeueTestProject
  2. 如果数据库中没有数据,则必须返回NULL
  3. 如果数据库中有数据,则必须返回有效的CommandMessages实例。
  4. 测试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如何响应不可见的结果,我们甚至可以抛出异常。
    • 当涉及单元测试时,这是抽象依赖关系的真正好处(更不用说,它减少了系统中的耦合,因为依赖关系不依赖于特定的具体类型)。

    不是很理想的方法

    在您的项目中引入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中存在缺陷,您的测试也可能会失败)但是& #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服务时找到正确的方向。