單元測試的藝術
====
- 什麼是單元測試?
- '優秀'單元測試有哪些特質?
- 是單元測試?還是整合測試?
- 第一個單元測試
- 虛設常式
- Dependency Injection
---
- 什麼是單元測試?
>一個單元測試是一段程式呼叫另一個工作單元,並驗證工作單元的一個具體最終結果。如果對這個最終結果的假設是錯誤的,那單元測試就失敗了。一個單測試的範圍,可以小到一個方法,大到多個類別
---
單元測試的特質
- 可被自動化,重複執行
- 任何人都可以按個按鈕就執行它
- 執行速度應該很快
- 多次執行結果一致
- 每個測試都該獨立
---
想想看,如果無法滿足這些特性,我們寫的測試應該是哪一種?
---
整合測試
- 測試速度不夠快,結果較不穩定
- 測試單元中需要用到一個或多個真實相依物件
- 例如:依賴檔案系統、時間、資料庫等等
---

---
測試驅動開發(TDD)

---
- 第一個單元測試
---
```csharp=
public class LogAnalyzer
{
public book IsValidLogFilename(string filename)
{
if fileName.EndWith(".SLF")
{
return false;
}
return true;
}
}
```
---
```csharp=
public class LogAnalyzerTests
{
public void IsValidFileName_BadExtension_RetrunFalse()
{
...
}
}
```
---
- UnitOfWorkName: 被測試的方法、一組方法或一組類別
- Scenario: 假設條件,例如:「登入失敗」、「無效的使用者」
- ExpectedBehavior: 預期被測試的方法行為,例如:「回傳值」、系統狀態的改變
---
```csharp=
public void IsValidFileName_BadExtension_RetrunFalse()
{
LogAnalyzer analyzer = new LogAnalyzer();
bool result = analyzer.IsValidLogFilename("filewithbadextension.foo");
Assert.False(result);
}
```
---
增加正向測試
```csharp=
public void IsValidFileName_GoodExtension_RetrunFalse()
{
LogAnalyzer analyzer = new LogAnalyzer();
bool result = analyzer.IsValidLogFilename("filewithgoodextension.foo");
Assert.True(result);
}
```
---

---
- 虛設常式(stub)
---
虛設常式(stub)
>是在系統中產生一個可控的替代物件,來取代一個外部相依物件。你可以再測試程式中,透過虛設常式來避免必須直接相依物件所造成的問題。
---

---
```csharp=
public class FileExtensionManager IExtensionManager
{
public bool IsValid(string fileName)
{
...
}
}
public interface IExtensionManager
{
bool IsValid(string fileName);
}
public bool IsValidLogFileName(string fileName)
{
IExtensionManager mgr = new FileExtensionManager();
return mgr.IsValid(fileName);
}
```
---
Dependency Injection
- 方法的參數
- 工廠模式
- 偽造工廠類別
---
- 方法的參數
---
stub
```csharp=
public class AlwaysValidFakeExtensionManager:IExtensionManager
{
public bool IsValid(string fileName)
{
return true;
}
}
```
---
```csharp=
public class LogAnalyzer
{
private IExtensionManager manager;
public LogAnalyzer(IExtensionManager mgr)
{
manager = mgr
}
public bool IsValidLogFileName(string fileName)
{
return manager.IsValid(fileName);
}
}
```
---
```csharp=
public class LogAnalyzerTests
{
public void IsValidFileName_NameSupportExtension_RetrunTrue()
{
FakeExtensionManager myFakeMgr = new FakeExtensioManager();
myFakeMgr.WillBeValid = true;
LogAnaylzer log = new LogAnaylzer(myFakeMgr);
bool result = log.IsValidLogFileName("short.ext");
Assert.True(result);
}
}
internal class FakeExtensionManager: IExtensionManager
{
public bool WillBeValid = false;
public bool IsValid(string filename)
{
return WillBeValid;
}
}
```
---
- 工廠類別
---
```csharp=
public class LogAnalyzer
{
private IExtensionManager manager;
public LogAnalyzer()
{
manager = ExtensionManagerFactory.Create();
}
public bool IsValidLogFileName(string fileName)
{
return manager.IsValid(fileName);
}
}
```
---
```csharp=
public void IsValidFileName_SupportExtension_RetrunTrue()
{
// setup the stub to use, make sure it return true
...
ExtensionManagerFactory.SetManager(myFakeManager);
myFakeMgr.WillBeValid = true;
LogAnaylzer log = new LogAnaylzer();
...
// Assert logic...
}
```
---
```csharp=
class ExtensionManagerFactory
{
private IExtensionManagr customManager=null;
public IExtensionManagr Create()
{
If (customManager!=null)
return customManager;
return new FileExtensionManager();
}
public void SetManager(IExtensionManager mgr)
{
customManager = mgr;
}
}
```
---
- 偽造工廠類別
---
```csharp=
public class LogAnalyzerUsingFactoryMethod
{
public bool IsValidLogFileName(string filename)
{
return GetManager.IsValid(filename);
}
protected virtual IExtensionManager GetManager()
{
return new FileExtensionManager();
}
}
```
---
```csharp=
class TestableLogAnalyzer: LogAnaylzerUsingFactoryMethod
{
public TestableLogAnalyzer(IExtensionManager mgr)
{
Manger = mgr;
}
public IExtensionManager Manager;
protected override IExtensionManager GetManager()
{
return Manager;
}
}
internal class FakeExtensionManager: IExtensionManager
{
// no changes from the previous samples
// ...
}
```
---
```csharp=
public class LogAnalyzerTests
{
public void overrideTests()
{
FakeExtensionManager stub = new FakeExtensioManager();
stub.WillBeValid = true;
TestableLogAnalyzer logan = new TestableLogAnalyzer(stub);
bool result = logan.IsValidLogFileName("file.ext");
Assert.True(result);
}
}
```
---
注意:
這會讓測試程式變得更不容易理解,建議最好還是勁量避免。
---
End😎
謝謝 !!
---
{"metaMigratedAt":"2023-06-14T22:46:18.365Z","metaMigratedFrom":"Content","title":"單元測試的藝術","breaks":true,"contributors":"[{\"id\":\"ec31673e-c7c8-4b72-8b89-eeb85bc7db71\",\"add\":6461,\"del\":1189}]"}