You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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("" + "Test Successful for test {}: ", context.getDisplayName() + "");

        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("" + "Test Failed for test {}: ", context.getDisplayName() + "");
        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("" + "Test Failed for test {}: ", context.getDisplayName() + "");
    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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 14:24:55