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