JUnit5中抽象类实现TestWatcher接口方法未执行求助
JUnit4迁移JUnit5后TestWatcher方法未执行问题
问题描述
将Selenium项目从JUnit4迁移至JUnit5后,原JUnit4中抽象类里负责测试失败截图的TestWatcher逻辑,在迁移到JUnit5的TestWatcher接口后,测试类继承抽象类时相关方法并未执行。
JUnit4原有实现
原JUnit4抽象类中的TestWatcher规则实现(核心为测试失败时截图):
@Rule public TestRule watchman = new TestWatcher() { @Override public Statement apply(Statement base, Description description) { return super.apply(base, description); } @Override protected void succeeded(Description description) { mainLogger.info(description.getDisplayName() + " " + "success!"); } /* 测试失败时记录日志并截图 */ @Override protected void failed(Throwable e, Description description) { mainLogger.error("Test ({}) failed because of exception {}", description, e); screenshothandler.takeScreenshotForAllure(); } @Override protected void starting(Description description) { super.starting(description); } @Override protected void finished(Description description) { super.finished(description); if (getDriver() != null) { screenshothandler.tearDown(); } } };
JUnit5迁移后的抽象类代码
迁移后抽象类SeleniumTestWrapper实现了JUnit5的TestWatcher和AfterAllCallback接口:
public abstract class SeleniumTestWrapper implements TestWatcher, AfterAllCallback{ final static Logger log = LoggerFactory.getLogger(SeleniumTestWrapper.class); protected static final TestConfig testConfig = new TestConfig(); private final WebDriverConfig webDriverConfig = new WebDriverConfig(); protected final WebDriverProvider webDriverProvider; public DatabaseProvider dbProvider; protected ScreenshotHandler screenshothandler; public List<TestResultStatus> testResultsStatus = new ArrayList<>(); final static boolean CERTIFICATES_ENABLED = new TypedProperties("/test_config.properties").getBoolean("certificate.login"); final static boolean IS_REMOTE_DRIVER = new TypedProperties("/driver_config.properties").getBoolean("driver_remote"); public SeleniumTestWrapper() { super(); this.webDriverProvider = new WebDriverProvider(this.webDriverConfig); this.screenshothandler = new ScreenshotHandler(this.getDriver()); this.dbProvider = new DatabaseProvider(); // 若配置启用则初始化dbProvider boolean jdbcEnabled = new TypedProperties("/test_config.properties").getBoolean("jdbc.enable"); if (jdbcEnabled) { this.dbProvider.initialize(); } } protected enum TestResultStatus { SUCCESSFUL, ABORTED, FAILED, DISABLED; } @Override public void afterAll(ExtensionContext context) throws Exception { Map<TestResultStatus, Long> summary = testResultsStatus.stream() .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); log.info("Test result summary for {} {}", context.getDisplayName(), summary.toString()); } @Override public void testDisabled(ExtensionContext context, Optional<String> reason) { log.info("Test Disabled for test {}: with reason :- {}", context.getDisplayName(), reason.orElse("No reason")); testResultsStatus.add(TestResultStatus.DISABLED); } @Override public void testSuccessful(ExtensionContext context) { log.info("[32m" + "Test Successful for test {}: ", context.getDisplayName() + "[37m"); testResultsStatus.add(TestResultStatus.SUCCESSFUL); } @Override public void testAborted(ExtensionContext context, Throwable cause) { log.info("Test Aborted for test {}: ", context.getDisplayName()); testResultsStatus.add(TestResultStatus.ABORTED); } @Override public void testFailed(ExtensionContext context, Throwable cause) { log.info("[31m" + "Test Failed for test {}: ", context.getDisplayName() + "[37m"); log.error("Test ({}) failed because of exception {}", cause); screenshothandler.takeScreenshotForAllure(); testResultsStatus.add(TestResultStatus.FAILED); } }
测试类代码
测试类ObracunDrugogDohotkaTests继承上述抽象类:
public class ObracunDrugogDohotkaTests extends SeleniumTestWrapper { private CopBasePage startPage = PageFactory.initElements(getDriver(), CopBasePage.class); final static Logger logger = LoggerFactory.getLogger(ObracunDrugogDohotkaTests.class); private MenuPage menu = new MenuPage(getDriver()); private TestUtils testUtils = new TestUtils(); private OtvaranjeObracunaPage otvaranjeObracunaPage = new OtvaranjeObracunaPage(getDriver()); private PripremaParameteraPrimateljaPage pripremaParameteraPrimateljaPage = new PripremaParameteraPrimateljaPage(getDriver()); private EvidencijaPrimitakaPage evidencijaPrimitakaPage = new EvidencijaPrimitakaPage(getDriver()); private ObracuniDrugogDohotkaPage obracuniDrugogDohotkaPage = new ObracuniDrugogDohotkaPage(getDriver()); private ObracunDAO obracunDao = new ObracunDAO(dbProvider); private CopSpecificUtils copUtils = new CopSpecificUtils(dbProvider); @BeforeEach public void setup() { ClientCertificateSetup ccs = new ClientCertificateSetup(getDriver()); ccs.setChromeCertificate(UserRoles.RESOLVER); testUtils.testStartLog(this.getClass().getSimpleName()); assertTrue(startPage.enterApplication()); obracunDao.sveObracuneDrugogDohotkaZaRazdobljeDeaktiviraj(DrugiDohodakContent.INST_AGENCIJA_ZA_ZAST_OSOB_POD, 2008); } @Test @Description("Obracun drugog dohotka do statusa 100 i natrag. Ispis obrazaca PDF, kontrolno, XML") public void Obracunaj_Drugi_Dohodak_do_statusa_100_pregledaj_Izvjesca() { menu.clickMenuDDObracuni(); menu.clickSubMenuOtvaranjeObracunaDD(); assertEquals(DrugiDohodakContent.RADNJA_USPJESNO_OBAVLJENA, otvaranjeObracunaPage.otvoriOdaberiObracun(DrugiDohodakContent.AGENCIJA_ZA_ZAST_OSOB_POD, "8", "2020", "obracun1", copUtils.dohvatiPrviSljedeciRadniDan())); menu.clickSubMenuPripremaParametaraPrimateljaDD(); pripremaParameteraPrimateljaPage.pripremiParametre(); menu.clickSubMenuEvidencijaPrimitakaDD(); assertEquals(DrugiDohodakContent.UNOS_EVIDENCIJE_USPJESNO_ZAVRSEN_BR_UGOVORA_4, evidencijaPrimitakaPage.unesiPodatkeIzEvidUgovora()); menu.clickSubMenuObracunDrugogDohotka(); obracuniDrugogDohotkaPage.provjeriGreskeObracuna(); assertEquals(DrugiDohodakContent.OBRACUN_DOVRSEN_3_PRIMATELJA, obracuniDrugogDohotkaPage.obracunajDrugiDohodak()); obracuniDrugogDohotkaPage.ispisiPDFObrazacZaSvePrimatelje(); obracuniDrugogDohotkaPage.ispisiKontrolneIzvjestajeCSV(); obracuniDrugogDohotkaPage.ispisiKontrolneIzvjestajePDF(); obracuniDrugogDohotkaPage.ispisiPDFObrazacZaPojedinogPrim(); obracuniDrugogDohotkaPage.ispisiXMLObrazacZaPojedinogPrim(); obracuniDrugogDohotkaPage.ponistiVerifikacijuiIzmjeniObracun(); // 清理数据 menu.clickSubMenuEvidencijaPrimitakaDD(); evidencijaPrimitakaPage.unesiPodatkeIzEvidUgovora(); evidencijaPrimitakaPage.brisiEvidenciju(); menu.clickSubMenuPripremaParametaraPrimateljaDD(); pripremaParameteraPrimateljaPage.pripremiParametre(); pripremaParameteraPrimateljaPage.uskladiParametre(); menu.clickSubMenuOtvaranjeObracunaDD(); assertEquals(DrugiDohodakContent.RADNJA_USPJESNO_OBAVLJENA, otvaranjeObracunaPage.obrisiPrviObracunUTablici()); } @AfterEach public void after() { assertFalse(startPage.fatalExceptionOccured()); testUtils.testEndLog(this.getClass().getSimpleName()); } }
问题原因与解决方案
核心原因
JUnit5的TestWatcher属于Extension扩展机制,和JUnit4的Rule规则机制不同,仅通过继承实现接口无法自动启用扩展,必须显式声明要使用该扩展。
解决方案
1. 给抽象类添加@ExtendWith注解
在SeleniumTestWrapper类上添加@ExtendWith(SeleniumTestWrapper.class),这样所有继承该抽象类的测试类都会自动启用这个扩展:
@ExtendWith(SeleniumTestWrapper.class) public abstract class SeleniumTestWrapper implements TestWatcher, AfterAllCallback { // 原有代码不变 }
2. 修复testFailed方法的日志参数错误
原testFailed方法中log.error的参数缺失了测试名称,修正为:
@Override public void testFailed(ExtensionContext context, Throwable cause) { log.info("[31m" + "Test Failed for test {}: ", context.getDisplayName() + "[37m"); log.error("Test ({}) failed because of exception {}", context.getDisplayName(), cause); screenshothandler.takeScreenshotForAllure(); testResultsStatus.add(TestResultStatus.FAILED); }
3. 确认JUnit5依赖完整性
确保项目中已正确引入JUnit5的核心依赖(以Maven为例):
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.9.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.9.2</version> <scope>test</scope> </dependency>
额外验证点
- 检查测试类是否使用JUnit5的
@Test注解(org.junit.jupiter.api.Test),而非JUnit4的org.junit.Test。 - 确认测试运行器配置为JUnit5(比如IDE中测试运行环境选择JUnit Jupiter)。
内容的提问来源于stack exchange,提问作者Antonio Cosic
相关产品推荐
相关产品推荐

