问题描述
我有下面的类和方法,它们将连接到数据库,但是对于测试,我不需要真正的连接,并且需要伪造它。我们正在为此使用FakeItEasy。:
public abstract class HandlerBase
{
public string errorMessage;
private MyActionsDataModel Action
{
get
{
if (_action == null)
{
_action = new MyActionsDataModel();
using (var connection = new SqlConnection(Constants.Connections.MyDatabase))
{
connection.Open();
using (var transaction = connection.BeginTransaction(IsolationLevel.Serializable))
{
_action.Id = connection.QuerySingle<int>("UpdateAction",transaction: transaction,commandType: CommandType.StoredProcedure,param: Action);
transaction.Commit();
}
}
}
return _action;
}
}
private MyActionsDataModel _action;
public void RecordFailure(AggregateException ex)
{
Console.WriteLine("A failure happened:");
Console.WriteLine(JsonConvert.SerializeObject(ex));
errorMessage = "Inner Exception\r\n" + ex.Message;
Action.ErrorOccurredOnUtc = DateTimeOffset.UtcNow;
Action.ErrorType = ex.GetType().FullName;
Action.ErrorMessage = errorMessage;
SaveAction();
}
private void SaveAction()
{
using (var connection = new SqlConnection(Constants.Connections.MyDatabase))
{
connection.Open();
using (var transaction = connection.BeginTransaction(IsolationLevel.Serializable))
{
connection.Execute("UpdateAction",param: Action);
transaction.Commit();
}
}
}
}
我将在测试中调用的另一个类:
public class MyHandlerType : HandlerBase
{
private readonly MyTracker _myTracker;
public MyHandlerType(MyTracker myTracker) : base()
{
_myTracker = myTracker;
}
}
我想要的是伪造 Action 参数以及 SaveAction 方法。
这是我要进行的测试,但不确定如何制作假部件。
public class HandlerTests
{
[TestCase]
public void Test_RecordFailure()
{
var innerMessage = "Throw AppException for UnitTest.";
var parentMessage = "Throw AggregationException for UnitTest.";
var testHandler = new MyHandlerType(null);
var innerException = new ApplicationException(innerMessage);
var parentException = new AggregateException(parentMessage,innerException);
testHandler.RecordFailure(parentException);
var includeInnerMessage = testHandler.errorMessage.Contains(innerMessage);
var includeParentMessage = testHandler.errorMessage.Contains(parentMessage);
Assert.IsTrue(includeInnerMessage);
Assert.IsTrue(includeParentMessage);
}
}
解决方法
当前类与实现方面的考虑紧密相关,这使得孤立地对其进行测试变得困难。
考虑重构类
public abstract class HandlerBase {
private readonly Lazy<MyActionsDataModel> model;
private readonly IDbConnectionFactory connectionFactory;
protected HandlerBase(IDbConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
model = new Lazy<MyActionsDataModel>(() => {
MyActionsDataModel action = new MyActionsDataModel();
using (DbConnection connection = this.connectionFactory.Create()) {
connection.Open();
using (DbTransaction transaction = connection.BeginTransaction(IsolationLevel.Serializable)) {
action.Id = connection.QuerySingle<int>("UpdateAction",transaction: transaction,commandType: CommandType.StoredProcedure,param: action);
transaction.Commit();
}
}
return action;
});
}
public string ErrorMessage;
public void RecordFailure(AggregateException ex) {
Console.WriteLine("A failure happened:");
Console.WriteLine(JsonConvert.SerializeObject(ex));
ErrorMessage = "Inner Exception\r\n" + ex.Message;
MyActionsDataModel action = model.Value;
action.ErrorOccurredOnUtc = DateTimeOffset.UtcNow;
action.ErrorType = ex.GetType().FullName;
action.ErrorMessage = ErrorMessage;
saveAction(action);
}
private void saveAction(MyActionsDataModel action) {
using (DbConnection connection = connectionFactory.Create()) {
connection.Open();
using (DbTransaction transaction = connection.BeginTransaction(IsolationLevel.Serializable)) {
connection.Execute("UpdateAction",param: action);
transaction.Commit();
}
}
}
}
请注意引入显式依赖项
public interface IDbConnectionFactory {
DbConnection Create();
}
可以实现的
// Connection Factory method
public DbConnection Create() {
DbConnection connection = new SqlConnection(Constants.Connections.MyDatabase);
return connection;
}
在进行测试时,锻炼被测对象时可以嘲笑出厂时的行为。
,这是我的新代码(基于@Nkosi的建议)和测试部分:
代码:
public abstract class HandlerBase {
private readonly Lazy<MyActionsDataModel> model;
private readonly IDbConnectionFactory connectionFactory;
protected HandlerBase(IDbConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
model = new Lazy<MyActionsDataModel>(() => {
MyActionsDataModel action = new MyActionsDataModel();
using (DbConnection connection = this.connectionFactory.Create()) {
connection.Open();
using (DbTransaction transaction = connection.BeginTransaction(IsolationLevel.Serializable)) {
action.Id = connection.QuerySingle<int>("UpdateAction",param: action);
transaction.Commit();
}
}
return action;
});
}
public string ErrorMessage;
public void RecordFailure(AggregateException ex) {
Console.WriteLine("A failure happened:");
Console.WriteLine(JsonConvert.SerializeObject(ex));
ErrorMessage = "Inner Exception\r\n" + ex.Message;
MyActionsDataModel action = model.Value;
action.ErrorOccurredOnUtc = DateTimeOffset.UtcNow;
action.ErrorType = ex.GetType().FullName;
action.ErrorMessage = ErrorMessage;
saveAction(action);
}
private void saveAction(MyActionsDataModel action) {
using (DbConnection connection = new SqlConnection(Constants.Connections.MyDatabase)) {
connection.Open();
using (DbTransaction transaction = connection.BeginTransaction(IsolationLevel.Serializable)) {
connection.Execute("UpdateAction",param: action);
transaction.Commit();
}
}
}
}
public interface IDbConnectionFactory {
DbConnection Create();
}
// Connection Factory method
public DbConnection Create() {
DbConnection connection = new SqlConnection(Constants.Connections.MyDatabase);
return connection;
}
public class MyHandlerType : HandlerBase
{
private readonly IDbConnectionFactory _connectionFactory;
public MyHandlerType(IDbConnectionFactory connectionFactory) : base(connectionFactory)
{
_connectionFactory = connectionFactory;
}
}
测试:
public class HandlerTests
{
protected MyHandlerType _subjectUnderTest;
protected HandlerBase.IDbConnectionFactory _fakeConnectionFactory;
[SetUp]
public void Setup()
{
_fakeConnectionFactory = A.Fake<HandlerBase.IDbConnectionFactory>();
A.CallTo(() => _fakeConnectionFactory.Create()).Returns<DbConnection>(new SqlConnection(Constants.Connections.MyDatabase));
_subjectUnderTest = new MyHandlerType(_fakeConnectionFactory);
}
[TestCase]
public void Test_RecordFailure()
{
var innerMessage = "Throw AppException for UnitTest.";
var parentMessage = "Throw AggregationException for UnitTest.";
var innerException = new ApplicationException(innerMessage);
var parentException = new AggregateException(parentMessage,innerException);
_subjectUnderTest.RecordFailure(parentException);
var includeInnerMessage = testHandler.errorMessage.Contains(innerMessage);
var includeParentMessage = testHandler.errorMessage.Contains(parentMessage);
Assert.IsTrue(includeInnerMessage);
Assert.IsTrue(includeParentMessage);
}
}