Unit testing 如何在JUnit测试中避免多个断言?

Unit testing 如何在JUnit测试中避免多个断言?,unit-testing,junit,Unit Testing,Junit,我有一个从request对象填充的DTO,request对象有许多字段。我想编写一个测试来检查populateDTO()方法是否将值放在正确的位置。如果我遵循每个测试一个断言的规则,我将不得不编写大量的测试来测试每个字段。另一种方法是在一个测试中编写多个断言。是否真的建议每个测试规则遵循一个断言,或者在这些情况下我们可以放松。我如何处理这个问题?这个规则是否扩展到循环中?考虑这个 Collection expectedValues = // populate expected values po

我有一个从request对象填充的DTO,request对象有许多字段。我想编写一个测试来检查
populateDTO()
方法是否将值放在正确的位置。如果我遵循每个测试一个断言的规则,我将不得不编写大量的测试来测试每个字段。另一种方法是在一个测试中编写多个断言。是否真的建议每个测试规则遵循一个断言,或者在这些情况下我们可以放松。我如何处理这个问题?

这个规则是否扩展到循环中?考虑这个

Collection expectedValues = // populate expected values
populateDTO();
for(DTO dto : myDtoContainer) 
  assert_equal(dto, expectedValues.get(someIndexRelatedToDto))
现在我对确切的语法不是很在行,但这正是我正在研究的概念

编辑: 在评论之后

答案是<不是

该原理存在的原因是,您可以识别对象的哪些部分失败。如果你在一个方法中有它们,你只会遇到一个断言,然后是下一个,然后是下一个,你不会看到所有的断言

