由方法而不是构造函数创建的假对象
Fake object that is created by a method, not a constructor
我正在尝试对使用 API 的代码进行单元测试,所以我正在尝试解耦。
我在API里面为"Application"class创建了一个接口,是密封的
然后我创建了一个 class,它使用的接口有一个方法,return 是一个 "Application" 类型的对象。
这是我遇到问题的地方,在我的单元测试中,我尝试创建一个 "Application" 对象来验证 return 值是否正确。然而 "Application" class 没有任何构造函数,没有任何 public 或私有的(我用反射检查过)。该对象是通过调用静态 Application.Connect(AnotherTypeFromAPI arg) 创建的,它 return 是一个应用程序对象。
如何 return 我无法创建的假对象?
appMock.Connect(arg).Returns("How do I return an Application object here?"));
或者对于依赖于 API 的单元测试代码,我是否采用了错误的方式?整个 API 依赖于 "Application" 类型,所以如果我不能伪造它,我还不确定如何存根或模拟我需要的其他方法。
我正在使用 C#、NUnit、NSUbstitute。
您通常不会模拟或伪造静态方法,例如 Application.Connect
。只需对被测代码进行分区,使其采用已创建的 IApplication
对象。
这个问题可以解决,但是你用错了模式。您需要创建一个 完全替换 具体依赖项的接口,而不是通过新接口公开应用程序的实例。
你有什么
如果我对你的问题的理解正确,你有一个密封的应用程序 class,它有一些你的程序需要能够调用的方法,它没有 public 构造函数,只有一个静态工厂方法。这里举一个简单的例子来讨论,只有一种方法,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");
这种方法永远不适用于单元测试,因为您仍然直接依赖于密封的应用程序 class。你刚刚移动了它。你仍然需要调用Application.SomeMethod()
,这是一个具体的方法;你应该只依赖于界面,而不是任何具体的东西。
什么会起作用
理论上,"right" 的方法是包装所有东西。因此,不要将 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");
或者完整的 class 与 DI,它看起来像这样:
class ClassUnderTest
{
protected readonly IApplication _application; //Injected
public ClassUnderTest(IApplication application)
{
_application = application; //constructor injection
}
public void MethodUnderTest()
{
_application.SomeMethod("Real argument");
}
}
如何进行单元测试
在您的单元测试中,您现在可以使用新的 class 模拟 IApplication,例如
class ApplicationStub : IApplication
{
public string TestResult { get; set; } //Doesn't exist in system under test
public void SomeMethod(string input)
{
this.TestResult = input;
}
}
请注意,此 class 完全不依赖于应用程序。因此,您根本不再需要对其调用 new
或调用其工厂方法。对于单元测试目的,您只需要确保它被正确调用。您可以通过传入存根并随后检查 TestResult
来执行此操作:
//Arrange
var stub = new ApplicationStub();
var c = ClassUnderTest(stub);
//Act
c.MethodUnderTest("Test Argument");
//Assert
Assert.AreEqual(stub.TestResult, "Test Argument");
编写完整的包装器需要做更多的工作(特别是如果它有很多方法),但是您可以使用反射或第三方工具生成大量代码。它允许您进行完整的单元测试,这就是切换到 IApplication
界面的全部想法。
TLDR:
而不是
IApplication wrapper = new ApplicationWrapper();
wrapper.Application.SomeMethod();
你应该使用
IApplication wrapper = new ApplicationWrapper();
wrapper.SomeMethod();
删除对具体类型的依赖。
我正在尝试对使用 API 的代码进行单元测试,所以我正在尝试解耦。
我在API里面为"Application"class创建了一个接口,是密封的
然后我创建了一个 class,它使用的接口有一个方法,return 是一个 "Application" 类型的对象。
这是我遇到问题的地方,在我的单元测试中,我尝试创建一个 "Application" 对象来验证 return 值是否正确。然而 "Application" class 没有任何构造函数,没有任何 public 或私有的(我用反射检查过)。该对象是通过调用静态 Application.Connect(AnotherTypeFromAPI arg) 创建的,它 return 是一个应用程序对象。
如何 return 我无法创建的假对象?
appMock.Connect(arg).Returns("How do I return an Application object here?"));
或者对于依赖于 API 的单元测试代码,我是否采用了错误的方式?整个 API 依赖于 "Application" 类型,所以如果我不能伪造它,我还不确定如何存根或模拟我需要的其他方法。
我正在使用 C#、NUnit、NSUbstitute。
您通常不会模拟或伪造静态方法,例如 Application.Connect
。只需对被测代码进行分区,使其采用已创建的 IApplication
对象。
这个问题可以解决,但是你用错了模式。您需要创建一个 完全替换 具体依赖项的接口,而不是通过新接口公开应用程序的实例。
你有什么
如果我对你的问题的理解正确,你有一个密封的应用程序 class,它有一些你的程序需要能够调用的方法,它没有 public 构造函数,只有一个静态工厂方法。这里举一个简单的例子来讨论,只有一种方法,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");
这种方法永远不适用于单元测试,因为您仍然直接依赖于密封的应用程序 class。你刚刚移动了它。你仍然需要调用Application.SomeMethod()
,这是一个具体的方法;你应该只依赖于界面,而不是任何具体的东西。
什么会起作用
理论上,"right" 的方法是包装所有东西。因此,不要将 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");
或者完整的 class 与 DI,它看起来像这样:
class ClassUnderTest
{
protected readonly IApplication _application; //Injected
public ClassUnderTest(IApplication application)
{
_application = application; //constructor injection
}
public void MethodUnderTest()
{
_application.SomeMethod("Real argument");
}
}
如何进行单元测试
在您的单元测试中,您现在可以使用新的 class 模拟 IApplication,例如
class ApplicationStub : IApplication
{
public string TestResult { get; set; } //Doesn't exist in system under test
public void SomeMethod(string input)
{
this.TestResult = input;
}
}
请注意,此 class 完全不依赖于应用程序。因此,您根本不再需要对其调用 new
或调用其工厂方法。对于单元测试目的,您只需要确保它被正确调用。您可以通过传入存根并随后检查 TestResult
来执行此操作:
//Arrange
var stub = new ApplicationStub();
var c = ClassUnderTest(stub);
//Act
c.MethodUnderTest("Test Argument");
//Assert
Assert.AreEqual(stub.TestResult, "Test Argument");
编写完整的包装器需要做更多的工作(特别是如果它有很多方法),但是您可以使用反射或第三方工具生成大量代码。它允许您进行完整的单元测试,这就是切换到 IApplication
界面的全部想法。
TLDR:
而不是
IApplication wrapper = new ApplicationWrapper();
wrapper.Application.SomeMethod();
你应该使用
IApplication wrapper = new ApplicationWrapper();
wrapper.SomeMethod();
删除对具体类型的依赖。