为什么要在单元测试中避免使用条件逻辑?

时间:2015-01-10 20:02:16

标签: java unit-testing testing junit automated-tests

想象一下,有以下几个类:

public class Product {
   private String name;
   private double price;

   // Constructors, getters and setters
}

public class Products {
   private List<Product> products;

   // CRUD methods

   public double getTotalPrice() {
      // calculates the price of all products
   }
}

我已经读过在单元测试中应该避免使用条件逻辑(if和loop),但是不明白为什么,更重要的是如何理解。如何有效地测试我在Products中以不同价格添加某些产品的情况,然后在不使用循环的情况下验证getTotalPrice()的结果?

2 个答案:

答案 0 :(得分:6)

对条件和循环的恐惧是双重的:

  1. 您的TEST代码中可能存在错误,因此测试运行不正确或更糟糕的是条件块内的断言未被断言。
  2. 阅读起来比较困难。
  3. 其他人已回答只是通过复制和粘贴对列表进行硬编码。我不喜欢这样,因为它不仅使测试变得混乱,而且使得以后重构变得更加困难。

    如果代码与Invisible Arrow的回答类似:

    @Test
    public void testsTotalPriceAsSumOfProductPrices() {
        Products products = new Products(); // Or any appropriate constructor
        products.add(new Product("first", 10)); // Assuming such a constructor exists
        products.add(new Product("second", 20));
        products.add(new Product("second", 30));
        assertEquals("Sum must be eq to 60", 60, products.getTotalPrice());
    }
    

    Product的构造函数已更改,您必须在许多不同的位置更改所有测试代码。

    我更喜欢制作辅助方法,使测试代码更具意图,并将重构期间要更改的位置数量保持在最低限度(希望只有一个)。

    这不容易阅读:

     @Test
    public void testsTotalPriceAsSumOfProductPrices() {
        Products products = new Products(); 
    
        addProductsWithPrices(products, 10,20,30);
    
        assertEquals(60, products.getTotalPrice());
    
    }
    
    private static void addProductsWithPrices(Products products, Double...prices){
      for(Double price : prices){
         //could keep static counter or something
         //to make names unique
         products.add(new Product("name", price));
      }
    }
    

    是的,这确实使用了for循环。但如果您担心它有错误或辅助方法更复杂,您也可以为它们编写额外的测试!最终,您可能希望将这些辅助方法考虑到自己的类中,以便可以在其他测试中重用它们。

    此外,您可以看到测试方法隐藏了制作与测试无关的产品(name)所需的其他字段。我们的测试只关心价格,所以我们的助手只是组成名字,阅读测试的人不会被额外的参数搞糊涂。很明显,这个测试确保10 + 20 + 30 == 60。

    最后,如果我们将价格从double更改为某些Currency对象,我们只需要进行一次更改,我们的测试代码就是可读的。

     private static void addProductsWithPrices(Products products, Double...prices){
      for(Double price : prices){
         //could keep static counter or something
         //to make names unique
         products.add(new Product("name", Currency.valueOf(price)));
      }
    }
    

答案 1 :(得分:2)

在单元测试中避免条件和循环的原因是因为它们变得难以阅读和维护。每个条件很可能代表它自己的测试场景,并且应该在单独的单元测试中。 然后,每个测试将作为功能的文档或规范。查看测试的人将确切知道该功能的预期/未预期内容。

对于上面的特定情况,您可以采用简单的方法,并通过在列表中添加一些产品来测试求和函数。

public class ProductsTest {
    @Test
    public void testsTotalPriceAsSumOfProductPrices() {
        Products products = new Products(); // Or any appropriate constructor
        products.add(new Product("first", 10)); // Assuming such a constructor exists
        products.add(new Product("second", 20));
        products.add(new Product("second", 30));
        assertEquals("Sum must be eq to 60", 60, products.getTotalPrice());
    }
}

在我看来,使用上面的循环创建/插入项目是不必要的,并增加了测试的复杂性。

你当然可以有更多的测试来测试边界条件,例如总和溢出,排除负数等。