因此,您可以采用以下两种方式之一:

  • 一种方法,更少的样板代码
  • 多种方法,更好地报告测试运行
  • 这取决于你,两者都有起伏。
    3.列表项

    您可以使用一个参数,其中第一个参数是propertyname,第二个参数是期望值。

    或者您可以进行一些变通

    import junit.framework.Assert;
    import org.junit.After;
    import org.junit.AfterClass;
    import org.junit.Before;
    import org.junit.BeforeClass;
    import org.junit.Test;
    
    public class NewEmptyJUnitTest {
    
        public NewEmptyJUnitTest() {
        }
    
        @BeforeClass
        public static void setUpClass() throws Exception {
        }
    
        @AfterClass
        public static void tearDownClass() throws Exception {
        }
    
        @Before
        public void setUp() {
        }
    
        @After
        public void tearDown() {
        }
    
    
         @Test
         public void checkMultipleValues() {
             String errMessages = new String();
    
             try{
                 this.checkProperty1("someActualResult", "someExpectedResult");
             } catch (Exception e){
                 errMessages += e.getMessage();
             }
    
            try{
                this.checkProperty2("someActualResult", "someExpectedResult");
             } catch (Exception e){
                 errMessages += e.getMessage();
             }
    
            try{
                 this.checkProperty3("someActualResult", "someExpectedResult");
             } catch (Exception e){
                 errMessages += e.getMessage();
             }
    
            Assert.assertTrue(errMessages, errMessages.isEmpty());
    
    
         }
    
    
    
         private boolean checkProperty1(String propertyValue, String expectedvalue) throws Exception{
             if(propertyValue == expectedvalue){
                 return true;
             }else {
                 throw new Exception("Property1 has value: " + propertyValue + ", expected: " + expectedvalue);
             }
         }
    
           private boolean checkProperty2(String propertyValue, String expectedvalue) throws Exception{
             if(propertyValue == expectedvalue){
                 return true;
             }else {
                 throw new Exception("Property2 has value: " + propertyValue + ", expected: " + expectedvalue);
             }
         }
    
             private boolean checkProperty3(String propertyValue, String expectedvalue) throws Exception{
             if(propertyValue == expectedvalue){
                 return true;
             }else {
                 throw new Exception("Property3 has value: " + propertyValue + ", expected: " + expectedvalue);
             }
         }  
    }  
    

    也许不是最好的方法,如果过度使用,可能会混淆。。。但这是可能的。

    将它们分开。单元测试应该告诉您哪个单元失败了。将它们分开还允许您快速隔离问题,而不需要您经历漫长的调试周期

    [注意:我对Java/JUnit非常“不感兴趣”,所以请注意下面细节中的错误]

    有两种方法可以做到这一点:

    1) 在同一测试中编写多个断言。如果只测试一次DTO生成,这应该是可以的。你可以从这里开始,当这开始伤痛的时候,转移到另一个解决方案

    2) 编写一个助手断言,例如assertDtoFieldsEqual,传入预期的和实际的DTO。在helper断言中,可以分别断言每个字段。这至少让您产生了一种错觉,即每个测试只有一个断言,并且如果您针对多个场景测试DTO生成,这将使事情变得更清楚

    3) 为检查每个属性的对象实现equals,并实现toString,这样您至少可以手动检查断言结果以找出哪些部分不正确

    4) 对于生成DTO的每个场景,创建一个单独的测试夹具,该夹具生成DTO并初始化设置方法中的预期属性。创建一个单独的测试来测试每个属性。这也会导致很多测试,但它们至少只有一行程序。伪代码中的示例:

    public class WithDtoGeneratedFromXxx : TestFixture
    {
      DTO dto = null;
    
      public void setUp()
      {
        dto = GenerateDtoFromXxx();
        expectedProp1 = "";
        ...
      }
    
      void testProp1IsGeneratedCorrectly()
      {
        assertEqual(expectedProp1, dto.prop1);
      }
      ...
    }
    
    abstract class AbstractDtoTest : TestFixture
    {
      DTO dto;
      SomeType expectedProp1;
    
      abstract DTO createDto();
      abstract SomeType getExpectedProp1();
    
      void setUp()
      {
        dto = createDto();
        ...
      }
    
      void testProp1IsGeneratedCorrectly()
      {
        assertEqual(getExpectedProp1(), dto.prop1);
      }
      ...
    }
    
    
    class WithDtoGeneratedFromXxx : AbstractDtoTest
    {
      DTO createDto() { return GenerateDtoFromXxx(); }
      abstract SomeType getExpectedProp1() { return new SomeType(); }
      ...
    }
    
    如果您需要在不同的场景下测试DTO生成,并选择最后一种方法,那么编写所有这些测试可能很快就会变得单调乏味。如果是这种情况,您可以实现一个抽象的基本fixture,它省略了有关如何创建DTO和为派生类设置预期属性的详细信息。伪代码:

    public class WithDtoGeneratedFromXxx : TestFixture
    {
      DTO dto = null;
    
      public void setUp()
      {
        dto = GenerateDtoFromXxx();
        expectedProp1 = "";
        ...
      }
    
      void testProp1IsGeneratedCorrectly()
      {
        assertEqual(expectedProp1, dto.prop1);
      }
      ...
    }
    
    abstract class AbstractDtoTest : TestFixture
    {
      DTO dto;
      SomeType expectedProp1;
    
      abstract DTO createDto();
      abstract SomeType getExpectedProp1();
    
      void setUp()
      {
        dto = createDto();
        ...
      }
    
      void testProp1IsGeneratedCorrectly()
      {
        assertEqual(getExpectedProp1(), dto.prop1);
      }
      ...
    }
    
    
    class WithDtoGeneratedFromXxx : AbstractDtoTest
    {
      DTO createDto() { return GenerateDtoFromXxx(); }
      abstract SomeType getExpectedProp1() { return new SomeType(); }
      ...
    }
    

    真的建议你吃吗?是的,有人提出了这个建议。他们说得对吗?我不这么认为。我发现很难相信这些人已经在真实代码上工作了很长时间

    因此,imagine您有一个要进行单元测试的mutator方法。变异子有某种效果,或者说是一些效果,你想检查一下。通常情况下,变异子的预期效果数量很少,因为许多效果表明变异子的设计过于复杂。由于每个效果有一个断言,每个断言有一个测试用例,您不需要每个mutator有很多测试用例,因此建议看起来并不那么糟糕

    但这种推理的缺陷在于,这些测试只关注突变子的预期效果。但是如果变异子有缺陷,它可能会产生意想不到的不良副作用。测试做出了一个愚蠢的假设,即代码没有完整的bug类,并且未来的重构不会引入这样的bug。在最初编写该方法时,作者可能很清楚,特定的副作用是不可能的,但重构和添加新功能可能会使这种副作用成为可能

    测试长寿命代码的唯一安全方法是检查变异子没有意外的副作用。但是你如何测试这些呢?大多数类都有一些不变量:没有一个变种人可以改变的东西。例如,容器的
    size
    方法永远不会返回负值。实际上,每个不变量都是每个变元(以及构造函数)的后条件。每个变异子通常也有一组不变量,用于描述它不进行什么样的更改。例如,
    sort
    方法不会更改容器的长度。类和mutator不变量实际上是每个mutator调用的post条件。为它们添加断言是检查意外副作用的唯一方法

    那么,只需添加更多的测试用例?在实践中,不变量的数量乘以要测试的变异体的数量是很大的,因此每个测试一个断言会导致许多测试用例。关于不变量的信息分散在许多测试用例中。调整一个不变量的设计更改将需要更改许多测试用例。它变得不切实际。最好为一个变种人设置参数化测试用例,使用几个断言检查变种人的几个不变量


    JUnit5的作者似乎也同意这一点。它们为在一个测试用例中检查多个断言提供了一种方法。

    这种结构可以帮助您拥有一个大断言(其中包含小断言)


    DTO是POJO..,请求也是POJO。因此,assertEqual很可能看起来像assertEqual(“abc”,dt)