Asp.net-Mvc
儲存庫模式和單元測試 ASP.NET Web API
我剛剛開始深入研究單元測試,並且剛剛開始掌握儲存庫模式和 IoC。然而,我不認為我完全理解它,因為它的某些部分似乎有點傻。讓我解釋。
我的控制器:
public class UserProfileController : ApiController { private IUserProfileRepository repository; // Optional constructor, passes repository, allows dependency injection public UserProfileController(IUserProfileRepository userProfileRepository) { this.repository = userProfileRepository; } // GET api/UserProfile // Returns a list of all users public IEnumerable<UserProfile> Get() { // Only Admins can see a list of users if (Roles.IsUserInRole("Admin")) { return repository.Get(); } else { throw new HttpResponseException( new HttpResponseMessage(HttpStatusCode.Forbidden) { ReasonPhrase = "Administrator access required" }); } } // Other methods, etc.(請注意,我有一個依賴項 Roles.IsUserInRole(“Admin”) 我無法弄清楚如何抽象,這會導致一些問題)。
我的典型回購界面:
public interface IUserProfileRepository : IDisposable { IEnumerable<UserProfile> Get(); // Other methods, etc. }回購:
public class UserProfileRepository : IUserProfileRepository, IDisposable { private OfootContext context; public UserProfileRepository(OfootContext context) { this.context = context; } public IEnumerable<UserProfile> Get() { return context.UserProfiles.AsEnumerable(); } // ... More code所以一切看起來都很好,我已經從我的業務邏輯中抽像出我的業務訪問層,現在我可以創建一個假儲存庫來執行單元測試。
假回購:
public class FakeUserProfileRepository : IUserProfileRepository, IDisposable { private List<UserProfile> context; public FakeUserProfileRepository(List<UserProfile> context) { this.context = context; } public IEnumerable<UserProfile> Get() { return context.AsEnumerable(); }和測試:
[TestMethod] public void GetUsers() { // Arrange var items = new List<UserProfile>() { new UserProfile { UserId = 1, Username = "Bob", }, new UserProfile { UserId = 2, Username = "Bob2", } }; FakeUserProfileRepository repo = new FakeUserProfileRepository( items); UserProfileController controller = new UserProfileController( repo); // Act IEnumerable<UserProfile> result = controller.Get(); // Assert Assert.IsNotNull(result); }現在我們在同一頁面上(並且隨時指出任何“程式碼氣味”),這是我的想法:
- 假儲存庫要求我重新實現我的所有實體框架邏輯並將其更改為處理 List 對象。這是我需要調試的更多工作和鏈中的更多連結。
- 如果單元測試確實通過了,它不會說明我的訪問 EF 的程式碼,所以我的應用程序仍然可能失敗。這只是意味著我需要單獨測試我的 EF 程式碼並讓它訪問數據庫。
- 從 #1 開始,如果單元測試沒有測試 EF 程式碼,那麼它只是在我的控制器中處理我的身份驗證、授權和使用者創建程式碼。它不能,因為 WebSecurity 和 Roles 類訪問了我的數據庫。
- 如果我要使用數據庫來測試 EF 程式碼(第 2 點)並且需要一個數據庫來測試控制器程式碼(用於身份驗證和授權,第 3 點),那麼為什麼還要費心使用儲存庫進行抽象。為什麼我不只是抽像上下文(使用 IContext 或其他東西?)並連接到使用 DropCreateDatabaseAlways 類填充的測試數據庫中?
如果我確實找到了一種將使用者帳戶垃圾抽像出來的方法,我仍然只是在改組程式碼並創建更多程式碼(甚至可能是兩倍?因為我需要創建假貨),我可以在其中替換 Context。
我的問題是:我錯過了什麼?它是一個整體概念還是特定的東西?
你在正確的軌道上。啟動和執行總是很痛苦,但你會發現它在路上得到了回報。
我建議使用像Moq這樣的框架,而不是創建“假”對象。它允許您在測試時設置所需的行為,而不是重新實現整個介面。例如,在您的測試中,您可以簡單地編寫:
Mock<IUserProfileRepository> mockUserRepo = new Mock<IUserProfileRepository>(); var items = new List<UserProfile>() { new UserProfile { UserId = 1, Username = "Bob", }, new UserProfile { UserId = 2, Username = "Bob2", } }; mockUserRepo.Setup(m => m.Get().Returns(items.AsEnumerable()); UserProfileController controller = new UserProfileController( mockUserRepo.Object); // Act IEnumerable<UserProfile> result = controller.Get(); //Now you can keep varying the mock response by changing the Setup(), so now //check for null response handling, 0 items, exceptions etc...所有這些努力的最終結果是您已經將測試完全隔離到您的控制器,沒有數據庫依賴項,您可以輕鬆更改輸入而無需編寫類,而是使用模擬設置。
如果您遵循這種簡單的架構模式,您將獲得出色的可測試性和清晰的關注點分離。隨著系統中的事情變得越來越複雜,您可以利用像 Unity 這樣的 DI 容器。
在身份驗證部分,我建議創建可以用來裝飾方法的屬性,例如 ASP.Net MVC 使用:
$$ Authorization(Roles=“Admin”) $$舉個例子。這創建了另一種有用的橫切模式,使 Auth 內容與控制器中的業務邏輯分離。