彩票走势图

如何创建JUnit参数化测试

原创|使用教程|编辑:郑恭琳|2020-12-07 11:29:15.390|阅读 267 次

概述:参数化测试是定义和运行多个测试用例的好方法,它们之间的唯一区别是数据。在这里,我们看一下JUnit测试常用的三个不同框架。

# 慧都年终大促·界面/图表报表/文档/IDE等千款热门软控件火热促销中 >>

相关链接:


参数化测试是定义和运行多个测试用例的好方法,它们之间的唯一区别是数据。在这里,我们看一下JUnit测试常用的三个不同框架。

在编写单元测试时,通常会在测试方法本身中初始化方法输入参数和预期结果。在某些情况下,使用少量输入就足够了;但是,在某些情况下,我们需要使用大量的值来验证代码中的所有功能。参数化测试是定义和运行多个测试用例的好方法,它们之间的唯一区别是数据。他们可以验证各种值的代码行为,包括边界情况。参数化测试可以增加代码覆盖率,并确保代码按预期运行。

有许多用于Java的良好参数化框架。在本文中,我们将研究JUnit测试常用的三个不同框架,并将它们与每个测试的结构示例进行比较。最后,我们将探索如何简化和加速参数化测试的创建。


JUnit参数化测试框架

让我们比较一下三种最常见的框架:JUnit 4JunitParamsJUnit5。每个JUnit参数化框架都有自己的优点和缺点。

JUnit 4

优点:

  • 这是JUnit 4内置的参数化框架,因此不需要其他外部依赖项。
  • 它支持Java的较早版本(JDK 7和更早版本)。

缺点:

  • 测试类使用字段和构造函数来定义参数,这会使测试更加冗长。
  • 对于每个要测试的方法,它都需要一个单独的测试类。

JunitParams

优点:

  • 通过允许将参数直接传递给测试方法来简化参数语法。
  • 每个测试类允许多个测试方法(每个方法都有自己的数据)。
  • 支持CSV数据源以及基于注释的值(无需方法)。

缺点:

  • 要求使用JunitParams依赖项配置项目。
  • 在运行和调试测试时,必须运行该类中的所有测试-无法在测试类中运行单个测试方法。

JUnit 5

优点:

  • 该参数化框架内置于JUnit 5中,并改进了JUnit 4中包含的内容。
  • 具有简化的参数语法,例如JunitParams
  • 支持多种数据集源类型,包括CSV和注释(无需方法)。
  • 即使不需要额外的依赖项,也需要多个.jar

缺点:

  • 需要Java 8和更高版本的构建系统(4.6版或Maven Surefire 2.21版)。
  • 您的IDE可能还不支持(在撰写本文时,仅EclipseIntelliJ支持JUnit 5)。


例子

例如,假设我们有一种处理银行贷款请求的方法。我们可能会编写一个单元测试,以指定贷款请求金额、预付定金金额和其他值。然后,我们将创建断言来验证响应——贷款可以被批准或拒绝,并且响应可以指定贷款的条款。

例如:

public LoanResponse requestLoan(float loanAmount, float downPayment, float availableFunds)
{
LoanResponse response = new LoanResponse();
response.setApproved(true);
if (availableFunds < downPayment) {
response.setApproved(false);
response.setMessage(“error.insufficient.funds.for.down.payment“);
return response;
}
if (downPayment / loanAmount < 0.1) {
response.setApproved(false);
response.setMessage(“error.insufficient.down.payment“);
}
return response;
}

首先,让我们看一下上述方法的常规测试:

@Test
public void testRequestLoan() throws Throwable
{
// Given
LoanProcessor underTest = new LoanProcessor();
// When
LoanResponse result = underTest.requestLoan(1000f, 200f, 250f);
// Then
assertNotNull(result);
assertTrue(result.isApproved());
assertNull(result.getMessage());
}

在此示例中,我们通过请求1000美元的贷款,200美元的首付并指示请求者有250美元的可用资金来测试我们的方法。然后,测试将验证贷款是否已获批准,并且未在响应中提供任何信息。

