JUnit 5 的新 @Nested 注解特性可用于创建更干净的单元测试类,使用任意嵌套的内部类作为单个测试类中附加的测试组成单元。这使您能够将测试集中在一起,同时能以不同方式初始化它们来模拟不同的运行时条件。
对代码执行单元测试,可以测试您的代码可能遇到的所有情况,但是如果无法理解单元测试报告,这又有什么用呢?将相关测试方法嵌套在同一个测试类中,再结合新的 @DisplayName 注解,会让您的单元测试在视觉上大变样。您的单元测试报告从未像现在这么美观!
JUnit 5 扩展模型遵循 JUnit 5 团队的“扩展优于特性”的核心原则,并定义了很多扩展点(已经为它们提供了默认实现,但是还应该为它们创建扩展以覆盖默认行为)。这些扩展是您实现扩展点并注册到 JUnit 5 框架的回调接口,在运行单元测试的时候,当到达该扩展点时,就会调用您的代码。
您可能从不需要编写自定义扩展,但好处在于,需要的时候,您可以使用工具供应商使用的相同 API。为了支持 JUnit,工具供应商常常使用其内部 API,这限制了 JUnit 维护者在该框架的演化过程中能做的事情(这是相互矛盾的)。扩展模型通过为您和工具供应商提供一致的公共 API,将推动 JUnit 不断演化。
而且,所有公共 API 方法都使用 @API 和类似下面这样的值进行注释:
Experimental – 该特性是新的,JUnit 团队欢迎提供反馈。
Internal – 该特性供 JUnit 内部使用,我们应该避免使用它。
Maintained – 该特性至少在下一个次要版本发布之前很不错(会以向后兼容方式更改)。
扩展让JUnit 5 变得如此灵活,应该使用 JUnit Yoga 作为它的代号。
谈到扩展,JUnit 5 提供了两个扩展点 – ContainerExecutionCondition 和 TestExecutionCondition接口 – 它们组成了测试执行条件 API。当然,JUnit 4 允许我们通过 @Ignore 注解测试类或方法,JUnit 5 通过 @Disabled 注解来提供此行为。
但是如果您愿意,可以为两种测试执行条件 API 方法中的一个创建自己的自定义扩展,以检查当前正在运行的测试,并动态决定是否要运行一个测试类(通过 ContainerExecutionCondition)或方法(通过 TestExecutionCondition)。
等等,还有更多!JUnit 5 甚至允许您停用条件,比如说通过 @Disabled 禁用单元测试,所以您也可以运行这些单元测试来查看其是否仍然处于禁用状态。太棒了!
Lambda表达式的编写紧凑代码能力非常方便。
现在该特性已通过使用一些新函数型接口(比如 Executable)而集成到 JUnit 5 中。Assertions.assertAll() 之类的方法接受 Executable... 作为一个可变参数,您可以将该参数替换为一个 lambda 表达式。
断言(Assertions)和前置条件(Assumptions)中的其他方法使用诸如 Supplier
编写单元测试可能很枯燥,尤其是当您需要使用不同的输入来运行同一个单元测试时。在这种情况下,您必须编写多个单元测试方法,或者在一个方法中编写多个块(这可能给您的单元测试留下不好的印象)。您是否曾经希望让您的单元测试更加动态?如果您可以将一个单元测试方法设置为运行多次,每次使用不同的输入值,那该多好?
借助参数化测试,JUnit 5 允许您指定一个或多个来源,为您的单元测试方法提供其运行测试所需的参数值。来源生成参数值,JUnit 5 然后将参数值传递给您的单元测试方法(一次一个),直到传完来源中的所有参数值。以下是 JUnit 5 提供的一些开箱即用来源的示例:
@ValueSource – 用于指定一个基本文字数组,这些文字将一次一个地传递给测试方法。
@MethodSource – 用于在测试类中指定一个或多个静态的无参数方法,它们返回一个 Stream、Iterable、Iterator 或参数值 数组供您的测试方法使用。
@CsvSource – 用于指定一组传递给测试方法的文字(基于测试方法中的参数数量而分解为元组)。
@ArgumentSource – 用于创建您自己的自定义参数提供程序。
附加:参数化测试代码示例:
package com.test.atchk.blockchain.chaincode.contract;
import com.test.atchk.blockchain.chaincode.builder.CampaignBeBuilder;
import com.test.atchk.blockchain.chaincode.builder.FulfilledBeResultBuilder;
import com.test.atchk.blockchain.chaincode.builder.TestBuilder;
import com.test.atchk.blockchain.chaincode.enumeration.StateEventStatus;
import com.test.atchk.blockchain.chaincode.model.*;
import com.test.atchk.blockchain.chaincode.service.CampaignBeService;
import com.test.atchk.blockchain.chaincode.service.CsService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.math.BigDecimal;
import java.time.DayOfWeek;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class CampaignBeahaviourSpendWithTxnValueTestV2 {
//the service of the test
CampaignBeService testBeService;
//the the arguments of the method
String testID = "CAMPAIGN001";
String testKey = "TEAM001";
List tests = Arrays.asList("MEM001");
String testEnrolDate = OffsetDateTime.now().minusDays(7).toString();
//Txn ID
String currentTestID = "TXN001";
String currentTestID1 = "TXN002";
String currentTestID2 = "TXN003";
//BeRestriction
CaTestDTO.BeRestriction totalNumOfTimesRestriction;
CaTestDTO.BeRestriction qualifyingMinimumTxnValue;
CaTestDTO.BeRestriction qualifyingLatLngRestriction;
CaTestDTO.BeRestriction qualifyingPaymentTypeRestriction;
CaTestDTO.BeRestriction qualifyingDaysOfWeekRestriction;
CaTestDTO.BeRestriction qualifyingTimeOfDayRestriction;
CaTestDTO.BeRestriction qualifyingTimeFrameRestriction;
CaTestDTO.BeRestriction prerequisiteBeRestriction;
//builder the expected outcomes
List expectedOutcomes = Arrays.asList(new OutcomeDTO()
.setAmount(new BigDecimal(100))
.setOrigin(new OutcomeDTO.Origin("OUTCOME001", "", "", "")) // TODO: [assignee:Fiona] help fill in testID and TeamID, and txnIDs
.setMemberId("MEM001")
.setType("point"));
//builder the expected result
FulfilledBeResult expectGetResults = FulfilledBeResultBuilder.fulfilledBeResultResult(true, Arrays.asList(currentTestID, currentTestID1, currentTestID2), expectedOutcomes);
FulfilledBeResult expectNoResults = FulfilledBeResultBuilder.fulfilledBeResultResult(false, new ArrayList<>(), null);
@BeforeAll
public void setUp() {
testBeService = CampaignBeService.getInstance();
totalNumOfTimesRestriction = CampaignBeBuilder.buildTotalNumOfTimesEventRestriction("totalNumEvent_001", 3, true);
qualifyingLatLngRestriction = CampaignBeBuilder.buildQualifyingLatLngTxnRestriction("LatLng_001", new BigDecimal(1), new BigDecimal(2), new BigDecimal(3));
qualifyingPaymentTypeRestriction = CampaignBeBuilder.buildQualifyingPaymentTypeTxnRestriction("PAYMENT_001", "cash");
qualifyingDaysOfWeekRestriction = CampaignBeBuilder.buildQualifyingDaysOfWeekTxnRestriction("Days_Of_WEEK_ID_001", Arrays.asList(
DayOfWeek.MONDAY,
DayOfWeek.TUESDAY,
DayOfWeek.WEDNESDAY,
DayOfWeek.THURSDAY,
DayOfWeek.FRIDAY,
DayOfWeek.SATURDAY,
DayOfWeek.SUNDAY
));
qualifyingTimeOfDayRestriction = CampaignBeBuilder.buildQualifyingTimeOfDayTxnRestriction("TIME_Of_DAY_001", Arrays.asList(
new TimeOfDayDTO().setFrom("08:10").setTo("10:14"),
new TimeOfDayDTO().setFrom("20:11").setTo("23:59")
));
qualifyingTimeFrameRestriction = CampaignBeBuilder.buildQualifyingTimeframeTxnRestriction("TIME_FRAME_ID_001", 123456);
prerequisiteBeRestriction = CampaignBeBuilder.buildPrerequisiteBeEventRestriction("prerequisiteBe_001", "A", false);
qualifyingMinimumTxnValue = CampaignBeBuilder.buildQualifyingMinimumTxnValueTxnRestriction("Min_NumTxn_001", new BigDecimal(100));
}
Stream providerTheTestArgumentsOfPas() {
CaTestDTO.BeEvent beEvent_4_1_1 = CampaignBeBuilder.buildPurchaseSpendWithTestValue("CAMPAIGN001", false, totalNumOfTimesRestriction, qualifyingMinimumTxnValue);
CaTestDTO.Be be_4_1_1 = CampaignBeBuilder.buildBeWithSingleEventWithFixedPointOutcome("Behave001", beEvent_4_1_1, new BigDecimal(100));
CaTestDTO.BeEvent beEvent_4_1_2 = CampaignBeBuilder.buildPurchaseSpendWithTestValue("CAMPAIGN001", false, totalNumOfTimesRestriction, qualifyingMinimumTxnValue).addRestriction(qualifyingLatLngRestriction);
CaTestDTO.Be be_4_1_2 = CampaignBeBuilder.buildBeWithSingleEventWithFixedPointOutcome("Behave001", beEvent_4_1_2, new BigDecimal(100));
CaTestDTO.BeEvent beEvent_4_1_3 = CampaignBeBuilder.buildPurchaseSpendWithTestValue("CAMPAIGN001", false, totalNumOfTimesRestriction, qualifyingMinimumTxnValue).addRestriction(qualifyingPaymentTypeRestriction);
CaTestDTO.Be be_4_1_3 = CampaignBeBuilder.buildBeWithSingleEventWithFixedPointOutcome("Behave001", beEvent_4_1_3, new BigDecimal(100));
CaTestDTO.BeEvent beEvent_4_1_4 = CampaignBeBuilder.buildPurchaseSpendWithTestValue("CAMPAIGN001", false, totalNumOfTimesRestriction, qualifyingMinimumTxnValue).addRestriction(qualifyingDaysOfWeekRestriction);
CaTestDTO.Be be_4_1_4 = CampaignBeBuilder.buildBeWithSingleEventWithFixedPointOutcome("Behave001", beEvent_4_1_4, new BigDecimal(100));
CaTestDTO.BeEvent beEvent_4_1_5 = CampaignBeBuilder.buildPurchaseSpendWithTestValue("CAMPAIGN001", true, totalNumOfTimesRestriction, qualifyingMinimumTxnValue).addRestriction(qualifyingTimeOfDayRestriction);
CaTestDTO.Be be_4_1_5 = CampaignBeBuilder.buildBeWithSingleEventWithFixedPointOutcome("Behave001", beEvent_4_1_5, new BigDecimal(100));
CaTestDTO.BeEvent beEvent_4_1_6 = CampaignBeBuilder.buildPurchaseSpendWithTestValue("CAMPAIGN001", true, totalNumOfTimesRestriction, qualifyingMinimumTxnValue).addRestriction(qualifyingTimeFrameRestriction);
CaTestDTO.Be be_4_1_6 = CampaignBeBuilder.buildBeWithSingleEventWithFixedPointOutcome("Behave001", beEvent_4_1_6, new BigDecimal(100));
CaTestDTO.BeEvent beEvent_4_1_7 = CampaignBeBuilder.buildPurchaseSpendWithTestValue("CAMPAIGN001", false, totalNumOfTimesRestriction, qualifyingMinimumTxnValue).addRestriction(prerequisiteBeRestriction);
CaTestDTO.Be be_4_1_7 = CampaignBeBuilder.buildBeWithSingleEventWithFixedPointOutcome("Behave001", beEvent_4_1_7, new BigDecimal(100));
return Stream.of(
Arguments.of(this.transactionTest_4_1_1_GetResult(),be_4_1_1,expectGetResults),
Arguments.of(this.transactionTest_4_1_1_NoResult(),be_4_1_1,expectNoResults),
Arguments.of(this.transactionTest_4_1_2_GetResult(),be_4_1_2,expectGetResults),
Arguments.of(this.transactionTest_4_1_2_NoResult(),be_4_1_2,expectNoResults),
Arguments.of(this.transactionTest_4_1_3_GetResult(),be_4_1_3,expectGetResults),
Arguments.of(this.transactionTest_4_1_3_NoResult(),be_4_1_3,expectNoResults),
Arguments.of(this.transactionTest_4_1_4_GetResult(),be_4_1_4,expectGetResults),
Arguments.of(this.transactionTest_4_1_4_NoResult(),be_4_1_4,expectNoResults),
Arguments.of(this.transactionTest_4_1_5_GetResult(),be_4_1_5,expectGetResults),
Arguments.of(this.transactionTest_4_1_5_NoResult(),be_4_1_5,expectNoResults),
Arguments.of(this.transactionTest_4_1_6_GetResult(),be_4_1_6,expectGetResults),
Arguments.of(this.transactionTest_4_1_6_NoResult(),be_4_1_6,expectNoResults),
Arguments.of(this.transactionTest_4_1_7_GetResult(),be_4_1_7,expectGetResults),
Arguments.of(this.transactionTest_4_1_7_NoResult(),be_4_1_7,expectNoResults)
);
}
@ParameterizedTest
@MethodSource("providerTheTestArgumentsOfPas")
public void spendWithTxnValueTest(CpsDTO cps, CaTestDTO.Be be, FulfilledBeResult expectResult) {
//testBeService.evaluateCampaignBe()为真正需要测试的方法
FulfilledBeResult result = testBeService.evaluateCampaignBe(cps,
be,
tests,
currentTestID1);
Assertions.assertEquals(expectResult, result);
}
public CpsDTO transactionTest_4_1_1_GetResult() {
CpsDTO cps = CsService.createNewPas(testID + ":" + testKey, testID, tests, testEnrolDate);
cps.addTestDetails(
new TestBuilder()
.basic(currentTestID, OffsetDateTime.now().minusDays(6).toString())
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID1, OffsetDateTime.now().minusDays(7).toString())
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID2, OffsetDateTime.now().minusDays(8).toString())
.payment(100)
.build()
);
return cps;
}
public CpsDTO transactionTest_4_1_1_NoResult() {
CpsDTO cps = CsService.createNewPas(testID + ":" + testKey, testID, tests, testEnrolDate);
cps.addTestDetails(
new TestBuilder()
.basic(currentTestID, OffsetDateTime.now().minusDays(3).toString())
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID1, OffsetDateTime.now().minusDays(9).toString())
.payment(100)
.build()
);
return cps;
}
public CpsDTO transactionTest_4_1_2_GetResult() {
CpsDTO cps = CsService.createNewPas(testID + ":" + testKey, testID, tests, testEnrolDate);
cps.addTestDetails(
new TestBuilder()
.basic(currentTestID, OffsetDateTime.now().minusDays(6).toString())
.location("LatLng001", new BigDecimal(1), new BigDecimal(2))
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID1, OffsetDateTime.now().minusDays(7).toString())
.location("LatLng001", new BigDecimal(1), new BigDecimal(2))
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID2, OffsetDateTime.now().minusDays(8).toString())
.location("LatLng001", new BigDecimal(1), new BigDecimal(2))
.payment(100)
.build()
);
return cps;
}
public CpsDTO transactionTest_4_1_2_NoResult() {
CpsDTO cps = CsService.createNewPas(testID + ":" + testKey, testID, tests, testEnrolDate);
cps.addTestDetails(
new TestBuilder()
.basic(currentTestID, OffsetDateTime.now().minusDays(6).toString())
.location("LatLng_001", new BigDecimal(2), new BigDecimal(2))
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID1, OffsetDateTime.now().minusDays(7).toString())
.location("LatLng_001", new BigDecimal(1), new BigDecimal(2))
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID2, OffsetDateTime.now().minusDays(8).toString())
.location("LatLng_001", new BigDecimal(1), new BigDecimal(2))
.payment(100)
.build()
);
return cps;
}
public CpsDTO transactionTest_4_1_3_GetResult() {
CpsDTO cps = CsService.createNewPas(testID + ":" + testKey, testID, tests, testEnrolDate);
cps.addTestDetails(
new TestBuilder()
.basic(currentTestID, OffsetDateTime.now().minusDays(6).toString())
.payment(new BigDecimal(100), "cash")
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID1, OffsetDateTime.now().minusDays(7).toString())
.payment(new BigDecimal(100), "cash")
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID2, OffsetDateTime.now().minusDays(8).toString())
.payment(new BigDecimal(100), "cash")
.build()
);
return cps;
}
public CpsDTO transactionTest_4_1_3_NoResult() {
CpsDTO cps = CsService.createNewPas(testID + ":" + testKey, testID, tests, testEnrolDate);
cps.addTestDetails(
new TestBuilder()
.basic(currentTestID, OffsetDateTime.now().minusDays(6).toString())
.payment(new BigDecimal(100), "cash")
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID1, OffsetDateTime.now().minusDays(7).toString())
.payment(new BigDecimal(100), "cash")
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID2, OffsetDateTime.now().minusDays(8).toString())
.payment(new BigDecimal(100), "alipay")
.build()
);
return cps;
}
public CpsDTO transactionTest_4_1_4_GetResult() {
CpsDTO cps = CsService.createNewPas(testID + ":" + testKey, testID, tests, testEnrolDate);
cps.addTestDetails(
new TestBuilder()
.basic(currentTestID, OffsetDateTime.now().minusDays(6).toString())
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID1, OffsetDateTime.now().minusDays(7).toString())
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID2, OffsetDateTime.now().minusDays(8).toString())
.payment(100)
.build()
);
return cps;
}
public CpsDTO transactionTest_4_1_4_NoResult() {
CpsDTO cps = CsService.createNewPas(testID + ":" + testKey, testID, tests, testEnrolDate);
cps.addTestDetails(
new TestBuilder()
.basic(currentTestID, OffsetDateTime.now().minusDays(6).toString())
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID1, OffsetDateTime.now().minusDays(8).toString())
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID2, OffsetDateTime.now().minusDays(10).toString())
.payment(100)
.build()
);
return cps;
}
public CpsDTO transactionTest_4_1_5_GetResult() {
CpsDTO cps = CsService.createNewPas(testID + ":" + testKey, testID, tests, testEnrolDate);
cps.addTestDetails(
new TestBuilder()
.basic(currentTestID, "2019-12-13T22:00:00+08:00")
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID1, "2019-12-12T22:00:00+08:00")
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID2, "2019-12-11T22:00:00+08:00")
.payment(100)
.build()
);
return cps;
}
public CpsDTO transactionTest_4_1_5_NoResult() {
CpsDTO cps = CsService.createNewPas(testID + ":" + testKey, testID, tests, testEnrolDate);
cps.addTestDetails(
new TestBuilder()
.basic(currentTestID, OffsetDateTime.now().minusDays(6).toString())
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID1, OffsetDateTime.now().minusDays(8).toString())
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID2, OffsetDateTime.now().minusDays(10).toString())
.payment(100)
.build()
);
return cps;
}
public CpsDTO transactionTest_4_1_6_GetResult() {
CpsDTO cps = CsService.createNewPas(testID + ":" + testKey, testID, tests, testEnrolDate);
cps.addTestDetails(
new TestBuilder()
.basic(currentTestID, OffsetDateTime.now().minusDays(6).toString())
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID1, OffsetDateTime.now().minusDays(7).toString())
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID2, OffsetDateTime.now().minusDays(8).toString())
.payment(100)
.build()
);
return cps;
}
public CpsDTO transactionTest_4_1_6_NoResult() {
CpsDTO cps = CsService.createNewPas(testID + ":" + testKey, testID, tests, testEnrolDate);
cps.addTestDetails(
new TestBuilder()
.basic(currentTestID, OffsetDateTime.now().minusDays(6).toString())
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID1, OffsetDateTime.now().minusDays(8).toString())
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID2, OffsetDateTime.now().minusDays(10).toString())
.payment(100)
.build()
);
return cps;
}
public CpsDTO transactionTest_4_1_7_GetResult() {
CpsDTO cps = CsService.createNewPas(testID + ":" + testKey, testID, tests, testEnrolDate);
cps.addTestDetails(
new TestBuilder()
.basic(currentTestID, OffsetDateTime.now().minusDays(6).toString())
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID1, OffsetDateTime.now().minusDays(7).toString())
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID2, OffsetDateTime.now().minusDays(8).toString())
.payment(100)
.build()
);
cps.addEvent("A", new CpsDTO.StateEvent().setStatus(StateEventStatus.FULFILLED));
return cps;
}
public CpsDTO transactionTest_4_1_7_NoResult() {
CpsDTO cps = CsService.createNewPas(testID + ":" + testKey, testID, tests, testEnrolDate);
cps.addTestDetails(
new TestBuilder()
.basic(currentTestID, OffsetDateTime.now().minusDays(6).toString())
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID1, OffsetDateTime.now().minusDays(7).toString())
.payment(100)
.build()
).addTestDetails(
new TestBuilder()
.basic(currentTestID2, OffsetDateTime.now().minusDays(8).toString())
.payment(100)
.build()
);
return cps;
}
}
还有其他一些有用的特性,比如: