當要測驗的物件依賴另一個無法控制的物件(系統相關、第三方服務等),這個時候我們應該如何測驗?
一.問題描述
判斷檔案是否有效的需求變更了:有效的檔案擴展名存盤在檔案系統中,要測驗的FileVerify類就依賴FileExtensionManager類,在這種場景下如何測驗FileVerify類的邏輯呢?
1 public class FileVerify 2 { 3 public bool IsValidFileName(string fileName) 4 { 5 FileExtensionManager manager = new FileExtensionManager(); 7 return manager.IsValid(fileName); 8 } 9 } 10 11 public class FileExtensionManager 12 { 13 public bool IsValid(string fileName) 14 { 15 //從檔案系統中讀取檔案并判斷 16 return true; 17 } 18 }
二.破除依賴的3種解決方案
方法1.對被測驗類繼承并重寫某些行為(最簡單的一種方法 無需引入新的介面和實作類)該方法在簡單的同時也同時失去了對被被測驗代碼更多的控制空間,也就時說能做的事情是有限的
修改被測驗代碼,將IsValid方法定義為virtual,這樣子類就可以重寫該方法并決定該方法回傳的結果
1 public class FileVerify
2 {
3 public bool IsValidFileName(string fileName)
4 {
5 return IsValid(fileName);
6 }
7
8 public virtual bool IsValid(string fileName)
9 {
10 FileExtensionManager manager = new FileExtensionManager();
11 return manager.IsValid(fileName);
12 }
13 }
在測驗類中創建FileVerify的子類TestFileVerity并修改測驗類
1 internal class TestFileVerify :FileVerify
2 {
3 public bool IsSupported { get; set; }
4
5 public override bool IsValid(string fileName)
6 {
7 return IsSupported;
8 }
9 }
10
11 [TestFixture]
12 public class FileVerifyTests
13 {
14 [Test]
15 public void IsValidFileName_NameSupportedExtension_RetureTrue()
16 {
17 TestFileVerify fileVerify = new TestFileVerify();
18 fileVerify.IsSupported = true;
19
20 bool result = fileVerify.IsValidFileName("test.txt");
21
22 Assert.IsTrue(result);
23 }
24 }
注意:以下方法均需在被測驗專案中定義介面IExtensionManager,并在測驗專案中添加一個介面的實作類FakeExtensionManager,下面為具體代碼:
1 public interface IExtensionManager 2 { 3 bool IsValid(string fileName); 4 } 5 6 public class FileExtensionManager : IExtensionManager 7 { 8 public bool IsValid(string fileName) 9 { 10 //從檔案系統中讀取檔案并判斷 11 return true; 12 } 13 }
1 internal class FakeExtensionManager : IExtensionManager 2 { 3 public bool WillBeValid { get; set; } 4 5 public bool IsValid(string fileName) 6 { 7 return WillBeValid; 8 } 9 }
方法2 :繼承被測驗類并重寫方法(與方法1相比 需引入介面與測驗實作類)被測驗類代碼修改代碼如下
1 public class FileVerify
2 {
3 public bool IsValidFileName(string fileName)
4 {
5 return GetManager().IsValid(fileName);
6 }
7
8 public virtual IExtensionManager GetManager()
9 {
10 return new FileExtensionManager();
11 }
12 }
在測驗類中創建FileVerify的子類TestFileVerity并修改測驗類
1 internal class TestFileVerify :FileVerify
2 {
3 public TestFileVerify(IExtensionManager manager)
4 {
5 this.manager = manager;
6 }
7
8 private readonly IExtensionManager manager;
9
10 public override IExtensionManager GetManager()
11 {
12 return manager;
13 }
14 }
15
16 [TestFixture]
17 public class FileVerifyTests
18 {
19 [Test]
20 public void IsValidFileName_NameSupportedExtension_RetureTrue()
21 {
22 FakeExtensionManager manager = new FakeExtensionManager();
23 manager.WillBeValid = true;
24 TestFileVerify fileVerify = new TestFileVerify(manager);
25
26 bool result = fileVerify.IsValidFileName("test.txt");
27
28 Assert.IsTrue(result);
29 }
30 }
方法3:該方法的思路是被測驗類依賴于介面,不依賴于具體的實作 那么問題就轉換成如何給被測驗類傳入具體的依賴項 對于這個思路有幾個解決方案
①建構式注入
1 public class FileVerify 2 { 3 public FileVerify(IExtensionManager manager) 4 { 5 this.manager = manager; 6 } 7 8 private readonly IExtensionManager manager; 9 10 public bool IsValidFileName(string fileName) 11 { 12 return manager.IsValid(fileName); 13 } 14 }
使用建構式注入需注意:
- 在只有一個建構式的情況下,這個類的所有使用者都必須傳入依賴
- 當這個類還需其它的依賴,例如日志服務、Web服務,那么建構式中會加入更多的引數,會降低可讀性和可維護性;解決這種情況有兩種方案:①創建一個特殊類,將創建這個類所需依賴的型別作為屬性,而建構式中只有一個引數,就是這個特殊類 ②使用第三方Ioc容器來管理依賴
②屬性注入
1 public class FileVerify 2 { 3 public FileVerify() 4 { 5 manager = new FileExtensionManager(); 6 } 7 8 public IExtensionManager Manager 9 { 10 get => manager; 11 set => manager = value; 12 } 13 14 private IExtensionManager manager; 15 16 public bool IsValidFileName(string fileName) 17 { 18 return manager.IsValid(fileName); 19 } 20 }
使用屬性注入要比使用建構式注入比較簡單,每個測驗只需要設定自己需要設定的屬性
③使用工廠,從工廠類中獲得實體,在被測驗專案中創建工廠類的代碼如下:
1 /// <summary> 2 /// 擴展管理器工廠 3 /// </summary> 4 internal class ExtensionManagerFactory 5 { 6 private static IExtensionManager _manager; 7 8 public static IExtensionManager Create() 9 { 10 if(_manager != null) 11 { 12 return _manager; 13 } 14 return new FileExtensionManager(); 15 } 16 17 public static void SetManager(IExtensionManager manager) 18 { 19 _manager = manager; 20 } 21 }
被測驗類代碼如下:在建構式中通過工廠類創建默認的實體
1 public class FileVerify 2 { 3 public FileVerify() 4 { 5 manager = ExtensionManagerFactory.Create(); 6 } 7 8 private IExtensionManager manager; 9 10 public bool IsValidFileName(string fileName) 11 { 12 return manager.IsValid(fileName); 13 } 14 }
測驗代碼如下:
1 [TestFixture] 2 public class FileVerifyTests 3 { 4 [Test] 5 public void IsValidFileName_NameSupportedExtension_RetureTrue() 6 { 7 FakeExtensionManager manager = new FakeExtensionManager(); 8 manager.WillBeValid = true; 9 ExtensionManagerFactory.Create(); 10 FileVerify fileVerify = new FileVerify(); 11 12 bool result = fileVerify.IsValidFileName("test.txt"); 13 14 Assert.IsTrue(result); 15 } 16 }
方法2和方法3比方法1相對來說比較麻煩,因為引入了新的介面,新的實作,引入了工廠方法,但是這兩種方法的可控制空間比較大,例如,可以在FakeExtensionManager類中模擬例外
破除依賴的3中方法就介紹完了,如有不對之處,請指出,大家共同學習!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/450396.html
標籤:其他
下一篇:Crash Course Computer Science - 1.計算機早期歷史-Early Computing
