c# - 接口(interface)和包装器太多?

标签 c# unit-testing mocking moq

我正在慢慢开始掌握单元测试和模拟的窍门,但这是一个缓慢的过程。我已经尝试对这个 Active Directory 代码进行单元测试。这个问题与 AD 并不严格相关。

class ActiveDirectoryQueryer {    
   DirectorySearcher mSearcher;

   public ActiveDirectoryQueryer() {
      var searcher = new DirectorySearcher(...);
   }

   public void GetAllMailEntries() {
      MailEntries =
         mSearcher
         .FindAll()
         .Select(result => result.GetDirectoryEntry())
         .Select(BuildNewADUser)
         .ToList();
   }

   static ActiveDirectoryUser BuildNewADUser(DirectoryEntry pDirectoryEntry) {
      return ActiveDirectoryUser.Create(
         pDirectoryEntry.Guid,
         (pDirectoryEntry.Properties["name"].Value ?? "").ToString(),
         (pDirectoryEntry.Properties["mail"].Value ?? "").ToString()
      );
   }

所以,我想对 GetAllMailEntries 方法进行单元测试。为了使用 MOQ 执行此操作,我不得不为各种 .NET 类型手动生成接口(interface)和包装器,并将上述许多对接口(interface)的引用更改为替代(如 IDirectoryEntry)。下面的每个 IXxxx 接口(interface)都有一个关联的包装类 XxxxWrapper。总共我为这个测试添加了至少 12 个新的源文件。这是我为单元测试完成的结果:

[TestMethod]
public void TestGetAllMailEntries() {
   var mockSearcher = new Mock<IDirectorySearcher>();
   var mockResultCollection = new Mock<ISearchResultCollection>();
   var mockSearchResult = new Mock<ISearchResult>();
   var mockDirectoryEntry = new Mock<IDirectoryEntry>();
   var mockPropertyCollection = new Mock<IPropertyCollection>();
   var nameMockPropertyValueCollection = new Mock<IPropertyValueCollection>();
   var mailMockPropertyValueCollection = new Mock<IPropertyValueCollection>();

   const string name = "SomeNameValue";
   const string mailAddress = "SomeMailAddress";

   nameMockPropertyValueCollection.SetupGet(pvc => pvc.Value).Returns(name);
   mailMockPropertyValueCollection.SetupGet(pvc => pvc.Value).Returns(mailAddress);
   mockPropertyCollection.SetupGet(pc => pc["name"]).Returns(nameMockPropertyValueCollection.Object);
   mockPropertyCollection.SetupGet(pc => pc["mail"]).Returns(mailMockPropertyValueCollection.Object);
   mockDirectoryEntry.SetupGet(de => de.Properties).Returns(mockPropertyCollection.Object);
   mockSearchResult.Setup(sr => sr.GetDirectoryEntry()).Returns(mockDirectoryEntry.Object);
   mockResultCollection.Setup(results => results.GetEnumerator()).Returns(new List<ISearchResult> { mockSearchResult.Object }.GetEnumerator());
   mockSearcher.Setup(searcher => searcher.FindAll()).Returns(mockResultCollection.Object);

   var queryer = new ActiveDirectoryQueryer(mockSearcher.Object);
   queryer.GetAllMailEntries();
   Assert.AreEqual(1, queryer.MailEntries.Count());
   var entry = queryer.MailEntries.Single();
   Assert.AreEqual(name, entry.Name);
   Assert.AreEqual(mailAddress, entry.EmailAddress);
}

这么多接口(interface)和包装类正常吗? (包装器是必需的,因为 .NET 类型不能以其他方式实现我的接口(interface)。)

最佳答案

我认为我的问题是过于紧密地反射(reflect)了 .NET 结构。我不应该一直包装每一个 .NET 类型,直到我得到原始类型。相反,我应该捕获第一个机会尽快删除所有依赖项。在本例中,它使用 DirectorySearcher 类和 FindAll 方法。

DirectorySearcher.FindAll 返回一个 SearchResultCollection,但与其将我的“包装器”类视为 .NET 类型的适配器,不如更多地使用

忽略 IDisposable 的实现和其他不必要的代码,我的包装器看起来像这样:

public interface IDirectorySearcher : IDisposable {
   ISearchResultCollection FindAll();
}

class DirectorySearcherWrapper : IDirectorySearcher {
   DirectorySearcher mDirectorySearcher;

   DirectorySearcherWrapper(DirectorySearcher pDirectorySearcher) {
      mDirectorySearcher = pDirectorySearcher;
   }

   public static IDirectorySearcher Wrap(DirectorySearcher pDirectorySearcher) {
      return new DirectorySearcherWrapper(pDirectorySearcher);
   }

   public ISearchResultCollection FindAll() {
      return SearchResultCollectionWrapper.Wrap(mDirectorySearcher.FindAll());
   }
}

相反,我应该借此机会停止所有依赖项。我不必返回 .NET 类型,甚至不需要返回 .NET 类型的包装器,我现在可以使用此接口(interface)返回我想要的任何内容。 IE:如果我想从 FindAll 方法中得到的是一堆 ActiveDirectoryUser,那么只返回它。

然后我的代码变成:

public interface IDirectorySearcher : IDisposable {
   IEnumerable<ActiveDirectoryUser> FindAll();
}

class DirectorySearcherWrapper : IDirectorySearcher {
   DirectorySearcher mDirectorySearcher;

   DirectorySearcherWrapper(DirectorySearcher pDirectorySearcher) {
      mDirectorySearcher = pDirectorySearcher;
   }

   public static IDirectorySearcher Wrap(DirectorySearcher pDirectorySearcher) {
      return new DirectorySearcherWrapper(pDirectorySearcher);
   }

   public IEnumerable<ActiveDirectoryUser> FindAll() {
      return
         mDirectorySearcher
         .FindAll()
         .Cast<SearchResult>()
         .Select(result => result.GetDirectoryEntry())
         .Select(/*BuildNewADUser*/)
         .ToList();
   }
}

GetAllMailEntries 方法变得简单:

public void GetAllMailEntries() {
   MailEntries = mSearcher.FindAll();
}

单元测试变成:

[TestMethod]
public void TestGetAllMailEntries2() {
   var mockSearcher = new Mock<IDirectorySearcher>();

   mockSearcher
   .Setup(s => s.FindAll())
   .Returns(new[] {
      ActiveDirectoryUser.Create(new Guid(), "Name", "EmailAddress")
   });

   var queryer = new ActiveDirectoryQueryer(mockSearcher.Object);
   queryer.GetAllMailEntries();
   Assert.AreEqual(1, queryer.MailEntries.Count());
   var entry = queryer.MailEntries.Single();
   Assert.AreEqual("Name", entry.Name);
   Assert.AreEqual("EmailAddress", entry.EmailAddress);
}

关于c# - 接口(interface)和包装器太多?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32173645/

相关文章:

c++ - 在 gTest 中模拟 FreeRTOS 函数

c# - FakeItEasy:模拟方法未返回预期结果

c# - 将异步调用变成同步调用

mysql - Django unittest - 无法创建表

c# - 如何使用 WiX Burn MBA bundle 在 MajorUpgrade 期间检测当前安装的功能?

javascript - Sails.js:如何实际运行测试

node.js - 使用 Jasmine 和 Express 模拟请求对象

typescript - NestJS 在 e2e 测试中模拟 JWT 身份验证

c# - 如何制作从 C# "Thread-safe"调用的 C (P/invoke) 代码

c# - Razor 页面链接忽略路由参数