你应该对 abstract 类 的构造函数进行单元测试吗?如果是,如何测试?
Should you unit test constructors of abstract classes and, if so, how?
假设你有一个像这样的抽象基 class:
public abstract class WebApiServiceBase
{
public WebApiServiceBase(
HttpClient httpClient)
{
HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
}
protected HttpClient HttpClient { get; }
// For brevity, omitted some instance methods that can be used by derived classes
}
class 知道它需要构造函数中的 HttpClient,因此如果它为 null 则抛出错误。是否应该对该错误抛出功能进行单元测试?
如果是这样,最好的方法是什么,因为您不能像这样在单元测试中直接实例化抽象 class:
[TestClass()]
public class WebApiServiceBaseTests
{
[TestMethod()]
public void WebApiServiceBase_NullHttpClient_ThrowsArgumentNull()
{
//Arrange
Action action = () => {
var controller = new WebApiServiceBase(null); // Won't compile, of course, because you can't directly instantiate an abstract class
};
//Act
//Assert
Assert.ThrowsException<ArgumentNullException>(action);
}
}
我知道我可以做很多事情:
- 只测试派生的 classes 中的错误抛出功能。
- 在测试代码中发明一个新的派生class只是为了测试这个
- 首先不要class像这样的摘要。
- 可能还有一些我没有想到的其他想法。
但是我应该做什么? 最佳做法是什么?
我可能会选择你的第二个选项(有一个派生的 class 只是为了测试目的)。如果您使用来自您的域的派生 class,它可能会在以后被删除,而抽象 class 仍被其他人使用。然后测试将不再编译。因此,为这个显式测试用例设置 class 会更有意义。
这有效,但有点 dodgy.The 构造函数异常不会发生,直到您尝试访问模拟的某些 属性。它确实确认某些东西正在抛出您想要的异常。
[TestClass]
public class UnitTest1
{
[TestMethod()]
public void WebApiServiceBase_NullHttpClient_ThrowsArgumentNull()
{
var controller = new Mock<WebApiServiceBase>(MockBehavior.Loose,
//Pass null in to the constructor
null
);
var exception = Assert.ThrowsException<TargetInvocationException>(() =>
{
var httpClient = controller.Object.HttpClient;
});
Assert.IsTrue(exception.InnerException is ArgumentNullException ane && ane.ParamName == "httpClient");
}
}
但是,您不应该像这样设计 classes,因为这会使它们难以测试。最好注入所有可重用代码,而不是创建基础 class。良好的 Http 客户端抽象意味着您不需要创建基础 class。这是一个很好的抽象:
public interface IClient
{
/// <summary>
/// Sends a strongly typed request to the server and waits for a strongly typed response
/// </summary>
/// <typeparam name="TResponseBody">The expected type of the response body</typeparam>
/// <param name="request">The request that will be translated to a http request</param>
/// <returns>The response as the strong type specified by TResponseBody /></returns>
/// <typeparam name="TRequestBody"></typeparam>
Task<Response<TResponseBody>> SendAsync<TResponseBody, TRequestBody>(IRequest<TRequestBody> request);
/// <summary>
/// Default headers to be sent with http requests
/// </summary>
IHeadersCollection DefaultRequestHeaders { get; }
/// <summary>
/// Base Uri for the client. Any resources specified on requests will be relative to this.
/// </summary>
AbsoluteUrl BaseUri { get; }
}
假设你有一个像这样的抽象基 class:
public abstract class WebApiServiceBase
{
public WebApiServiceBase(
HttpClient httpClient)
{
HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
}
protected HttpClient HttpClient { get; }
// For brevity, omitted some instance methods that can be used by derived classes
}
class 知道它需要构造函数中的 HttpClient,因此如果它为 null 则抛出错误。是否应该对该错误抛出功能进行单元测试?
如果是这样,最好的方法是什么,因为您不能像这样在单元测试中直接实例化抽象 class:
[TestClass()]
public class WebApiServiceBaseTests
{
[TestMethod()]
public void WebApiServiceBase_NullHttpClient_ThrowsArgumentNull()
{
//Arrange
Action action = () => {
var controller = new WebApiServiceBase(null); // Won't compile, of course, because you can't directly instantiate an abstract class
};
//Act
//Assert
Assert.ThrowsException<ArgumentNullException>(action);
}
}
我知道我可以做很多事情:
- 只测试派生的 classes 中的错误抛出功能。
- 在测试代码中发明一个新的派生class只是为了测试这个
- 首先不要class像这样的摘要。
- 可能还有一些我没有想到的其他想法。
但是我应该做什么? 最佳做法是什么?
我可能会选择你的第二个选项(有一个派生的 class 只是为了测试目的)。如果您使用来自您的域的派生 class,它可能会在以后被删除,而抽象 class 仍被其他人使用。然后测试将不再编译。因此,为这个显式测试用例设置 class 会更有意义。
这有效,但有点 dodgy.The 构造函数异常不会发生,直到您尝试访问模拟的某些 属性。它确实确认某些东西正在抛出您想要的异常。
[TestClass]
public class UnitTest1
{
[TestMethod()]
public void WebApiServiceBase_NullHttpClient_ThrowsArgumentNull()
{
var controller = new Mock<WebApiServiceBase>(MockBehavior.Loose,
//Pass null in to the constructor
null
);
var exception = Assert.ThrowsException<TargetInvocationException>(() =>
{
var httpClient = controller.Object.HttpClient;
});
Assert.IsTrue(exception.InnerException is ArgumentNullException ane && ane.ParamName == "httpClient");
}
}
但是,您不应该像这样设计 classes,因为这会使它们难以测试。最好注入所有可重用代码,而不是创建基础 class。良好的 Http 客户端抽象意味着您不需要创建基础 class。这是一个很好的抽象:
public interface IClient
{
/// <summary>
/// Sends a strongly typed request to the server and waits for a strongly typed response
/// </summary>
/// <typeparam name="TResponseBody">The expected type of the response body</typeparam>
/// <param name="request">The request that will be translated to a http request</param>
/// <returns>The response as the strong type specified by TResponseBody /></returns>
/// <typeparam name="TRequestBody"></typeparam>
Task<Response<TResponseBody>> SendAsync<TResponseBody, TRequestBody>(IRequest<TRequestBody> request);
/// <summary>
/// Default headers to be sent with http requests
/// </summary>
IHeadersCollection DefaultRequestHeaders { get; }
/// <summary>
/// Base Uri for the client. Any resources specified on requests will be relative to this.
/// </summary>
AbsoluteUrl BaseUri { get; }
}