單元測試學習筆記01_基本觀念介紹和測試框架使用

 

大綱

1.測試的種類
2.單元測試框架的定義
3.撰寫單元測試的目的與效益
4.較好的撰寫單元測試原則
5.主流測試框架的使用
MSTest
NUnit
6.虛設常式(stub)跟模擬物件(mock)、假物件(Fake)






1.測試的種類


軟體測試一般分4階段:



1.單元測試:對軟體中的最小可驗證單元進行檢查和驗證。各個程式獨
立單元會與其他單元隔離。
優點:盡早發現Bug、較利於重構、需要的測試環境和情境前置準備較單純
缺點:增加開發人員的工作量、學習門檻及時間成本較高、沒辦法找出跨模組時期才會出現的Bug


2.整合測試:軟體功能單元依據設計需求規格,組裝成模組、(子)系統的
過程中各部分工作是否有成功達到要求。
優點:較能驗證到模組間銜接、參數互傳的功能是否穩定
缺點:
每次都要重Build運行花費的時間太久(比較多重複性動作)
需要的測試環境和情境前置準備較為繁雜

3.系統測試:在所有單元、整合測試後,對系統的功能及效能的總體測試。在此階段測試不僅僅包括軟體本身,還包括電腦硬體及其相關的周邊裝置、實際執行時大批量資料、非正常操作(如資安攻擊)等。通常包括壓力測試、容量測試、效能測試、安全測試、容錯測試….等。

4.驗收測試(交付測試):針對使用者需求、業務流程進行的測試,以確定系統是否滿足驗收標準,由使用者、客戶或其他授權機構決定是否接受系統。



2.單元測試框架的定義


單元測試框架 (Unit Test Framework)
讓開發人員可以輕易地撰寫以「類 (Class)」為單位的測試程式,並且隨時可以執行自動化的重複性測試 (automation repeatable test),以確保該單一類別的正確性。





3.撰寫單元測試的目的與效益


目的:
1.能快速發現既有的某段程式碼邏輯是否因需求修改增加而有被變動。
2.減少重複性的測試前置動作
3.自動化測試原先所有測試案例,節省人工手動重複測試的耗時。
Unit Test:
Tiny testable parts of a program independently tested for expected functionality.
TDD(Test-Driven Development):
Process that uses unit tests to drive the design of software.


人工手動測試(未導入單元(自動化)測試時傳統驗證作業流程)
每次要驗證某一頁網站表單程式當中的某一個function或event邏輯
都要不停重複執行某些前置動作 
=>Launch App
=>Login 
=>Navigate
=>Fill out Form 
=>Submit
=>Verify the result



軟體修復調整成本與時間關係



Ref:https://devmethodologies.blogspot.com/2013/10/change.html


手動測試(未導自動化單元測試)vs單元(自動化)測試

情境:
某個Function因為不同時間點演進而增減改某些code過去撰寫的背景規則不可考
通常習慣只針對新需求的Output做驗證而可能忽略以前的規格會不會因為新的一次更改而導致舊的需求又無法work只符合新的需求。
在我電腦上跑可以過怎麼在你電腦上跑就壞?


導入單元測試優點:
會明確留下過去的需求規格在測試程式中,且會自動把所有test case再次測試。
可讓開發者在需求異動情境維護成本降至最低。
新增功能時較不用擔心改壞以前的程式。
讓我們能自動每次功能更新都去不間斷的自動Review Code。


4.較好的撰寫單元測試原則

  • 一個測試只測一件事情、一個方法,以一個邏輯為單位。
  • Unit Test本身應不具備邏輯(if… else if…else,switch , loop…)。
  • 測試案例之間不應該有相依性,而應該各自獨立。
  • 不和外部資源(DB,WebService,web api,檔案系統…)相關聯,通常又被稱作「隔離測試」(Ex:即便在離線環境也要能夠測試跟api呼叫或DB存取有關的unit test)
  • F.I.R.S.T 原則
  • 3A原則(Arrange , Act , Assert)

