C# 太多的接口和包装?
我慢慢地开始掌握单元测试和模拟的窍门,但这是一个缓慢的过程。我尝试过对这个Active Directory代码进行单元测试。这个问题与广告没有严格的关系C# 太多的接口和包装?,c#,unit-testing,mocking,moq,C#,Unit Testing,Mocking,Moq,我慢慢地开始掌握单元测试和模拟的窍门,但这是一个缓慢的过程。我尝试过对这个Active Directory代码进行单元测试。这个问题与广告没有严格的关系 class ActiveDirectoryQueryer { DirectorySearcher mSearcher; public ActiveDirectoryQueryer() { var searcher = new DirectorySearcher(...); } public voi
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类型生成接口和包装,并将上面的许多引用改为接口(如IDirectoryEntry
)。下面的每个ixxx
接口都有一个关联的包装类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);
}
[TestMethod]
public void TestGetAllMailEntries(){
var mockSearcher=new Mock();
var mockResultCollection=new Mock();
var mockSearchResult=new Mock();
var mockDirectoryEntry=new Mock();
var mockPropertyCollection=new Mock();
var nameMockPropertyValueCollection=new Mock();
var mailMockPropertyValueCollection=new Mock();
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”])。返回(nameMockPropertyValueCollection.Object);
mockPropertyCollection.SetupGet(pc=>pc[“mail”])。返回(mailMockPropertyValueCollection.Object);
SetupGet(de=>de.Properties).Returns(mockPropertyCollection.Object);
mockSearchResult.Setup(sr=>sr.GetDirectoryEntry())。返回(mockDirectoryEntry.Object);
mockResultCollection.Setup(results=>results.GetEnumerator())。返回(新列表{mockSearchResult.Object}.GetEnumerator());
Setup(searcher=>searcher.FindAll()).Returns(mockResultCollection.Object);
var queryer=new-ActiveDirectoryQueryer(mockSearcher.Object);
GetAllMailEntries();
AreEqual(1,queryer.MailEntries.Count());
var entry=queryer.MailEntries.Single();
Assert.AreEqual(name,entry.name);
Assert.AreEqual(mailAddress、entry.EmailAddress);
}
有这么多接口和包装类是正常的吗?(包装器是必要的,因为.NET类型无法以其他方式实现我的接口。)我认为我的问题是镜像.NET结构太接近。我不应该把每一个.NET类型都包装起来,直到我得到了原语。相反,我应该利用第一次机会尽快删除所有依赖项。在本例中,它使用
DirectorySearcher
类和FindAll
方法
DirectorySearcher.FindAll
返回一个SearchResultCollection
,但是我应该更多地利用它,而不是将我的“wrapper”类看作是.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类型的包装器,我现在可以使用这个接口返回我想要的任何东西。IE:如果我想从FindAll
方法中得到的是一堆ActiveDirectoryUser
s,那么就返回它
然后,我的代码变成:
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();
}
}
单元测试变成:
[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);
}
[TestMethod]
public void TestGetAllMailEntries2(){
var mockSearcher=new Mock();
模拟搜索者
.Setup(s=>s.FindAll())
.返回(新[]{
创建(新Guid(),“名称”,“电子邮件地址”)
});
var queryer=new-ActiveDirectoryQueryer(mockSearcher.Object);
GetAllMailEntries();
AreEqual(1,queryer.MailEntries.Count());
var entry=queryer.MailEntries.Single();
Assert.AreEqual(“Name”,entry.Name);
Assert.AreEqual(“EmailAddress”,entry.EmailAddress);
}
我简要回顾了您的设置,以下内容很快让我印象深刻。而不是静态的“BuildNewADUser”函数。把它放到一个新的服务(一个接口)中,并提供它所需要的。例如,IActiveDirectoryUserFactory.Create(Guid id、字符串名称、字符串电子邮件)返回ActiveDirectoryUser。现在,只需模拟一个接口和一个方法,就可以更轻松地对初始类进行单元测试。@Atoms谢谢,我有点喜欢这个想法,但我不认为这会让我不模拟DirectorySearcher
。这看起来好多了!我想你已经把事情弄明白了。正如您自己所了解的,我上面的建议主要是关于删除外部依赖项(特别是在讨厌的静态方法中)。依赖关系很好,但如果它们都是实现内部的,那么这通常是理想的。当您在接口中公开外部依赖项时,它将需要一个实现或一个模拟。。。想想看,我到底做了什么?我把我想测试的方法移到了另一个类中,最终没有实际测试它。。。我在周三之前不会再做这方面的工作,但我不完全确定这是否更好。我提出这个建议的原因是,一个班级有那么多需要嘲笑的项目,这显然表明这个班级做得太多了。您是对的,最终,在某个地方,您可能不得不直接使用这些active directory对象。但是如果您的设计被正确地分解,那么创建单元测试就变得容易多了。换言之;类更容易测试,但要测试的类更多。根据我的经验,这一直是件好事。
[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);
}