我有一个类,它内部使用外部定义的接口。测试这个类变得很困难,因为我需要模拟这个接口,但是我需要引用外部库(这可能相当大),因为测试是在一个单独的测试项目中。有没有任何方法可以将这个接口解耦,这样我就可以模拟这个接口并测试它,而不必包含外部项目,或者它是如何设置我的解决方案的一个继承问题?
发布于 2017-12-22 19:48:39
Bob的文章清洁建筑建议您的类的依赖项应该指向内部,而不是向外。
使此体系结构工作的压倒规则是依赖规则。这条规则规定,源代码依赖只能指向内部。在一个内部圈里,任何东西都不可能知道外圈中的任何东西。特别是,在外圆圈中声明的某物的名称不能在内圆圈中的代码中提及。包括函数类。变量,或任何其他命名的软件实体。
跟着这封信,这意味着你的内在库甚至根本不会引用外部库。相反,它将定义自己的接口,然后额外的库将创建组合,将您的内部类与它所依赖的接口的实现结合起来。
)我从来没见过这样的事情。我并不是嘲笑它不实际。这听起来很棒,我想在死前大规模地实现它。)
在较小的范围内,您可以从确保类依赖于定义的接口而不是外部定义的接口中获得一些好处。
使用一个非常简单的示例,您可以定义自己的日志记录接口,如
public interface ILogger
{
void LogException(Exception ex);
}现在,如果一个类需要记录异常,它只依赖于该接口,而不依赖于Microsoft接口或温莎接口,或者其他什么。然后,如果使用外部日志库,则创建一个ILogger实现,该实现包装对该库的调用。
最棒的是,现在你实现了界面隔离。您的类不太可能依赖于世界上提供给它们的任何巨大接口,而更可能依赖于提供该类所需内容的小型接口。
然后,回到原来的问题,这些接口将很容易模拟。它们甚至可能非常小,以至于您发现使用简单的测试双类而不是模拟框架更容易。我喜欢使用Moq,但有时所有这些设置都很难读懂。如果我能像PermissionsValidatorThatAlwaysReturnsTrue那样轻松地编写一个类,那么我可以继续编写几行额外的代码。当有人看到测试中使用的类,导航它并看到它所做的事情时,可能会节省时间,而不是试图找出使用的是哪个Mock,以及它是如何设置的。
发布于 2017-12-18 14:57:17
也许您可以使用诸如MEF (托管扩展框架)这样的插件框架来解耦DLL/Project。但正如爱默生所言,创建一个包装器可能更可行。
发布于 2017-12-22 20:36:17
最好的答案是引用包含接口的程序集。当然,它很大,但这样做不会触发任何额外的程序集加载,因为外部库无论如何都必须加载,这样才能运行被测试的类。
但是,如果出于某种原因,你真的不想设置那个引用,那就可以做你想做的事情了。下面是一个示例,它支持正在测试的方法,该方法需要一个符合非引用接口的参数。
首先,添加此扩展方法:
public static class ExtensionMethods
{
public static void CallWithMockedParameter(this object objectUnderTest, string methodName, object valuesForMockedParameter)
{
var typeUnderTest = objectUnderTest.GetType();
var methodUnderTest = typeUnderTest.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance);
var parameterUnderTest = methodUnderTest.GetParameters()[0];
var parameterType = parameterUnderTest.ParameterType;
var typeToPass = parameterType.Assembly.GetTypes().Where( a => parameterType.IsAssignableFrom(a) && a.IsClass).Single();
var objectToPass = Activator.CreateInstance(typeToPass);
foreach (var propertyToCopy in valuesForMockedParameter.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
typeToPass.GetProperty(propertyToCopy.Name).SetValue(objectToPass, propertyToCopy.GetValue(valuesForMockedParameter));
}
methodUnderTest.Invoke(objectUnderTest, new[] { objectToPass });
}
}这段代码查找被测试类的类型,找到感兴趣的方法,识别其参数的类型,并找到与参数接口匹配的具体类。然后,它实例化具体的类,并通过逐个映射属性,用匿名类型中提供的值填充它。
使用上面的内容,您可以替换这个正常的调用:
//Normal execution
ClassUnderTest o1 = new ClassUnderTest();
IExternalInterface a1 = new ExternalClass { Text = "Normal" };
o1.MethodUnderTest(a1);在这方面:
//Mocked
var o2 = new ClassUnderTest();
var a2 = new { Text = "Mocked" };
o2.CallWithMockedParameter(nameof(o2.MethodUnderTest), a2);看看我的DotNetFiddle上的工作实例。
https://softwareengineering.stackexchange.com/questions/362622
复制相似问题