我最近刚刚阅读了关于单元测试的“模拟对象”,目前我在我的应用程序中实现这种方法时遇到了困难。请让我解释一下我的问题。
我有一个User
模型类,它依赖于2个数据源(数据库和facebook web服务)。控制器类只使用此User
模型作为访问数据的接口,而不关心数据的来源。
目前我从未对此User
模型进行任何单元测试,因为它依赖于外部Web服务。但就在不久前,我读到了关于对象模拟的内容,现在我知道这是一种通常的方法来对依赖外部资源的类进行单元测试(就像我的情况一样)。
现在我想为User
模型创建一个单元测试,但后来遇到了一个设计问题:
为了让User
模型使用模拟的Facebook SDK,我必须将这个模拟的Facebook SDK注入User对象(可能使用setter)。因此,我无法在User对象中构建Facebook SDK。我必须在User对象之外构建它,并且将 SDK注入到User对象中。
我的User
模型的真实客户端是应用程序的控制器。因此,我必须在控制器内构建Facebook SDK并将其注入用户对象。嗯,这是一个问题,因为我希望我的控制器尽可能干净。我希望我的控制器对应用程序的数据源一无所知。
我不擅长系统地解释一些东西,所以在阅读最后一段之前你可能会睡觉。但无论如何,我想问一下这里是否有人遇到过与我相同的问题?你是如何解决这个问题的?
此致 ANDREE
P.S:我正在使用Zend框架,PHP 5.3。
答案 0 :(得分:2)
解决此问题的一种方法是在User
中创建两个构造函数:默认值实例化实际数据源,另一个获取它们作为参数。这样,您的控制器可以使用默认构造函数,而您的测试使用参数化的传递来传递模拟数据源。
由于您没有指定语言,我向您展示了Java中的示例,希望这有助于实现这个想法:
class User {
private DataBase database;
private WebService webService;
// default constructor
public User() {
database = new OracleDataBase();
webService = new FacebookWebService();
}
// constructor for unit testing
public User(DataBase database, WebService webService) {
this.database = database;
this.webService = webService;
}
}
答案 1 :(得分:1)
这不是关于模拟的问题,而是关于依赖关系 - 并且尝试单元测试已经迫使这个问题。听起来好像您在Controller中创建了User对象。
如果用户和控制器具有相同的生命周期(它们是同时创建的),那么您可以将用户传递到Controller的构造函数中,您可以在其中进行替换。
如果每次调用都有一个User对象,那么User对象可能应该由环境传入,或者从某个Context对象返回。
答案 2 :(得分:0)
如果您将Facebook SDK对象公开访问,您的User对象仍然可以在设置时创建它,但您可以在对User对象实际执行任何操作之前用模拟替换它。
[Test]
public void Test()
{
User u = new User(); // let's say that the object on User gets created in the ctor
u.FacebookObj = new DynamicMock(typeof(FacebookSDK)).MockInstance;
Assert.That(u.Method(), Does.Stuff, "u.Method didn't do stuff");
}
答案 3 :(得分:0)
你没有说你正在使用什么语言,但我使用Ruby和Mocha来模拟对象,这很容易。
请参阅http://mocha.rubyforge.org/。
此处第四个示例显示拦截了被测单元对Product.name的任何调用,并返回值“stubbed_name”。
我想Java中有类似的机制等。
答案 4 :(得分:0)
由于您将进行单元测试,因此您的测试类将扮演Controller的角色,因此不应该触及。这样可以保持清洁。
您正在彻底改造依赖注入。因此,看看Spring或Guice来帮助您进行管道工作可能是值得的。 (如果你在Java领域)。
您的测试确实可以使用setter或构造函数参数注入Mocked Facebook SDK和数据库服务。
您的测试现在将执行控制器(可能是视图)将执行的操作,并验证在SDK中调用正确的例程,以及是否正确获取和处理资源。