未执行 spock 测试中的模拟 java class
Mocked java class in spock test is not being executed
我正在尝试使用 spock 框架对一些 java class 进行单元测试。结构如下所示:
com.myorg.requests
(Class 姓名:RequestProcessor
)
com.myorg.query
(Class 姓名:DatabaseQuery
)
第一个 class 看起来像这样:
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
class 编写 Spock 测试,它依赖于 DatabaseQuery
。我正在考虑模拟 DatabaseQuery
class 以便简单地单独测试 RequestProcessor
class。
正在调用RequestProcessor
的processRequest()
方法,该方法依赖于另一个私有方法。该方法将使用 DatabaseQuery
来获取实际的查询结果。这就是我的 Spock 测试的样子:
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
进行测试以获得更多结果时,我看到控制台上打印了一条日志,该日志由 processRequest
方法中的 try-catch 语句捕获。
我做错了什么?
示例代码中的问题
首先,您的示例代码不起作用,即使我简化它并创建我自己的虚拟 DatabaseQuery
class 因为您这里至少有三个错误:
- 在构造函数中你有
this.request = request
(self-assignment),但它应该是 this.request = aRequest;
.
- 在测试中你有
requestHandler.processRequest()
,但应该是 requestProcessor.processRequest()
。
- 方法
executeRequest()
不 return 一个 String
指定。所以我只能推测实际上它在 DatabaseQuery
上调用了另一个方法,以便将查询结果转换为 String
.
为什么模拟不起作用?
解决了这个问题后,让我们看看您的测试真正根本性的错误是什么。
What am I doing wrong here?
假设您的局部变量中的模拟在您的应用程序代码中的其他局部变量中以某种方式有效。为了使用模拟,您需要将其注入到被测 class 中。但是像许多开发人员一样,您没有通过依赖注入来设计解耦和可测试性,而是在内部创建您的依赖项 - 在本例中为 DatabaseQuery
对象。
测试还有什么问题?
我认为你的测试只是受到 over-specification 的影响。为什么要检查另一个 class 中的特定方法是否是从私有方法调用的(间接地)?您不直接测试私有方法,而是通过调用 public 方法来覆盖它们的代码,并且您的测试已经这样做了。
如果您想覆盖 catch
块,只需确保您的请求导致正确的异常。也许您需要为此模拟数据库连接并确保它 return 是 DatabaseQuery
的预期结果。我没有看到足够多的代码来准确地说。
原问题的技术解决方案
现在假设您绝对想检查此交互,无论我之前说过什么。你需要做什么取决于情况(你没有在你的代码中显示):
在任何情况下,您都需要使 DatabaseQuery
可注射。您可以只添加一个成员和另一个 setter 到您的 class.
现在你有一个岔路口,这取决于互动 dbquery.executeQuery(_ as String)
是从哪里进行(调用)的:
- 如果方法是从外部调用的
DatabaseQuery
,你可以注入一个正常的Mock
。
- 如果从内部调用方法
DatabaseQuery
,你需要注入一个 Spy
因为 mock 不会像原始对象那样调用其他内部方法,因为 - 好吧,它只是一个 mock .
情况一: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)
是从其自身内部调用的 class
您看到 RequestProcessor
和 RequestProcessorTest
中的两条 commented-out 行了吗?只需使用它们并注释掉其他两个,而不是像这样:
private String executeRequest() {
//return databaseQuery.executeQuery(request);
return databaseQuery.getResult();
}
and: "mock the DBQuery class"
//dbquery = Mock(DatabaseQuery)
dbquery = Spy(DatabaseQuery, constructorArgs: [queryRequest])
测试仍然有效,包括交互检查。
当然我不得不伪造一些东西并填写你没有提供的缺失的拼图块,但这基本上就是它的工作原理。
我正在尝试使用 spock 框架对一些 java class 进行单元测试。结构如下所示:
com.myorg.requests
(Class 姓名:RequestProcessor
)com.myorg.query
(Class 姓名:DatabaseQuery
)
第一个 class 看起来像这样:
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
class 编写 Spock 测试,它依赖于 DatabaseQuery
。我正在考虑模拟 DatabaseQuery
class 以便简单地单独测试 RequestProcessor
class。
正在调用RequestProcessor
的processRequest()
方法,该方法依赖于另一个私有方法。该方法将使用 DatabaseQuery
来获取实际的查询结果。这就是我的 Spock 测试的样子:
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
进行测试以获得更多结果时,我看到控制台上打印了一条日志,该日志由 processRequest
方法中的 try-catch 语句捕获。
我做错了什么?
示例代码中的问题
首先,您的示例代码不起作用,即使我简化它并创建我自己的虚拟 DatabaseQuery
class 因为您这里至少有三个错误:
- 在构造函数中你有
this.request = request
(self-assignment),但它应该是this.request = aRequest;
. - 在测试中你有
requestHandler.processRequest()
,但应该是requestProcessor.processRequest()
。 - 方法
executeRequest()
不 return 一个String
指定。所以我只能推测实际上它在DatabaseQuery
上调用了另一个方法,以便将查询结果转换为String
.
为什么模拟不起作用?
解决了这个问题后,让我们看看您的测试真正根本性的错误是什么。
What am I doing wrong here?
假设您的局部变量中的模拟在您的应用程序代码中的其他局部变量中以某种方式有效。为了使用模拟,您需要将其注入到被测 class 中。但是像许多开发人员一样,您没有通过依赖注入来设计解耦和可测试性,而是在内部创建您的依赖项 - 在本例中为 DatabaseQuery
对象。
测试还有什么问题?
我认为你的测试只是受到 over-specification 的影响。为什么要检查另一个 class 中的特定方法是否是从私有方法调用的(间接地)?您不直接测试私有方法,而是通过调用 public 方法来覆盖它们的代码,并且您的测试已经这样做了。
如果您想覆盖 catch
块,只需确保您的请求导致正确的异常。也许您需要为此模拟数据库连接并确保它 return 是 DatabaseQuery
的预期结果。我没有看到足够多的代码来准确地说。
原问题的技术解决方案
现在假设您绝对想检查此交互,无论我之前说过什么。你需要做什么取决于情况(你没有在你的代码中显示):
在任何情况下,您都需要使 DatabaseQuery
可注射。您可以只添加一个成员和另一个 setter 到您的 class.
现在你有一个岔路口,这取决于互动 dbquery.executeQuery(_ as String)
是从哪里进行(调用)的:
- 如果方法是从外部调用的
DatabaseQuery
,你可以注入一个正常的Mock
。 - 如果从内部调用方法
DatabaseQuery
,你需要注入一个Spy
因为 mock 不会像原始对象那样调用其他内部方法,因为 - 好吧,它只是一个 mock .
情况一: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)
是从其自身内部调用的 class
您看到 RequestProcessor
和 RequestProcessorTest
中的两条 commented-out 行了吗?只需使用它们并注释掉其他两个,而不是像这样:
private String executeRequest() {
//return databaseQuery.executeQuery(request);
return databaseQuery.getResult();
}
and: "mock the DBQuery class"
//dbquery = Mock(DatabaseQuery)
dbquery = Spy(DatabaseQuery, constructorArgs: [queryRequest])
测试仍然有效,包括交互检查。
当然我不得不伪造一些东西并填写你没有提供的缺失的拼图块,但这基本上就是它的工作原理。