[英]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.