我试图用斯波克框架进行单元测试的一些Java类。 该结构是这样的:
-
com.myorg.requests
(类名: RequestProcessor
) -
com.myorg.query
(类名: DatabaseQuery
)
第一类看起来是这样的:
public class RequestProcessor {
private String request;
public RequestProcessor(String aRequest) {
this.request = request;
}
public String processRequest() {
String response ;
//do something here
try {
if(condition meets) {
response = executeRequest();
}
} catch ( various exceptions... ) {
System.out.println("something went wrong...");
}
}
private String executeRequest() throws <<exceptions thrown by DatabaseQuery>> {
//do something here
DatabaseQuery queryResult = new DatabaseQuery(request)
}
}
我试着写了这样的斯波克测试RequestProcessor
这对依赖类DatabaseQuery
。 我想嘲弄的DatabaseQuery
以简单的测试类RequestProcessor
孤立类。
所述RequestProcessor
的processRequest()
方法被调用,这依赖于另一个私有方法。 这种方法将使用DatabaseQuery
获得实际的查询结果。 这就是我的斯波克测试是这样的:
class RequestProcessorSpec extends Specification {
//Class to be tested
RequestProcessor requestProcessor
//Dependencies
DatabaseQuery dbquery
def "Given a valid request, dbquery's executeQuery method is called" () {
given: "a valid request"
def queryRequest = '{"info1":"value1","info2":"value2","query":"select * from users"}'
and: "mock the DBQuery class"
dbquery = Mock(DatabaseQuery)
and: "create a new request"
requestProcessor = new RequestProcessor(queryRequest)
when: "the request is processed"
requestHandler.processRequest()
then: "dbquery executeQuery method is called"
1 * dbquery.executeQuery(_ as String)
}
}
这不完全是为我工作。 我发现了一个错误:
当我运行与测试gradlew test --info
更多的结果,我看到印刷是由try-catch语句捕获的控制台上的日志processRequest
方法。
我在做什么错在这里?
在示例代码中存在的问题
首先,您的示例代码不工作,即使我简化它,并创建自己的虚拟DatabaseQuery
类,因为你在这里至少有三个错误:
- 在构造函数中你有
this.request = request
(自赋值),但它应该是this.request = aRequest;
。 - 在测试你有
requestHandler.processRequest()
但应该是requestProcessor.processRequest()
- 方法
executeRequest()
不返回String
的规定。 所以,我只能推测,在现实中,它呼吁其他方法DatabaseQuery
,以便将查询结果转换为String
。
为什么不工作模拟?
已经得到了这一点的方式,让我们来看看什么是真正从根本不对您的测试。
我在做什么错在这里?
假设在你的局部变量的模拟是在应用程序代码的其他局部变量在某种程度上有效。 为了要使用的模拟则需要将其注入类测试。 但像这么多的开发者,你没有设计用于通过依赖注入解耦和可测试性,但要创建你的依赖-在这种情况下DatabaseQuery
对象-内部。
还有什么是错的考验?
我想你的测试距离过规范受到影响。 你为什么要检查,如果在其他类中的特定方法是从私有方法调用(间接地,在那个)? 您不直接测试私有方法,但通过调用公共方法掩盖他们的代码,并且您的测试已经做到这一点。
如果你想护住catch
块,只要确保你的要求导致右例外。 也许你需要模拟的数据库连接,并确保它返回预期的结果到DatabaseQuery
。 我没有看到为了准确说出你的代码不够。
原始问题的技术解决方案
现在让我们假设你绝对要检查这种互动,不管是什么我以前说过。 你需要做的视情况而定(你不要在你的代码显示):
在任何情况下,你需要做的DatabaseQuery
注射。 你可以只添加一个成员和另一个二传手到类。
现在你有不同的地方的互动在一个岔路口dbquery.executeQuery(_ as String)
从制造(称呼):
- 如果该方法是从外面叫
DatabaseQuery
,你可以注入正常的Mock
。 - 如果该方法是从内部被称为
DatabaseQuery
,你需要注入一个Spy
,因为一个模拟不会把像原来的物体其他内部方法,因为-嗯,它只是一个模拟。
案例1: executeQuery(String)
从外面叫
package de.scrum_master.query;
public class DatabaseQuery {
private String request;
public DatabaseQuery(String request) {
this.request = request;
}
public String executeQuery(String request) {
return request.toUpperCase();
}
public String getResult() {
return executeQuery(request);
}
}
package de.scrum_master.requests;
import de.scrum_master.query.DatabaseQuery;
public class RequestProcessor {
private String request;
private DatabaseQuery databaseQuery;
public RequestProcessor(String aRequest) {
this.request = aRequest;
databaseQuery = new DatabaseQuery(request);
}
public String processRequest() {
return executeRequest();
}
private String executeRequest() {
return databaseQuery.executeQuery(request);
//return databaseQuery.getResult();
}
public void setDatabaseQuery(DatabaseQuery databaseQuery) {
this.databaseQuery = databaseQuery;
}
}
package de.scrum_master.requests
import de.scrum_master.query.DatabaseQuery
import spock.lang.Specification
class RequestProcessorTest extends Specification {
//Class to be tested
RequestProcessor requestProcessor
//Dependencies
DatabaseQuery dbquery
def "Given a valid request, dbquery's executeQuery method is called" () {
given: "a valid request"
def queryRequest = '{"info1":"value1","info2":"value2","query":"select * from users"}'
and: "mock the DBQuery class"
dbquery = Mock(DatabaseQuery)
//dbquery = Spy(DatabaseQuery, constructorArgs: [queryRequest])
and: "create a new request"
requestProcessor = new RequestProcessor(queryRequest)
requestProcessor.databaseQuery = dbquery
when: "the request is processed"
requestProcessor.processRequest()
then: "dbquery executeQuery method is called"
1 * dbquery.executeQuery(_ as String)
}
}
现在的测试工作,包括交互检查。
案例2: executeQuery(String)
从自己的类内部调用
你看到两个注释掉线RequestProcessor
和RequestProcessorTest
? 只要使用,并注释掉其他两个,而不是像这样:
private String executeRequest() {
//return databaseQuery.executeQuery(request);
return databaseQuery.getResult();
}
and: "mock the DBQuery class"
//dbquery = Mock(DatabaseQuery)
dbquery = Spy(DatabaseQuery, constructorArgs: [queryRequest])
测试仍然有效,包括互动检查。
当然,我不得不伪造一些东西,填充缺失的拼图砖你没有提供,但是这基本上它是如何工作的。