为了确保对我们的requestLoan()方法进行了彻底的测试,我们需要使用各种预付款,请求的贷款金额和可用资金进行测试。例如,让我们测试一笔零首付的100万美元贷款请求,该请求应被拒绝。我们可以简单地用不同的值复制现有测试,但是由于测试逻辑相同,因此参数化测试效率更高。

我们将参数化请求的贷款金额,预付款和可用资金以及预期的结果:贷款是否被批准,以及在验证后返回的消息。每组请求数据及其预期结果将成为其自己的测试用例。


使用JUnit 4参数化的示例参数化测试

让我们从一个Junit 4 Parameterized示例开始。要创建参数化测试,我们首先需要定义测试的变量。我们还需要包括一个构造函数来初始化它们:

@RunWith(Parameterized.class)
public class LoanProcessorParameterizedTest {
float loanAmount;
float downPayment;
float availableFunds;
boolean expectApproved;
String expectedMessage;
public LoanProcessorParameterizedTest(float loanAmount, float downPayment,
float availableFunds, boolean expectApproved, String expectedMessage)
{
this.loanAmount = loanAmount;
this.downPayment = downPayment;
this.availableFunds = availableFunds;
this.expectApproved = expectApproved;
this.expectedMessage = expectedMessage;
}
// …
}

在这里,我们看到该测试使用@RunWith批注指定该测试将与Junit4参数化运行器一起运行。 该跑步者知道正在寻找一种方法,该方法将为测试提供值集(用@Parameters注释),正确初始化测试并运行多行测试。

请注意,每个参数都在测试类中定义为一个字段,并且构造函数初始化这些值(如果您不想创建构造函数,也可以使用@Parameter注释将值注入字段)。对于值集中的每一行,参数化运行器将实例化测试类并运行该类中的每个测试。

让我们添加一个为参数化运行器提供参数的方法:

@Parameters(name = “Run {index}: loanAmount={0}, downPayment={1}, availableFunds={2}, expectApproved={3}, expectedMessage={4}“)
public static Iterable<Object[]> data() throws Throwable
{
return Arrays.asList(new Object[][] {
{ 1000.0f, 200.0f, 250.0f, true, null }
});
}

值集通过data()方法构建为“对象数组列表”,并使用@Parameters进行注释。请注意,@ Parameters使用占位符设置测试的名称,将在运行测试时将其替换。稍后我们将看到,这使得查看测试结果中的值更加容易。当前,只有一行数据用于测试应批准贷款的情况。我们可以添加更多行以增加被测方法的覆盖范围。

@Parameters(name = “Run {index}: loanAmount={0}, downPayment={1}, availableFunds={2}, expectApproved={3}, expectedMessage={4}“)
public static Iterable<Object[]> data() throws Throwable
{
return Arrays.asList(new Object[][] {
{ 1000.0f, 200.0f, 250.0f, true, null },
{ 1000.0f, 50.0f, 250.0f, false, “error.insufficient.down.payment“ },
{ 1000.0f, 200.0f, 150.0f, false, “error.insufficient.funds.for.down.payment“ }
});
}

 在这里,我们有一个测试案例,其中贷款将被批准,而另外两个案例中,由于不同的原因而不应被批准。我们可能要添加使用零或负值的行以及测试边界条件。

现在我们准备创建测试方法:

@Test
public void testRequestLoan() throws Throwable
{
// Given
LoanProcessor underTest = new LoanProcessor();
// When
LoanResponse result = underTest.requestLoan(loanAmount, downPayment, availableFunds);
// Then
assertNotNull(result);
assertEquals(expectApproved, result.isApproved());
assertEquals(expectedMessage, result.getMessage());
}

在这里,我们在调用requestLoan()方法并验证结果时引用这些字段。


JunitParams示例

JunitParams库通过允许将参数直接传递给测试方法来简化参数化测试语法。参数值由单独的方法提供,其名称在@Parameters批注中引用。

