我正在尝试通过测试来涵盖以下课程:
public sealed class VdiRunHistoryDataManager : IVdiRunHistoryDataManager
{
private const string InvalidValueErrorMessage = "Invalid value";
private readonly IDbConnectionWrapper _connection;
public VdiRunHistoryDataManager(IDbConnectionWrapper connection)
{
_connection = connection;
}
public DateTime GetVdiLastRunDateTime(string vdiRunTypeCode, string vdiRunScopeCode, int operatorId)
{
if (!IsValidVdiRunTypeCode(vdiRunTypeCode))
{
throw new ArgumentException(InvalidValueErrorMessage, nameof(vdiRunTypeCode));
}
if (!IsValidVdiRunScopeCode(vdiRunScopeCode))
{
throw new ArgumentException(InvalidValueErrorMessage, nameof(vdiRunScopeCode));
}
var vdiRunScope = GetVdiRunScopeByCode(vdiRunScopeCode);
var vdiRunType = GetVdiRunTypeByCode(vdiRunTypeCode);
var vdiRunHistory = FindVdiRunHistory(operatorId, vdiRunType.Id, vdiRunScope.Id);
var longTimeAgo = DateTime.UtcNow.AddYears(-100);
var lastRun = vdiRunHistory?.LastRun ?? longTimeAgo;
return lastRun;
}
public void SaveVdiRunHistory(VdiRunHistoryDataModel historyDataModel)
{
if (historyDataModel == null)
{
throw new ArgumentNullException(nameof(historyDataModel));
}
if (!IsValidVdiRunTypeCode(historyDataModel.VdiRunTypeCode))
{
throw new ArgumentException(InvalidValueErrorMessage, nameof(historyDataModel.VdiRunTypeCode));
}
if (!IsValidVdiRunScopeCode(historyDataModel.VdiRunScopeCode))
{
throw new ArgumentException(InvalidValueErrorMessage, nameof(historyDataModel.VdiRunScopeCode));
}
var vdiRunType = GetVdiRunTypeByCode(historyDataModel.VdiRunTypeCode);
var vdiRunScope = GetVdiRunScopeByCode(historyDataModel.VdiRunScopeCode);
var historyInternalModel = FindVdiRunHistory(historyDataModel.OperatorId, vdiRunType.Id, vdiRunScope.Id) ??
CreateVdiRunHistoryInternalModel(historyDataModel.OperatorId, vdiRunType.Id,
vdiRunScope.Id);
historyInternalModel.LastRun = historyDataModel.LastRun;
_connection.Save(historyInternalModel);
}
private VdiRunScopeLookupInternalDataModel GetVdiRunScopeByCode(string code)
{
var runScope = _connection.SingleOrDefault<VdiRunScopeLookupInternalDataModel>(p => p.Code == code);
ThrowInvalidOperationExceptionIfNull(runScope, CreateNotFoundErrorMessage("VDIRunScope", code));
return runScope;
}
private VdiRunTypeLookupInternalDataModel GetVdiRunTypeByCode(string code)
{
var runType = _connection.SingleOrDefault<VdiRunTypeLookupInternalDataModel>(p => p.Code == code);
ThrowInvalidOperationExceptionIfNull(runType, CreateNotFoundErrorMessage("VDIRunType", code));
return runType;
}
[AssertionMethod]
private static void ThrowInvalidOperationExceptionIfNull<T>(T instance, string exceptionMessage) where T : class
{
if (instance == null)
{
throw new InvalidOperationException(exceptionMessage);
}
}
private static string CreateNotFoundErrorMessage(string tableName, string code)
{
var errorMessage = $"{tableName} does not contain required record with field Code={code}";
return errorMessage;
}
private static bool IsValidVdiRunScopeCode(string vdiRunScopeCode)
{
return vdiRunScopeCode == VdiRunScopeCodeDataModel.Full || vdiRunScopeCode == VdiRunScopeCodeDataModel.Partial;
}
private static bool IsValidVdiRunTypeCode(string vdiRunTypeCode)
{
return vdiRunTypeCode == VdiRunTypeCodeDataModel.Products;
}
private static VdiRunHistoryInternalDataModel CreateVdiRunHistoryInternalModel(int operatorId, int vdiRunTypeId, int vdiRunScopeId)
{
return new VdiRunHistoryInternalDataModel
{
OperatorId = operatorId,
VdiRunTypeId = vdiRunTypeId,
VdiRunScopeId = vdiRunScopeId
};
}
private VdiRunHistoryInternalDataModel FindVdiRunHistory(int operatorId, int vdiRunTypeId, int vdiRunScopeId)
{
var foundVdiRunHistory = _connection.SingleOrDefault<VdiRunHistoryInternalDataModel>(p =>
p.OperatorId == operatorId &&
p.VdiRunTypeId == vdiRunTypeId &&
p.VdiRunScopeId == vdiRunScopeId);
return foundVdiRunHistory;
}
}
有一个IDbConnectionWrapper注入数据管理器:
public interface IDbConnectionWrapper
{
void ExecuteSql(string sql);
IReadOnlyCollection<T> Query<T>(string sql, object anonymousType);
T SingleOrDefault<T>(Expression<Func<T, bool>> predicate);
void Save<T>(T instance) where T : new();
}
我使用Moq库来编写测试。问题是模型(类)*InternalDataModels是非公开的“原始”模型,不应在被测程序集之外可见。正是在这些内部模型上SingleOrDefault<T> 调用方法和其他方法,这些是中间模型,然后用于获得最终结果。当试图锁定这些方法时,会出现一个逻辑错误,即模型不可用于被测组件。
问题:在这种情况下使用程序集的InternalVisibleTo属性好不好?还是有其他方法可以在不使用此属性的情况下编写测试?有谁知道如何重构此代码以使其可测试?
这就是属性
InternalVisibleTo的用途。所以大胆地使用它。有两种类型的隔离框架:受约束的和不受约束的。
Moq 以及 NSubstitute 和 FakeItEasy 是有限的。它们允许您仅锁定公共类型和成员。
无限的包括 TypeMock Isolator、JustMock(均付费)和 Microsoft Fakes(包含在 Visual Studio Enterprise 中,不在社区版甚至专业版中)。
后者允许您锁定任何东西:隐藏和内部、静态和密封类型和成员。在某种程度上,这使测试更容易,但会损坏,因此,您可能会滑入一个糟糕的架构,而没有明确划分范围等。