Mar*_*cke 5 c# unit-testing visual-studio
我对单元测试还很陌生,希望在服务不可用时进行模拟/测试,以确保抛出正确的错误。
在 C# 中通过 LDAP/DirectorySearcher 查询 Active Directory 用户帐户的 REST API。我看到三种可能的结果:找到用户、未找到用户和服务不可用 (DirectorySearcher)。我为此设置了三个测试,但一个总是失败,这取决于我是否连接到域。连接后,测试#1、#2 成功。当断开连接时,测试#2,#3 成功。由于 DirectoryServices 库已经稳固,我的测试是否过大?我的目的是确保 Web 服务器在失去查询 Active Directory 的能力时抛出异常。
using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Web.Http;
namespace IdentitiesApi.Controllers
{
public class UsersController : ApiController
{
// GET api/users/?username=admin
public SearchResult Get([FromUri]string userName)
{
using (var searcher = new DirectorySearcher())
{
searcher.Filter = string.Format("(&(objectClass=user)(sAMAccountName={0}))", userName);
try
{
SearchResult user = searcher.FindOne();
if (user == null)
{
var response = new HttpResponseMessage(HttpStatusCode.NotFound)
{
Content = new StringContent(string.Format("No user with username = \"{0}\" found.", userName)),
ReasonPhrase = "User Not Found"
};
throw new HttpResponseException(response);
}
else
{
return user;
}
}
catch (COMException)
{
var response = new HttpResponseMessage(HttpStatusCode.ServiceUnavailable)
{
Content = new StringContent("The directory service could not be contacted. Please try again later."),
ReasonPhrase = "Directory Service Unavailable"
};
throw new HttpResponseException(response);
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
using System;
using System.DirectoryServices;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Web.Http;
using IdentitiesApi.Controllers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace IdentitiesApi.Test
{
[TestClass]
public class UsersTest
{
[TestMethod]
public void Single_AD_User()
{
// arrange
var controller = new UsersController();
SearchResult searchResult;
string userName = "admin"; // existing user name
string expected = "admin";
string actual = "";
// act
searchResult = controller.Get(userName);
// assert
foreach (object value in searchResult.Properties["samAccountName"])
{
actual = value.ToString();
}
Assert.AreEqual(expected, actual);
}
[TestMethod]
[ExpectedException(typeof(HttpResponseException))]
public void AD_User_Not_Found_Exception()
{
// arrange
var controller = new UsersController();
SearchResult searchResult;
string userName = "?"; // invalid user name
// act
try
{
searchResult = controller.Get(userName);
}
catch (HttpResponseException ex)
{
// assert
Assert.AreEqual(HttpStatusCode.NotFound, ex.Response.StatusCode);
throw;
}
}
[TestMethod]
[ExpectedException(typeof(HttpResponseException))]
public void AD_Service_Unavailable_Exception()
{
// arrange
var controller = new UsersController();
SearchResult searchResult;
string userName = "admin";
// act
searchResult = controller.Get(userName);
}
}
}
Run Code Online (Sandbox Code Playgroud)
测试此类内容的最佳方法是对 DirectorySearcher 使用依赖项注入,然后在单元测试中使用模拟。
看起来有一个IDirectorySearcher接口,虽然我不知道 DirectorySearcher 是否实现了它。无论如何,这可能超出您的要求,这就是我的建议:
保持您的控制器轻便。现在,您的操作中有大量不可重用的业务逻辑。您正在捕获 COM 异常,并且您的控制器“了解”低级 AD 工作原理。相反,我会编写一个包装器来处理这个问题,并抛出一个通用异常。您可以避免大量重复代码(两个异常都会额外抛出),并且如果您更改使用 AD 的方式,则可以在一个地方完成它。
将包装器注入到您的控制器中。这将让您模拟该服务,以便您可以通过您的操作测试所有不同的路径。
重写控制器后:
public class UsersController : ApiController
{
private IDirectorySearcher _searcher;
public UsersController(IDirectorySearcher searcher)
{
_searcher = searcher;
}
// GET api/users/?username=admin
public SearchResult Get([FromUri]string userName)
{
try
{
return _searcher.FindSAMAccountName(userName);
}
catch (ADException ex)
{
var response = new HttpResponseMessage(HttpStatusCode.NotFound)
{
Content = ex.Content,
ReasonPhrase = ex.Reason
};
throw new HttpResponseException(response);
}
}
}
Run Code Online (Sandbox Code Playgroud)
然后你的单元测试(在这种情况下,我使用最小起订量作为我的模拟库):
[TestMethod]
[ExpectedException(typeof(HttpResponseException))]
public void AD_User_Not_Found_Exception()
{
var searcher = new Mock<IDirectorySearcher>();
searcher.Setup(x => x.FindSAMAccountName(It.IsAny<string>()).Throws(new ADException());
var controller = new UsersController(searcher.Object);
try
{
SearchResult searchResult = controller.Get("doesn't matter. any argument throws");
}
catch (HttpResponseException ex)
{
// assert
Assert.AreEqual(HttpStatusCode.NotFound, ex.Response.StatusCode);
throw;
}
}
Run Code Online (Sandbox Code Playgroud)
使用模拟的美妙之处在于,对于每个单元测试,您可以更改 Setup() 调用以返回您想要的任何内容。它可以返回 SearchResult,或者抛出异常,或者什么都不做。你甚至可以使用
searcher.Verify(x => x.FindSAMAccountName(It.IsAny<string>()), Times.Once())
Run Code Online (Sandbox Code Playgroud)
验证调用恰好发生了 1 次(或者没有,或者其他什么)。
这可能比您要求的要多,但一般来说,每层越不复杂,每层就越容易进行单元测试。
| 归档时间: |
|
| 查看次数: |
3032 次 |
| 最近记录: |