@RunWith(JUnitParamsRunner.class)
public class LoanProcessorParameterizedTest2 {
@Test
@Parameters(method = “testRequestLoan_Parameters“)
public void testRequestLoan(float loanAmount, float downPayment, float availableFunds,
boolean expectApproved, String expectedMessage) throws Throwable
{
…
}
@SuppressWarnings(“unused“)
private static Object[][] testRequestLoan_Parameters() throws Throwable {
// Parameters: loanAmount={0}, downPayment={1}, availableFunds={2}, expectApproved={3}, expectedMessage={4}
return new Object[][] {
{ 1000.0f, 200.0f, 250.0f, true, null },
{ 1000.0f, 50.0f, 250.0f, false, “error.insufficient.down.payment“},
{ 1000.0f, 200.0f, 150.0f, false, “error.insufficient.funds.for.down.payment“ }
};
}
}

JunitParams的另一个好处是,除了在代码中提供值外,它还支持使用CSV文件提供值。这允许将测试与数据分离,并在不更新代码的情况下更新数据值。


Junit 5示例

JUnit 5解决了JUnit 4的一些局限和缺点。与JunitParams一样,Junit 5也简化了参数化测试的语法。语法上最重要的变化是:

  • 测试方法使用@ParameterizedTest而不是@Test进行注释
  • 测试方法直接接受参数,而不是使用字段和构造函数
  • 不再需要@RunWith批注

Junit 5中定义相同的示例如下所示:

public class LoanProcessorParameterizedTest {
@ParameterizedTest(name=“Run {index}: loanAmount={0}, downPayment={1}, availableFunds={2}, expectApproved={3}, expectedMessage={4}“)
@MethodSource(“testRequestLoan_Parameters“)
public void testRequestLoan(float loanAmount, float downPayment, float availableFunds,
boolean expectApproved, String expectedMessage) throws Throwable
{
…
}
static Stream<Arguments> testRequestLoan_Parameters() throws Throwable {
return Stream.of(
Arguments.of(1000.0f, 200.0f, 250.0f, true, null),
Arguments.of(1000.0f, 50.0f, 250.0f, false, “error.insufficient.down.payment“),
Arguments.of(1000.0f, 200.0f, 150.0f, false, “error.insufficient.funds.for.down.payment“)
);
}
}


高效地创建参数化测试

可以想象,编写上面的参数化测试可能会有些麻烦。对于每个参数化的测试框架,都需要正确编写一些样板代码。记住正确的结构可能很困难,而且要花时间写出来。为了使此操作更容易,您可以使用Parasoft Jtest自动生成参数化测试,就像上面描述的那样。为此,只需选择要为其生成测试的方法(在EclipseIntelliJ中),即可:


使用默认值和断言生成测试。然后,您可以使用实际输入值和断言配置测试,并将更多数据行添加到data()方法。


运行参数化测试

Parasoft Jtest可以直接在EclipseIntelliJ中运行参数化测试。


Eclipse中的JUnit视图

请注意,如图所示,每个测试的名称都包含来自数据集的输入值和预期结果值。由于在每种情况下都会显示输入参数和预期的输出,因此可以使测试失败时的调试更加容易。

您还可以使用Parasoft Jtest的“全部运行”操作:


Parasoft Jtest中的“流树”视图

它分析测试流程并提供有关先前测试运行的详细信息。 这使您可以查看测试中发生的情况,而无需使用断点或调试语句重新运行测试。例如,您可以在“变量”视图中看到参数化的值:

Parasoft Jtest中的变量视图


结论

我们审查的三个框架中的每一个都是不错的选择,并且运作良好。如果使用JUnit 4,则由于测试类的设计更加简洁,并且能够在同一类中定义多个测试方法,因此我倾向于使用JunitParams而不是内置的JUnit 4 Parameterized框架。但是,如果使用JUnit 5,我建议您使用内置的JUnit 5框架,因为它可以解决JUnit 4中的缺点,并且不需要额外的库。我还喜欢使用Parasoft Jtest的单元测试功能来提高参数化测试的创建、执行和调试的效率。


标签:

本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@capbkgr.cn


为你推荐

  • 推荐视频
  • 推荐活动
  • 推荐产品
  • 推荐文章
  • 慧都慧问
扫码咨询


添加微信 立即咨询

电话咨询

客服热线
023-68661681

TOP