我正在尝试对使用API的代码进行单元测试,所以我试图解耦。
我为"应用程序"创建了一个界面。 API中的类,它是密封的。
然后我创建了一个使用该接口的类,该接口有一个返回" Application"类型对象。
这是我遇到麻烦的地方,在我的单元测试中,我尝试创建一个"应用程序" object来验证返回值是否正确。但是"应用程序" class没有任何构造函数,没有公共或私有(我用反射检查)。该对象是通过调用静态Application.Connect(AnotherTypeFromAPI arg)创建的,它返回一个Application对象。
如何返回无法创建的虚假对象?
appMock.Connect(arg).Returns("How do I return an Application object here?"));
或者我是否在依赖API的单元测试代码方面做错了?整个API依赖于"应用程序"如果我不能伪造它,我不知道我怎么能存根或嘲笑我需要的其他方法。
我正在使用C#,NUnit,NSUbstitute。
答案 0 :(得分:2)
这个问题可以解决,但你使用的是错误的模式。您需要创建一个完全替换具体依赖关系的接口,而不是通过新接口公开Application实例。
如果我正确理解你的问题,你有一个密封的Application类,它有一些你的程序需要能够调用的方法,它没有公共构造函数,只有一个静态工厂方法。这是一个简单的讨论示例,只有一种方法SomeMethod()
。
public sealed class Application
{
//private ctor prevents anyone from using new to create this
private Application()
{
}
//Here's the method we want to mock
public void SomeMethod(string input)
{
//Implementation that needs to be stubbed or mocked away for testing purposes
}
//Static factory method
static public Application GetInstance()
{
return new Application();
}
}
你做的可能是这样的:
interface IApplication
{
Application Application { get; }
}
class ApplicationWrapper : IApplication
{
protected readonly Application _application;
public ApplicationWrapper()
{
_application = Application.GetInstance();
}
public Application Application
{
get { return _application; }
}
}
所以在你的主代码中,你这样做:
var a = new ApplicationWrapper();
a.Application.SomeMethod("Real argument");
这种方法永远不会用于单元测试,因为您仍然直接依赖于密封的Application类。你刚刚搬了它。你仍然需要调用Application.SomeMethod()
,这是一个具体的方法;你应该只依赖于界面,而不是任何具体的东西。
理论上,“正确”的方法是包装所有内容。因此,不要将Application
作为属性公开,而是将其保密;相反,您公开方法的包装版本,如下所示:
public interface IApplication
{
void SomeMethod(string input);
}
public class ApplicationWrapper : IApplication
{
protected readonly Application _application;
public ApplicationWrapper()
{
_application = Application.GetInstance();
}
public void SomeMethod(string input)
{
_application.SomeMethod(input);
}
}
然后你会这样称呼它:
var a = new ApplicationWrapper();
a.SomeMethod("Real argument");
或者在DI的完整课程中,它看起来像这样:
class ClassUnderTest
{
protected readonly IApplication _application; //Injected
public ClassUnderTest(IApplication application)
{
_application = application; //constructor injection
}
public void MethodUnderTest()
{
_application.SomeMethod("Real argument");
}
}
在单元测试中,您现在可以使用新类来模拟IApplication,例如
class ApplicationStub : IApplication
{
public string TestResult { get; set; } //Doesn't exist in system under test
public void SomeMethod(string input)
{
this.TestResult = input;
}
}
请注意,此类完全不依赖于Application。所以你不再需要在它上面调用new
,或者根本不需要调用它的工厂方法。对于目的的单元测试,您只需要确保它被正确调用。您可以通过传入存根并在之后检查TestResult
来执行此操作:
//Arrange
var stub = new ApplicationStub();
var c = ClassUnderTest(stub);
//Act
c.MethodUnderTest("Test Argument");
//Assert
Assert.AreEqual(stub.TestResult, "Test Argument");
编写完整的包装器需要做更多的工作(特别是如果它有很多方法),但你可以使用反射或第三方工具生成大量代码。它允许您进行完整的单元测试,这是开始切换到IApplication
接口背后的整个想法。
而不是
IApplication wrapper = new ApplicationWrapper();
wrapper.Application.SomeMethod();
你应该使用
IApplication wrapper = new ApplicationWrapper();
wrapper.SomeMethod();
删除对具体类型的依赖。
答案 1 :(得分:1)
您通常不会模仿或伪造静态方法,例如Application.Connect
。只需对正在测试的代码进行分区,以便它采用已创建的IApplication
对象。