WF单元测试系列3:测试Activity的行为
?????上一篇中介紹了如何簡單地測試Activity的行為,我們只是使用VSTS的單元測試工具,捕獲了一個期待的異常,這樣簡單的行為測試當然是不能滿足我們的需求的,我們要進一步測試Activity的更多,更復雜的行為。
?????比如我們有一個LogActivity 負責寫日志,任何調用它的結果等同于調用一個ILog接口的一個Write()方法。我們就要寫一個單元測試,測試是否LogActivity的調用等同于對ILog接口的Write()方法的調用。
?????有的人不禁要問了,這樣的測試如何做呢?我們的測試工具只支持Assert操作,難道我要去Check日志文件,看看兩個日志是否相等嗎?答案是否定的,我們不會這么做,因為這樣還屬于“狀態測試”的范疇,而不是“行為測試”。
?????還好有人想到了這一點,提供給我們現成的工具,這就是Mock Object Framework。Mock Object Framework基于單元測試中的這樣幾個問題:
?????1.在一個龐大的系統中,成百上千個類共同協調完成一個功能。往往一個調用的背后,是幾十個類在共同工作,而且很多類的創建很耗費資源和時間。在這樣的情況下,運行一個測試的代價可想而知,何況我們每次對代碼做一次改動,就要運行單元測試呢。
?????2.很多代碼都需要調用外部資源,比如,讀取配置文件,讀取數據庫,調用遠程的Web Service等等。這樣的操作很費時間,最主要的是依賴外部的資源。如果外部資源處了問題,測試將無法進行下去。
?????3.對系統的行為無法準確捕獲。一個簡單的調用,系統究竟調用了那些類的那個方法,調用了幾次,做了幾次查詢,修改,等等,一系列的操作都無法準確測試,難以保證單元測試的嚴謹性。
?????基于以上幾點考慮(當然不是全部的),Mock Object Framework出現了。它解決了這些問題,
?????所以成為了TDD的必備武器!
?????Mock Object Framework的主要功能:
?????1.Mock Object---顧名思義,就是模仿對象。正是由于單元測試中龐大的對象群的創建太耗費資源和時間,所以我們使用Mock Object來代替那些對象。Mock Object是輕量級的,代價很低,所以大大減少單元測試的開銷。
?????2.對于一些需要訪問外部資源的對象,Mock可以用硬編碼代替對外部資源的訪問。從而提高效率。
?????3.Mock Object Framework可以準確地記錄所模仿的對象的一切操作,使得我們可以準確地測試對象的行為是否符合要求。
?????4.Mock Object Framework可以模仿接口,抽象類,或者尚未實現的類,從而加快開發速度。
?????關于Mock Object Framework,大家可以去找一些資料。
?????這里我們使用的是Rhino Mock,大家可以去這里看Rhino Mock的簡單介紹:http://stephenwalther.com/blog/archive/2008/03/23/tdd-introduction-to-rhino-mocks.aspx
?????下面開始我們的單元測試:
?????在前文的工程中,加入一個名叫的Activity,繼承自BaseEmployeeActivity。定義三個屬性:
????????public?static?readonly?DependencyProperty
????????????????NewEmployeeProperty?=?DependencyProperty.Register("NewEmployee",
????????????????????typeof(AcmeEmployee),?typeof(StoreNewAcmeEmployee));
????????public?AcmeEmployee?NewEmployee
????????{
????????????get?{?return?(AcmeEmployee)GetValue(NewEmployeeProperty);?}
????????????set?{?SetValue(NewEmployeeProperty,?value);?}
????????}
????????public?IEmployeeRepository?EmployeeDataStore?{?get;?set;?}
????????protected?override?ActivityExecutionStatus?Execute(ActivityExecutionContext?
executionContext)
????????{
????????????return?ActivityExecutionStatus.Closed;
????????}
?????我們設定這個Activity的功能是:根據給定的參數,創建一個NewEmployee對象,把它存儲在EmployeeDataStore對象中。我們先不實現Execute()方法。注意:EmployeeDataStore會在后面使用依賴注入賦值。
?????為了測試這個Activity,我們如法炮制,在測試工程中加一個新的Activity:StoreNewAcmeEmployee_Accessor,繼承自StoreNewAcmeEmployee。并Wrap它的Execute方法。
?????接下來添加一個測試類:StoreNewAcmeEmployee_ActivityShould。記得引用Rhino.Mocks庫。
?????先把代碼貼出來:
using?System;
using?AcmeCorp.DomainLibrary.AcmeEmployeeDomain;
using?Microsoft.VisualStudio.TestTools.UnitTesting;
using?NewEmployeeWFLibrary.Activities;
using?NewEmployeeWFLibrary_Test.Helpers;
using?Rhino.Mocks;//引用Rhino?Mock庫
namespace?NewEmployeeWFLibrary_Test.Activities
{
????[TestClass]
????public?class?StoreNewAcmeEmployee_ActivityShould
????{
????????//被Mock的對象,將被注入到StoreNewAcmeEmployee的EmployeeDataStore屬性。
????????private?IEmployeeRepository?_mockEmployeeRepository;
????????
????????//Mock功能類
????????private?MockRepository?_mocker;
????????[TestInitialize]
????????public?void?TestInitializer()
????????{
????????????//初始化Mock功能類???????????
????????????_mocker?=?new?MockRepository();
????????????//創建Mock對象
????????????_mockEmployeeRepository?=?_mocker.CreateMock<IEmployeeRepository>();
????????}
????????[TestMethod]
????????public?void?CallAddOnTheRepo()
????????{
????????????//記錄Mock對象的操作
????????????using?(_mocker.Record())
????????????{
????????????????_mockEmployeeRepository.Add(null);
????????????????LastCall.IgnoreArguments();//忽略上面的null參數,即傳什么參數都一樣
。
????????????}
????????????//回放Mock對象的操作,Rhino?Mock會自動檢驗對被Mock的對
象_mockEmployeeRepository的操作與上面記錄的是否一致
????????????//如果不一致,就會自動拋出異常
????????????using?(_mocker.Playback())
????????????{
????????????????using?(StoreNewAcmeEmployee_Accessor?activity?=?new?
StoreNewAcmeEmployee_Accessor())
????????????????{
????????????????????SetActivityProperties(activity,?Mother.FIRST_NAME,?
Mother.LAST_NAME,?Mother.EMAIL,
??????????????????????????????????????????_mockEmployeeRepository);
????????????????????activity.Execute_Accessor(null);
????????????????}
????????????}
????????}
????????
????????private?void?SetActivityProperties(StoreNewAcmeEmployee?activity,?string?
firstName,?string?lastName,
???????????????????????????????????????????string?email,?IEmployeeRepository?
employeeRepo)
????????{
????????????activity.FirstName?=?firstName;
????????????activity.LastName?=?lastName;
????????????activity.Email?=?email;
????????????activity.EmployeeDataStore?=?employeeRepo;//注入被Mock對象
????????}
????}
}
?
?????在上面的代碼中,下面一句比較重要
?????_mockEmployeeRepository = _mocker.CreateMock<IEmployeeRepository>();
?????這一句創建了針對接口IEmployeeRepository的Mock對象。注意:在我的工程里,名沒有實現這個接口。這就是Mock的特點,可以不用實現功能就能測試。可謂針對接口測試吧!:)
?????在接下來的測試方法中,首先使用_mocker.Record()記錄對Mock對象的操作。在_mocker.Record()的Unsing塊中的所有操作,都是我們期待的Mock對象的“行為”。而_mocker.Playback()塊則是對Activity的實際測試過程,在這個過程中,必須和Record塊對_mockEmployeeRepository的操作一致------也就是說,也調用了它的Add()方法。如果一致,測試通過,否則Rhino Mock會拋出異常,測試失敗!
?????我們運行一下測試,失敗!因為還沒有為StoreNewAcmeEmployee的Execute()方法添加邏輯。
?????回過頭來實現StoreNewAcmeEmployee的Execute()方法:
protected?override?ActivityExecutionStatus?Execute(ActivityExecutionContext?
executionContext)
????????{
????????????NewEmployee?=?new?AcmeEmployee(Guid.NewGuid().ToString(),?FirstName,?
LastName,?Email);
????????????EmployeeDataStore.Add(NewEmployee);
????????????return?ActivityExecutionStatus.Closed;
????????}
???????
?????最關鍵的一個操作就是調用了注入進來的EmployeeDataStore的Add()方法!
?????再次運行測試!通過!
?????上面討論了如何對Activity進行“行為測試”,當然這還只是一個開始。
?????類似前一篇中提到的,我們為測試加入“異常處理”。
?????添加如下測試方法和輔助方法:
[TestMethod]
????????[ExpectedException(typeof(ArgumentNullException))]
????????public?void?ThrowExceptionOnNullFirstName()
????????{
????????????ExerciseActivityWithNoExpectations(null,?Mother.LAST_NAME,?
Mother.EMAIL,?_mockEmployeeRepository);
????????}
????????private?void?ExerciseActivityWithNoExpectations(string?firstName,?string?
lastName,?string?email,
????????????????????????????????????????????????????????IEmployeeRepository?
employeeRepository)
????????{
????????????_mocker.Record().Dispose();//不記錄任何操作
????????????using?(_mocker.Playback())
????????????{
????????????????using?(StoreNewAcmeEmployee_Accessor?activity?=?new?
StoreNewAcmeEmployee_Accessor())
????????????????{
????????????????????SetActivityProperties(activity,?firstName,?lastName,?email,?
employeeRepository);
????????????????????activity.Execute_Accessor(null);
????????????????}
????????????}
????????}
???????
?????在這里,沒有對Mock對象進行任何記錄。就是說不期待對Mock對象進行任何操作。但是期待一個異常:ArgumentNullException,因為傳入和一個Null參數。
?????回到StoreNewAcmeEmployee中,改造Execute()方法為:
?
???protected?override?ActivityExecutionStatus
????????????????Execute(ActivityExecutionContext?executionContext)
????????{
????????????EnsurePropertiesAreValid();
????????????
????????????NewEmployee?=?new?AcmeEmployee(Guid.NewGuid().ToString(),
??????????????????????????????FirstName,
??????????????????????????????LastName,
??????????????????????????????Email);
????????????EmployeeDataStore.Add(NewEmployee);
????????????return?ActivityExecutionStatus.Closed;
????????}
????????private?void?EnsurePropertiesAreValid()
????????{
????????????if?(Email?==?null)
????????????????throw?new?ArgumentNullException("Email");
????????????if?(FirstName?==?null)
????????????????throw?new?ArgumentNullException("FirstName");
????????????if?(LastName?==?null)
????????????????throw?new?ArgumentNullException("LastName");
????????????if?(EmployeeDataStore?==?null)
????????????????throw?new?ArgumentNullException("Email");
????????}
?????在調用EmployeeDataStore的Add()方法之前,檢查參數是否合法,不合法就拋出異常,不調用EmployeeDataStore的Add()方法。
?????運行測試,通過!
?????對Activity的行為測試就到這里,下次為大家介紹對Workflow的測試。
?????附源碼:NewEmployeeWF3.rar
?????注:以上示例來自WF3.5 Hands On Lab,英文好的朋友可以去這里看:https://www.microsoft.com/resources/virtuallabs/step3-msdn.aspx?LabId=c4a993a5-d498-4d5c-9f98-476c1f496d15&BToken=reg
轉載于:https://www.cnblogs.com/zhaojunqi/archive/2009/03/10/1407951.html
總結
以上是生活随笔為你收集整理的WF单元测试系列3:测试Activity的行为的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Ajax实战:Ajax的四个基本原则
- 下一篇: Visual C#中的(ListBox)