F.I.R.S.T 原則


  1. Fast:測試執行的速度要夠快。
  2. Independent:單元測試之間各自互不影響,不會 因為測試A失敗而影響測試B。
  3. Repeatable:測試應能在任何環境下都能夠成功重複執行。
  4. Self-Validation:測試結果的輸出,能讓人分辨哪些測試通過與沒通過。
  5. Timely:撰寫測試要及時,最好是在產品開發前期就先寫(TDD的概念)

(PS:Test-Driven Development)


3A原則



Arrange:初始化目標物件、相依物件、方法參數、預期結果
Act:呼叫目標物件的方法
Assert:驗證是否符合預期(預期結果跟Act返回實際結果是否吻合)



寫單元測試的時機點

以既有系統而言:
1.經常有需求異動的功能
2.經常出現錯誤的function
3.極為複雜的邏輯
4.通常會針對public修飾的 method做測試

不適合寫單元測試的情況

以既有系統而言:
1.和DB有相關存取
2.和檔案系統有相關存取
3.和Call WebService , Web API , ASHX
4.有要在特定實體機器上綁定driver的操作或call一些特定環境指令(linux)

都較適合直接把時間投入在整合測試
也有一派專門研究怎麼去針對這些外部資源寫替身物件(也就是之後延伸出Test Double,Fake之類的派別)





5.主流測試框架的使用

以下都是在vs2019 IDE (community)測試操作



MSTest(MSTest2.0)


Visual Studo內建的單元測試工具(對VS IDE整合性最佳)
自動產生測試程式相關框架
不限Class Library的專案才可以測試

Supported platforms:
.NET 4.5.0+ - .NET Core 1.0+ (Universal Windows Apps 10+, DNX Core 5+) - ASP.NET Core 1.0+
MIT License
https://www.nuget.org/packages/MSTest.TestFramework/2.1.1/license


MSTest單元測試專案建立(.NET Framework)

假如目前你有一個現有的專案隸屬在某方案之下
Step1.可針對方案=>右鍵=>新增專案=>選擇「單元測試專案(.NET Framework)」
有一個很像酒精燒杯圖示的




選定好放置目錄跟專案名稱後按建立



就可以看到自己剛創建好的單元測試專案





MSTest單元測試專案建立(.NET Core)

選擇「MSTest測試專案」



預設選擇以.Net Core版本為主


開啟Test Explorer


從檢視下拉點選開來或 透過快捷組合鍵Ctrl+E , T







Test Explorer裡面跟方案類似一樣可包含多個不同測試專案並可自行選擇要一次全執行還是只針對特定測試專案做執行



MSTest執行測試
(TestClass/TestMethod)
預設MSTest專案會有所謂的TestClass跟TestMethod兩種annotation
TestClass就是將一個類別標記成測試類別,TestMethod則一樣是標記成測試方法的意思。
這裡假設我們就是直接要來驗證程式主進入點Main要輸出的結果為”Hello World”







(AreEqual/AreNotEqual)
比較常見的就是用於測試實際運行結果(actual)是否跟預期的輸出or回傳值(Expected)一致
在MSTest透過Assert.AreEqual(Expected, result);來進行




除了一致也提供不一致的查檢method






MSTest偵錯測試

(AreEqual/AreSame差異)

Assert.AreEqual(Expected, result); 單純只針對內容、值的比對
Assert.AreSame(Expected, result); 指兩個物件是否為相同的物件(同一塊記憶體位址)




在前面增加一段指向到Expected物件的程式
就又通過測試 result = Expected;//Copy the reference into result.
這裡執行Mstest的偵錯測試來看過程差異









可以到在執行到多加的指向同一Expected物件語句之前
兩個memory位置是不同的


MSTest的Assert執行流程

可以在一個TestMethod中使用多次Assert相關測試API(比如AreEqual),
但只要前方有任一個API測試比對失敗,緊接在後的都不繼續執行下去。(不建議這樣寫)



單一值得測試比對

兩值是否相同
Assert.AreEqual(object expected, object actual)
Assert.AreNotEqual(object expected, object actual)

