原创|使用教程|编辑:郑恭琳|2020-12-07 11:29:15.390|阅读 267 次
概述:参数化测试是定义和运行多个测试用例的好方法,它们之间的唯一区别是数据。在这里,我们看一下JUnit测试常用的三个不同框架。
# 慧都年终大促·界面/图表报表/文档/IDE等千款热门软控件火热促销中 >>
相关链接:
参数化测试是定义和运行多个测试用例的好方法,它们之间的唯一区别是数据。在这里,我们看一下JUnit测试常用的三个不同框架。
在编写单元测试时,通常会在测试方法本身中初始化方法输入参数和预期结果。在某些情况下,使用少量输入就足够了;但是,在某些情况下,我们需要使用大量的值来验证代码中的所有功能。参数化测试是定义和运行多个测试用例的好方法,它们之间的唯一区别是数据。他们可以验证各种值的代码行为,包括边界情况。参数化测试可以增加代码覆盖率,并确保代码按预期运行。
有许多用于Java的良好参数化框架。在本文中,我们将研究JUnit测试常用的三个不同框架,并将它们与每个测试的结构示例进行比较。最后,我们将探索如何简化和加速参数化测试的创建。
让我们比较一下三种最常见的框架:JUnit 4,JunitParams和JUnit5。每个JUnit参数化框架都有自己的优点和缺点。
JUnit 4
优点:
缺点:
JunitParams
优点:
缺点:
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 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库通过允许将参数直接传递给测试方法来简化参数化测试语法。参数值由单独的方法提供,其名称在@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 4的一些局限和缺点。与JunitParams一样,Junit 5也简化了参数化测试的语法。语法上最重要的变化是:
在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自动生成参数化测试,就像上面描述的那样。为此,只需选择要为其生成测试的方法(在Eclipse或IntelliJ中),即可:
使用默认值和断言生成测试。然后,您可以使用实际输入值和断言配置测试,并将更多数据行添加到data()方法。
Parasoft Jtest可以直接在Eclipse和IntelliJ中运行参数化测试。
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