如何在mockito中测试循环内部的循环



Hi i have methodinsertOrUpdateProductsToDB(Product product)用于使用Broadleaf的catalogService在数据库中执行插入操作,catalog Service正在数据库中执行所有保存操作。我的方法需要restClient产品作为参数。在通过restClient产品后,我们将使用ProductConversion类将此产品转换为Broadleafproduct。在产品转换中,仅设置将rest产品转换为阔叶产品。现在我的要求是使用mockito测试这个方法,但当我试图在测试方法的末尾添加这两行时

verify(mainProduct).getAdditionalSkus().add(sku);
verify(mainProduct).setProductOptions(productOptionList);

它失败了。

当我调试代码时,方法insertOrUpdateProductsToDB(Product product)中的for循环内部有for循环,我在这里发现productOption = catalogService.saveProductOption(productOption);productOption为空,所以请告诉如何测试循环内部的循环,也会发生同样的情况

for (Sku skuWithProductOptions : productConversion.createSkuWithProductOptions(product, mainProduct,productOptionList)) {
catalogService.saveSku(skuWithProductOptions);
}

这一行也是同样的方法。请检查我的测试用例,看我做得是否正确。

待测试的类别和insertOrUpdateProductsToDB(Product product)方法

import com.admin.exception.AdminGenericException;
import com.admin.exception.AdminRestException;
import com.admin.util.helper.ProductConversion;
import com.admin.wrapper.getproducts.req.ObjectFactory;
import com.admin.wrapper.getproducts.resp.Product;
import com.admin.wrapper.getproducts.resp.Response;
import com.mycompany.rest.service.client.RestClientUtil;
import com.mycompany.util.constants.ApplicationConstants;
@Service
public class GetProductsServiceImpl {
private static final Logger logger = Logger.getLogger(GetProductsServiceImpl.class);
@Resource(name = "blCatalogService")
protected CatalogService catalogService;
public void setCatalogService(CatalogService catalogService) {
this.catalogService = catalogService;
}
protected RestClientUtil restClientUtil;
public void setRestClientUtil(RestClientUtil restClientUtil) {
this.restClientUtil = restClientUtil;
}
@Value("#{configProperties['salePriceRate']}")
private long salePriceRate;
public void setRetailPriceRate(long retailPriceRate) {
this.retailPriceRate = retailPriceRate;
}
@Value("#{configProperties['retailPriceRate']}")
private long retailPriceRate;
public void setSalePriceRate(long salePriceRate) {
this.salePriceRate = salePriceRate;
}

//Insertion/Update DB logic
public String insertOrUpdateProductsToDB(Product product) {
logger.debug("Start of : insertOrUpdateProductsToDB()");
try {
List<String> category = new ArrayList<String>         (Arrays.asList(ApplicationConstants.CATEGORY));
ProductConversion productConversion = new ProductConversion();
List<ProductOption> productOptionList = new ArrayList<ProductOption>();
if (category.contains(product.getCategory().toUpperCase())) {
org.broadleafcommerce.core.catalog.domain.Product mainProduct=catalogService.createProduct(new ProductType("org.broadleafcommerce.core.catalog.domain.Product", "Normal Product"));
mainProduct = productConversion.createProduct(mainProduct,product);
Sku sku=catalogService.createSku();
mainProduct.setDefaultSku(sku);
mainProduct = productConversion.addSkuToProduct(mainProduct, product, salePriceRate,retailPriceRate);
for (ProductOption productOption : productConversion.createProductOptions(product, mainProduct)) {
productOption.setAllowedValues(productConversion.createProductOptionValues(product,productOption));
productOption = catalogService.saveProductOption(productOption);
productOptionList.add(productOption);
}
sku = catalogService.saveSku(mainProduct.getDefaultSku());
mainProduct.getAdditionalSkus().add(sku);
mainProduct.setProductOptions(productOptionList);
mainProduct = catalogService.saveProduct(mainProduct);
for (Sku skuWithProductOptions : productConversion.createSkuWithProductOptions(product, mainProduct,productOptionList)) {
catalogService.saveSku(skuWithProductOptions);
}
}
logger.debug("End of : insertOrUpdateProductsToDB()");
return "Product inserted into DB successfully";
}
catch (Exception e) {
logger.error("Error:", e);
return "Insertion of product into DB Failed ";
}
}
//Insertion service for DB
public String insertProductsIntoDB(){
logger.debug("Start of : insertProductsIntoDB()");
int insertionCount=0;
try{
com.admin.wrapper.getproducts.resp.Response resp = getAvailableProductsFromPBS();
for (Product product : resp.getProducts().getProduct()) {
if(catalogService.findProductById(Long.parseLong(product.getId()))==null){
String str=insertOrUpdateProductsToDB(product);
if(str.equalsIgnoreCase("Product inserted into DB successfully")){
insertionCount=insertionCount+1;
}
}
}
logger.debug(insertionCount+" Products inserted into DB successfully");
logger.debug("End of : insertProductsIntoDB()");
return insertionCount+" Products inserted into DB successfully";
}catch (AdminRestException e) {
logger.error("Error:", e);
return e.getMessage();
}
}
}

我的测试用例类和方法

