"自動測試" 這個名詞對我來說不太陌生,在剛進公司時當時有位資深工程師寫了一支資料傳輸的程式,而當時菜鳥的我們被要求要寫替這支程式寫測試案例,並且做Unit Test。事實上我當時還一頭霧水,什麼是Unit Test,連聽都沒聽過。時光飛逝後的現在,一兩年時間過去了,這段時間內做的專案"測試工作"算是開發中很重要的公事,最近再回頭看網路上IN91的教學文章特別有感觸以及體會其中的奧妙,因此想要寫下些紀錄。
在TDD(Test-Driven Development)中分成了幾個步驟:
1. ATDD & BDD
2. TDD
3. Testing
4. Refactoring
在3項中Test還分成"驗收測試"、"整合測試"及"單元測試",在這一篇中主要講的是Testing中的單元測試中的Stub及Mock
Unit Test是對單一目標物件做測試,基本原則如下:
1. 一個測試案例只能有一個方法
2. 最小的測試單位
3. 不與外部(包含檔案、資料庫、網路、服務、物件或類別)直接相依
4. 不具備邏輯
5. 測試案例之間相依性為0
另外還有Stub, Mocks 及Fake
Stub - 用來驗證受測目標物件的回傳值 以及 驗證受測目標狀態改變
Mock - 則可用來驗證測試目標物件與其相依物件互動
Fake - 在測試中若需要用到.Net Framework原生套件,則可以用Fake來建立一個假的來用
用上一篇<Interface使用>來做一下練習。在這有一個Interface叫INewThings,他的實作則在Company裡。
public interface INewThings
{
string CreateNewMember(PersonInfo person);
};
public class PersonInfo
{
public string Name { get; set; }
public int age { get; set; }
}
public class Company : INewThings
{
string INewThings.CreateNewMember(PersonInfo PI)
{
if(PI.age <18)
{
return "too young to work";
}
else if(PI.age >65)
{
return "too old to work";
}
else
{
return PI.Name + "-" + PI.age;
}
}
public string humanresource(INewThings NewThings, PersonInfo Newguy)
{
return NewThings.CreateNewMember(Newguy);
}
}
接著建立測試專案並撰寫測試案例,依照這程式它共有三個狀態,基於單元測試原則一次只驗證一件事情,因此TestMethod會有三個。
在Visual Studio中要使用Stub Mocks需先透過NuGet安裝,在測試專案上按右鍵選擇管理NuGet套件,並搜尋RhinoMocks下載。
接著撰寫測試程式
[TestMethod]
public void TestAgeisInRange()
{
//arrange
INewThings stubNewThing = MockRepository.GenerateStub< INewThings >();
stubNewThing.Stub(x => x.CreateNewMember(Arg.Is.Anything)).Return("Frank-27");
Company cp = new Company();
PersonInfo PI = new PersonInfo();
PI.Name = "Frank";
PI.age = 27;
//act
var actualWork = cp.humanresource(stubNewThing, PI);
Assert.AreEqual("Frank-27", actualWork);
}
[TestMethod]
public void TestAgeLessThan18()
{
//arrange
INewThings stubNewThing = MockRepository.GenerateStub < INewThings >();
stubNewThing.Stub(x => x.CreateNewMember(Arg.Is.Anything)).Return("too young to work");
Company cp = new Company();
PersonInfo PI = new PersonInfo();
PI.Name = "Brown";
PI.age = 10;
//act
var actualWork = cp.humanresource(stubNewThing, PI);
Assert.AreEqual("too young to work", actualWork);
}
[TestMethod]
public void TestAgeGreatThan80()
{
//arrange
INewThings stubNewThing = MockRepository.GenerateStub < INewThings >();
stubNewThing.Stub(x => x.CreateNewMember(Arg .Is.Anything)).Return("too old to work"); Company cp = new Company(); PersonInfo PI = new PersonInfo(); PI.Name = "Chen"; PI.age = 85; //act var actualWork = cp.humanresource(stubNewThing, PI); Assert.AreEqual("too old to work", actualWork); }
在程式中可以看到因為humanresource這個方法其中一個參數是Interface INewThings,因此透過MockRepository.GenerateStub< INewThings >() 來生成一個獨立的物件提供測試使用,透過Mock產生取代物件後,還要透過.Return來設定預期回傳的值。
以上是Stub的寫法,接著以同樣範例來以Mock方式來撰寫
[TestMethod]
public void TestViaMocks()
{
MockRepository mock = new MockRepository();
INewThings stubNewThings = mock.StrictMock();
Company cp = new Company();
List People = new List();
PersonInfo person1 = new PersonInfo();
person1.Name = "Frank";
person1.age = 27;
People.Add(person1);
PersonInfo person2 = new PersonInfo();
person2.Name = "Brown";
person2.age = 12;
People.Add(person2);
PersonInfo person3 = new PersonInfo();
person3.Name = "Chen";
person3.age = 89;
People.Add(person3);
using (mock.Record())
{
cp.humanresource(stubNewThings, People[0]);
LastCall
.Return("Frank-27");
cp.humanresource(stubNewThings, People[1]);
LastCall
.Return("too young to work");
cp.humanresource(stubNewThings, People[2]);
LastCall
.Return("too old to work");
}
using (mock.Playback())
{
var target1 = cp.humanresource(stubNewThings, People[0]);
var target2 = cp.humanresource(stubNewThings, People[1]);
var target3 = cp.humanresource(stubNewThings, People[2]);
}
}
結論:
根據In91大大的經驗
使用 stub 的比例大概是8~9成,使用mock的比例大概僅1~2成。而 fake 的方式,則用在特例,例如靜態方法跟 .net framework 原生組件。而本身實務經驗上也很少用到這三個,也許打滾的還不夠久,但先熟悉起來未來也許會用到的!另外最近還摸了BDD、Selenium及重構,在另外做個紀錄。
資料參考
In91 - [30天快速上手TDD]

沒有留言:
張貼留言