在 Spock 中测试创建方法的问题
Problems with testing create method in Spock
Hey guys, I am new in spock and i have problems with writing tests for save method and setDefaultFormFieldConfig(). Could You help me? i don't know what is wrong with my test.
public ForConfig create(ForConfig forConfig, Long id) {
setNormal(forConfig, "title", "offer.label.title", FieldType.IN);
setNormal(forConfig, "shortDescription", "offer.label.shortDescription", FieldType.TEXTAREA);
forConfig.setCategory(categService.findById(id));
return forConRep.save(formConfig);
}
private void setNormal(FormConfig forConfig, String n, String s, FieldType fieldType) {
if (formConfig.getFc().stream()
.noneMatch(fieldConfig -> fieldConfig.getName().equals(name))) {
forConfig.getFieldsConfig()
.add(new FieldConfig(n, s, null, "M",
fieldType, false, false, true, null));
}
Test for save method:
def 'test create forConfig without normal fields'() {
given:
def forConfig = Mock(FormConfig)
forConfig.getFieldsConfig() >> new ArrayList<Config>()
def forConfRep = Mock(ForConRep);
def categService = Mock(CategService);
def impl = new ForConfServiceImpl(forConfRep, categService)
forConfRep.save(_) >> new FormConfig()
categoryService.findById(_) >> new Categ()
when:
impl.create(forConfig, 1)
then:
1 * forConfig.getFieldsConfig().add(_)
2 * forConfig.setCategory(_)
}
And in the console I get these errors:
Too few invocations for:
1 * formConfig.getFieldsConfig().add(_) (0 invocations)
Unmatched invocations (ordered by similarity):
None
Too few invocations for:
2 * formConfig.setCategory(_) (1 invocation)
Unmatched invocations (ordered by similarity):
None
at org.spockframework.mock.runtime.InteractionScope.verifyInteractions(InteractionScope.java:98)
at org.spockframework.mock.runtime.MockController.leaveScope(MockController.java:77)
at pl.offer.service.impl.ForConfServiceImplSpec.test save formConfig without defaults fields(FormConfigServiceImplSpec.groovy:24)
Process finished with exit code -1
Could You help me?
你的测试有两个主要问题:
计数错误,您在 then:
块中将它们颠倒了:
add(..)
方法应该被调用两次,因为 setDefaultFormFieldConfig(..)
也被调用了两次。
setCategory(..)
方法只能调用一次。
如果您尝试验证 add(..)
上的交互,这是一个 List
方法,您必须为其注入模拟对象。为了简单起见并且为了避免必须存根很多 List
方法,我建议只使用 Spy
对象。但是,你不能做的是将它写成像 1 * formConfig.getFieldsConfig().add(_)
这样的调用链,你必须直接验证目标 mock/spy,例如2 * fieldConfigList.add(_)
.
这是一个 MCVE:
虚拟依赖项 类 使应用程序和测试编译和 运行:
package de.scrum_master.Whosebug.q65144983;
public class Category {
private String name;
public Category() { }
public Category(String name) {
this.name = name;
}
}
package de.scrum_master.Whosebug.q65144983;
public class CategoryService {
public Category findById(Long categoryId) {
return new Category("BY_ID_" + categoryId);
}
}
package de.scrum_master.Whosebug.q65144983;
public class FieldConfig {
private String name;
public FieldConfig(String name, String label, Object o, String match, FieldType type, boolean b, boolean b1, boolean b2, Object o1) {
this.name = name;
}
public String getName() {
return name;
}
}
package de.scrum_master.Whosebug.q65144983;
public enum FieldType {
TEXTAREA, INPUT
}
package de.scrum_master.Whosebug.q65144983;
import java.util.List;
public class FormConfig {
private Category category;
private List<FieldConfig> fieldsConfig;
public FormConfig() {}
public void setCategory(Category category) {
this.category = category;
}
public List<FieldConfig> getFieldsConfig() {
return fieldsConfig;
}
}
package de.scrum_master.Whosebug.q65144983;
public class FormConfigRepository {
public FormConfig save(FormConfig formConfig) {
return formConfig;
}
}
正在测试的接口+实现:
package de.scrum_master.Whosebug.q65144983;
public interface FormConfigService {
FormConfig save(FormConfig formConfig, Long categoryId);
}
package de.scrum_master.Whosebug.q65144983;
public class FormConfigServiceImpl implements FormConfigService {
private FormConfigRepository formConfigRepository;
private CategoryService categoryService;
public FormConfigServiceImpl(FormConfigRepository formConfigRepository, CategoryService categoryService) {
this.formConfigRepository = formConfigRepository;
this.categoryService = categoryService;
}
@Override
public FormConfig save(FormConfig formConfig, Long categoryId) {
setDefaultFormFieldConfig(formConfig, "title", "offer.label.title", FieldType.INPUT);
setDefaultFormFieldConfig(formConfig, "shortDescription", "offer.label.shortDescription", FieldType.TEXTAREA);
formConfig.setCategory(categoryService.findById(categoryId));
return formConfigRepository.save(formConfig);
}
private void setDefaultFormFieldConfig(FormConfig formConfig, String name, String label, FieldType type) {
if (
formConfig.getFieldsConfig().stream()
.noneMatch(fieldConfig -> fieldConfig.getName().equals(name))
)
{
formConfig.getFieldsConfig()
.add(new FieldConfig(name, label, null, "MATCH", type, false, false, true, null));
}
}
}
斯波克测试:
我希望你能接受我让模拟更加类型安全和内联存根方法。否则,测试与您的几乎相同。我只修复了开头提到的两个问题
package de.scrum_master.Whosebug.q65144983
import spock.lang.Specification
class FormConfigServiceImplTest extends Specification {
def 'test save formConfig without defaults fields'() {
given:
List fieldConfigList = Spy(new ArrayList<FieldConfig>())
FormConfig formConfig = Mock() {
getFieldsConfig() >> fieldConfigList
}
FormConfigRepository formConfigRepository = Mock() {
save(_) >> new FormConfig()
}
CategoryService categoryService = Mock() {
findById(_) >> new Category()
}
def formConfigService = new FormConfigServiceImpl(formConfigRepository, categoryService)
when:
formConfigService.save(formConfig, 1)
then:
2 * fieldConfigList.add(_)
1 * formConfig.setCategory(_)
}
}
P.S.: 我向您展示了如何使用 Spock 从技术上解决您的问题。不过我确实相信,这种测试实现内部结构的方法可能不是测试应用程序的正确方法。如果您进行一些内部重构,这些类型的测试往往很脆弱并且很容易破坏。您还必须重构您的测试,这通常没问题。但我建议您专注于测试应用程序的 public API,不要着迷于内部工作,除非它们绝对重要。那么你当然可以而且也许应该测试它们。
Hey guys, I am new in spock and i have problems with writing tests for save method and setDefaultFormFieldConfig(). Could You help me? i don't know what is wrong with my test.
public ForConfig create(ForConfig forConfig, Long id) {
setNormal(forConfig, "title", "offer.label.title", FieldType.IN);
setNormal(forConfig, "shortDescription", "offer.label.shortDescription", FieldType.TEXTAREA);
forConfig.setCategory(categService.findById(id));
return forConRep.save(formConfig);
}
private void setNormal(FormConfig forConfig, String n, String s, FieldType fieldType) {
if (formConfig.getFc().stream()
.noneMatch(fieldConfig -> fieldConfig.getName().equals(name))) {
forConfig.getFieldsConfig()
.add(new FieldConfig(n, s, null, "M",
fieldType, false, false, true, null));
}
Test for save method:
def 'test create forConfig without normal fields'() {
given:
def forConfig = Mock(FormConfig)
forConfig.getFieldsConfig() >> new ArrayList<Config>()
def forConfRep = Mock(ForConRep);
def categService = Mock(CategService);
def impl = new ForConfServiceImpl(forConfRep, categService)
forConfRep.save(_) >> new FormConfig()
categoryService.findById(_) >> new Categ()
when:
impl.create(forConfig, 1)
then:
1 * forConfig.getFieldsConfig().add(_)
2 * forConfig.setCategory(_)
}
And in the console I get these errors:
Too few invocations for:
1 * formConfig.getFieldsConfig().add(_) (0 invocations)
Unmatched invocations (ordered by similarity):
None
Too few invocations for:
2 * formConfig.setCategory(_) (1 invocation)
Unmatched invocations (ordered by similarity):
None
at org.spockframework.mock.runtime.InteractionScope.verifyInteractions(InteractionScope.java:98)
at org.spockframework.mock.runtime.MockController.leaveScope(MockController.java:77)
at pl.offer.service.impl.ForConfServiceImplSpec.test save formConfig without defaults fields(FormConfigServiceImplSpec.groovy:24)
Process finished with exit code -1
Could You help me?
你的测试有两个主要问题:
计数错误,您在
then:
块中将它们颠倒了:add(..)
方法应该被调用两次,因为setDefaultFormFieldConfig(..)
也被调用了两次。setCategory(..)
方法只能调用一次。
如果您尝试验证
add(..)
上的交互,这是一个List
方法,您必须为其注入模拟对象。为了简单起见并且为了避免必须存根很多List
方法,我建议只使用Spy
对象。但是,你不能做的是将它写成像1 * formConfig.getFieldsConfig().add(_)
这样的调用链,你必须直接验证目标 mock/spy,例如2 * fieldConfigList.add(_)
.
这是一个 MCVE:
虚拟依赖项 类 使应用程序和测试编译和 运行:
package de.scrum_master.Whosebug.q65144983;
public class Category {
private String name;
public Category() { }
public Category(String name) {
this.name = name;
}
}
package de.scrum_master.Whosebug.q65144983;
public class CategoryService {
public Category findById(Long categoryId) {
return new Category("BY_ID_" + categoryId);
}
}
package de.scrum_master.Whosebug.q65144983;
public class FieldConfig {
private String name;
public FieldConfig(String name, String label, Object o, String match, FieldType type, boolean b, boolean b1, boolean b2, Object o1) {
this.name = name;
}
public String getName() {
return name;
}
}
package de.scrum_master.Whosebug.q65144983;
public enum FieldType {
TEXTAREA, INPUT
}
package de.scrum_master.Whosebug.q65144983;
import java.util.List;
public class FormConfig {
private Category category;
private List<FieldConfig> fieldsConfig;
public FormConfig() {}
public void setCategory(Category category) {
this.category = category;
}
public List<FieldConfig> getFieldsConfig() {
return fieldsConfig;
}
}
package de.scrum_master.Whosebug.q65144983;
public class FormConfigRepository {
public FormConfig save(FormConfig formConfig) {
return formConfig;
}
}
正在测试的接口+实现:
package de.scrum_master.Whosebug.q65144983;
public interface FormConfigService {
FormConfig save(FormConfig formConfig, Long categoryId);
}
package de.scrum_master.Whosebug.q65144983;
public class FormConfigServiceImpl implements FormConfigService {
private FormConfigRepository formConfigRepository;
private CategoryService categoryService;
public FormConfigServiceImpl(FormConfigRepository formConfigRepository, CategoryService categoryService) {
this.formConfigRepository = formConfigRepository;
this.categoryService = categoryService;
}
@Override
public FormConfig save(FormConfig formConfig, Long categoryId) {
setDefaultFormFieldConfig(formConfig, "title", "offer.label.title", FieldType.INPUT);
setDefaultFormFieldConfig(formConfig, "shortDescription", "offer.label.shortDescription", FieldType.TEXTAREA);
formConfig.setCategory(categoryService.findById(categoryId));
return formConfigRepository.save(formConfig);
}
private void setDefaultFormFieldConfig(FormConfig formConfig, String name, String label, FieldType type) {
if (
formConfig.getFieldsConfig().stream()
.noneMatch(fieldConfig -> fieldConfig.getName().equals(name))
)
{
formConfig.getFieldsConfig()
.add(new FieldConfig(name, label, null, "MATCH", type, false, false, true, null));
}
}
}
斯波克测试:
我希望你能接受我让模拟更加类型安全和内联存根方法。否则,测试与您的几乎相同。我只修复了开头提到的两个问题
package de.scrum_master.Whosebug.q65144983
import spock.lang.Specification
class FormConfigServiceImplTest extends Specification {
def 'test save formConfig without defaults fields'() {
given:
List fieldConfigList = Spy(new ArrayList<FieldConfig>())
FormConfig formConfig = Mock() {
getFieldsConfig() >> fieldConfigList
}
FormConfigRepository formConfigRepository = Mock() {
save(_) >> new FormConfig()
}
CategoryService categoryService = Mock() {
findById(_) >> new Category()
}
def formConfigService = new FormConfigServiceImpl(formConfigRepository, categoryService)
when:
formConfigService.save(formConfig, 1)
then:
2 * fieldConfigList.add(_)
1 * formConfig.setCategory(_)
}
}
P.S.: 我向您展示了如何使用 Spock 从技术上解决您的问题。不过我确实相信,这种测试实现内部结构的方法可能不是测试应用程序的正确方法。如果您进行一些内部重构,这些类型的测试往往很脆弱并且很容易破坏。您还必须重构您的测试,这通常没问题。但我建议您专注于测试应用程序的 public API,不要着迷于内部工作,除非它们绝对重要。那么你当然可以而且也许应该测试它们。