兩址是否相同
Assert.AreSame(object expected, object actual)
Assert.AreNotSame(object expected, object actual)

Boolean比對
Assert.IsTrue(bool condition)
Assert.IsFalse(bool condition)

Null比對
Assert.IsNull(object value)
Assert.IsNotNull(object value)

字串類比對
StringAssert.Contains(string value, string substring)
StringAssert.StartsWith(string value, string substring)

正規表示式比對
StringAssert.Matches(string value, Regex regex)
StringAssert.DoesNotMatch(string value, Regex regex)

特定某型別比對
Assert.IsNotInstanceOfType(object value, Type type)



集合內容的測試比對


兩集合是否元素及總數皆相同(元素的順序要相同)
CollectionAssert.AreEqual(ICollection expected, ICollection actual)
CollectionAssert.AreNotEqual(ICollection expected, ICollection actual)
兩集合是否元素及總數皆相同(元素的順序不用相同)
CollectionAssert.AreEquivalent(ICollection expected, ICollection actual)
CollectionAssert.AreNotEquivalent(ICollection expected, ICollection actual)
集合A是否為集合B的子集合
CollectionAssert.IsSubsetOf(ICollection subset, ICollection superset)
CollectionAssert.IsNotSubsetOf(ICollection subset, ICollection superset)

該集合中是否都為特定某型別
CollectionAssert.AllItemsAreInstancesOfType(ICollection collection, Type expectedType)
該集合中是否都非Null
CollectionAssert.AllItemsAreNotNull(ICollection collection)
該集合中是否都為唯一值
CollectionAssert.AllItemsAreUnique(ICollection collection)
該集合是否包含特定某一元素
CollectionAssert.Contains(ICollection collection, object element)
CollectionAssert.DoesNotContain(ICollection collection, object element)


在既有專案中新增單元測試
到既有專案的受測程式區域(某個class)右鍵建立單元測試



必須是public修飾的class,method才可建立測試。




















6.虛設常式(stub)跟模擬物件(mock)、假物件(Fake)

單元測試領域中事實上最難學也最花時間的部分並不是在測試框架工具的使用
反而是在於寫出不依賴外部資源到何處都能夠直接執行的測試
一般通常我們會稱作假物件(Fake) 或者 模擬物件(mock)


目前所遇問題就是我們寫的測試直接去依賴外部的檔案系統資源




一些術語的認識
外部依賴(external dependency):
程式段落去相依外部某個物件與之互動,該外部物件無法被自己掌控,
例如:某段程式或檔案系統、系統時間、記憶體、網路(api、db connection)。
(通常對於外部依賴越強則可測試性也越弱)



System Under Test,SUT
需要被測試的屬於系統一部分的東西(也有人稱為MUT就是method為單位)

A method under test (MUT) is a method in the SUT called by the
test. The terms MUT and SUT are often used as synonyms, but normally, MUT
refers to a method while SUT refers to the whole class.



Depended On Component , DOC
SUT需要依賴的東西



測試替身(Test Double):測試替身包含了 Dummy、Mock、Fake、Stub、Spy。
當你在參閱其他文章或課程遇到這個詞其實就是一個統稱。
一般不同門派或者書籍又會再細分如下不同門派(不過主要是分成Mock , Stub兩大類)




假物件(Fake) : 用於描述一個虛設常式、模擬物件(通用名詞),對Unit Test而言,假物件都像真實物件,主要在於unit test中使用方式來作不同類型區分。
若測試會跟這個假物件驗證成功或失敗,那此時的假物件就算一種模擬物件。
虛設常式不會導致測試失敗,但模擬物件會有機會讓測試失敗。


虛設常式(stub) : 於系統中產生一個可控的替代物件,來取代外部相依的物件。
主要用於輔助對被測試的類別,並不會和UnitTest有直接互動關係,模擬外部回傳值或是回應

