簡體   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