Asp.net-Mvc

儲存庫模式和單元測試 ASP.NET Web API

  • January 26, 2013

我剛剛開始深入研究單元測試,並且剛剛開始掌握儲存庫模式和 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);
}

現在我們在同一頁面上(並且隨時指出任何“程式碼氣味”),這是我的想法:

  1. 假儲存庫要求我重新實現我的所有實體框架邏輯並將其更改為處理 List 對象。這是我需要調試的更多工作和鏈中的更多連結。
  2. 如果單元測試確實通過了,它不會說明我的訪問 EF 的程式碼,所以我的應用程序仍然可能失敗。這只是意味著我需要單獨測試我的 EF 程式碼並讓它訪問數據庫。
  3. 從 #1 開始,如果單元測試沒有測試 EF 程式碼,那麼它只是在我的控制器中處理我的身份驗證、授權和使用者創建程式碼。它不能,因為 WebSecurity 和 Roles 類訪問了我的數據庫。
  4. 如果我要使用數據庫來測試 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 內容與控制器中的業務邏輯分離。

引用自:https://stackoverflow.com/questions/14531888