虛擬/模擬物件(Mock) : 可將沒有實作介面的程式碼 IL Code編譯成「替身」假物件,又被稱作虛擬物件用於模擬一些測試時難以隔離的依賴,通常用來驗證被測式物件是否如預期地呼叫(與相依物件互動是否符合預期),模擬物件在模擬一段行為過程引發的例外有機會導致Unit Test的失敗,因為會和UnitTest直接有互動。


要用虛設常式(stub)還是模擬物件(mock)?
模擬一整個檔案系統感覺成本過高…
可能用虛設常式比較容易~~~

1.找到導致受測單元無法順利執行的介面(在此非僅僅只是代表interface而是受測程式中有一童鞋做的方法、類別或依賴外部資源),這裡則是指外部Log檔案。

2.若受測的工作單元屬於直接相依該介面(這裡是直接讀檔的情境),則可在程式中添加一個中介層,藉此隱藏直接相依的行為。(這裡直接移到另一個class實作直接讀檔行為)

3.將該相依介面底層內容替換為自己可控的程式碼。



改善原本測試對於外部檔案系統依賴

藉由 虛設常式(stub)模擬外部回傳值或是回應

Step1.擷取介面將實作抽離,以便替換底層實作內容。
對既有目標受測Class檔案 Ctrl+r , Ctrl+i,額外命名IFileManager。
確定產生後,移除預設對當前Class實作interface。






擷取介面將實作抽離,以便替換底層實作內容。
額外定義class  FileManager去實作IFileManager。




Step2.將實作抽離並注入介面
把具體實抽離到FileManager,在method中注入IFileManager。


Step3.進行第二次擷取和抽離
方法擷取後產生IFileUility並移除對當前實作,額外定義FileUility去實作IFileUtility。







將IFileUtility屬性注入進來後間接呼叫CheckFileExists方法




Step4.於測試專案下新增Fake目錄並新增Fake物件去實作IFileUtility,在此直接返回true(模擬檔案是存在的情境)






Step5.新增另一支測試方法並將FileManager注入然後設定剛建立的Fake物件到FileManager當中去替換檔案資源。






透過Stub來做Unit Test 可不理會檔案是否存在的問題 將關注點放於該段流程





NUnit3

Open source 需額外透過nuget下載安裝相應test framework的dll

會需要額外安裝VS插件 (以下兩組安裝順序不重要各有各自用途)
  1. Test Generator Nunit extension:讓你在右鍵新增unit test專案時候可以選Nunit
  2. Test Adapter:可以直接在Visual Studio 中執行 NUnit 測試







NUnit單元測試專案建立 (.NET Framework/.Net Core)

新增「NUnit測試專案」




可選擇的版本




NUnit單元測試專案建立(.net5.0)



不安裝Test Generator Nunit extension這套VSIX(NUnit插件)前操作方式


在既有專案中新增單元測試(右鍵方式)






補安裝Test Generator Nunit extension VSIX插件






已安裝Test Generator Nunit extension這套VSIX(NUnit插件)後操作方式





在MSTest是
用TestClass將一個類別標記成測試類別
用TestMethod標記成測試方法

在Nunit則是
用TestFixture將一個類別標記成測試類別
用Test標記成測試方法



Nunit 3 Test Adapter視狀況需不需要安裝
這裡測試透過建立新的NUnit測試專案
跟既有專案中新增單元測試(右鍵方式)
兩個目前在vs2019都會自動配置Test Adapter不用額外安裝也可直接在vs執行測試




Visual Studio 2017 或早期版可能需要安裝才能直接在visual studio上執行Nunit的測試
相關案例經驗:


Test Adapter安裝方式
















NUnit跟MSTest相同語法

AreEqual
AreNotEqual
AreSame
AreNotSame
IsTrue
IsFalse















留言

這個網誌中的熱門文章

何謂淨重(Net Weight)、皮重(Tare Weight)與毛重(Gross Weight)

Architecture(架構) 和 Framework(框架) 有何不同?_軟體設計前的事前規劃的藍圖概念

經得起原始碼資安弱點掃描的程式設計習慣培養(五)_Missing HSTS Header