public class GetProductsServiceImplTest {
private CatalogService catalogService;
private RestClientUtil restClientUtil;
private GetProductsServiceImpl getProductsServiceImpl;
private org.broadleafcommerce.core.catalog.domain.Product mainProduct;
private Sku sku;
private  ProductOption productOption;
private List<ProductOption> productOptionList;

@Before
public void setUp() throws Exception {
catalogService = mock(CatalogService.class);
productOptionList=mock(List.class);
mainProduct = spy(new ProductImpl()); 
sku =  new SkuImpl();
getProductsServiceImpl = new GetProductsServiceImpl();
getProductsServiceImpl.setCatalogService(catalogService);
productOption=mock(ProductOption.class);
restClientUtil = new RestClientUtil();

}
@Test
public void testInsertOrUpdateProductsToDB() {
restClientUtil.setSellerCode("1");
restClientUtil.setPbsUrl("http://10.52.165.239:8080/pbs");
getProductsServiceImpl.setRestClientUtil(restClientUtil);
Response pbsResponse = getProductsServiceImpl
.getAvailableProductsFromPBS();
for (Product pbsProduct : pbsResponse.getProducts().getProduct()) {
when(catalogService.createProduct(new ProductType("org.broadleafcommerce.core.catalog.domain.Product","Normal Product"))).thenReturn(mainProduct);
when(catalogService.createSku()).thenReturn(sku);
when(catalogService.saveProductOption(productOption)).thenReturn(productOption);
when(catalogService.saveSku(sku)).thenReturn(sku);
when(catalogService.saveProduct(mainProduct)).thenReturn(mainProduct);
when(catalogService.saveSku(sku)).thenReturn(sku);
getProductsServiceImpl.insertOrUpdateProductsToDB(pbsProduct);
verify(mainProduct,times(2)).setDefaultSku(sku);
verify(mainProduct).getAdditionalSkus().add(sku);
verify(mainProduct).setProductOptions(productOptionList);
break;          
}
}
}

这是测试时的错误

java.lang.NullPointerException
at com.admin.api.service.getproducts.test.GetProductsServiceImplTest.testInsertOrUpdateProductsToDB(GetProductsServiceImplTest.java:68)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)

我有几句话可能无法回答您最初的问题。但我希望它们能引导您更好地重构这段代码。此外,您展示的代码示例不足以指出确切的问题;这是测试方法中的一个NPE,所以追踪起来应该不会那么困难。

话虽如此,这就是我想提高的要点

  • 测试代码制作得很奇怪,在我看来,这些代码过度使用了Mockito。总的来说,这段代码看起来太复杂了,无论如何都无法进行正确的测试。我不认为它是按照TDD原理编码的(TDD在测试和设计应用程序时真的很方便)

  • 您可能希望遵循通用准则在一个方法中不超过10行代码,这通常有助于分离关注点并确定更简单的代码/意图。如果设计正确(不会泄露概念或变量),这些更简单的代码可以更容易地更改和测试。例如,您可能希望提取一个保存单个Product的方法,然后只测试该方法。

  • 更引人注目的是,这段代码看起来有点程序化(即使在对象内部)。并没有用商业语言真正解释意图(好吧,这是关于在DB中保存东西,但出于什么原因,有所有这些逻辑,这个原因应该出现在方法名称中)。

  • 测试和Mockito很奇怪,代码不应该在集合上迭代以存根然后验证

    for (Product pbsProduct : pbsResponse.getProducts().getProduct()) {
    when(catalogService.createProduct(new ProductType("org.broadleafcommerce.core.catalog.domain.Product","Normal Product"))).thenReturn(mainProduct);
    when(catalogService.createSku()).thenReturn(sku);
    when(catalogService.saveProductOption(productOption)).thenReturn(productOption);
    when(catalogService.saveSku(sku)).thenReturn(sku);
    when(catalogService.saveProduct(mainProduct)).thenReturn(mainProduct);
    when(catalogService.saveSku(sku)).thenReturn(sku);
    getProductsServiceImpl.insertOrUpdateProductsToDB(pbsProduct);
    verify(mainProduct,times(2)).setDefaultSku(sku);
    verify(mainProduct).getAdditionalSkus().add(sku);
    verify(mainProduct).setProductOptions(productOptionList);
    break;
    }
    
  • 在伪代码中,我会首先尝试使用给定的/提取保存逻辑,当/时,然后使用BBDD关键字(它们有助于澄清在哪个场景和上下文中需要测试什么)。尽量减少fixture和断言,您宁愿处理多个测试方法,也不愿处理多个复杂的测试方法。

    @Test
    public void ensure_product_is_saved_in_the_catalog() {
    // given
    Product a_simple_product = ProductBuilder.simpleProduct().build();
    when(catalogService.doSomething(....))).thenReturn(mainProduct);
    // when 
    productsService.saveProduct(product);
    // then
    verify(catalogService).doSomethingElseWith(mainProduct);
    }
    

如果产品数据的断言与测试场景相关,那么编写一个实际测试数据的测试(使用JUnit断言、AssertJ…)。不要嘲笑Product

并逐步进行每个测试,然后在需要时进行重构,以保持代码的可管理性(如有必要,在另一个类中提取一个方法,等等)

  • 你肯定应该读以下几本书,它们帮助很多程序员获得了更好的代码Clean Coder或Growing Object Oriented Software,Guided by Tests。这份清单当然不是详尽无遗的

希望能有所帮助。

相关内容

  • 没有找到相关文章

最新更新