有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java更改JUnit测试顺序会根据Hibernate Validator的平台ResourceBundleLocator使用来自多个JAR文件的验证消息中断测试

我有一个GradleSpring Boot应用程序,使用Hibernate Validator在Java11上运行。该应用程序使用多个带有custom validation constraint annotations的自定义库jar,每个jar都有自己的ValidationMessages.properties文件,其中包含这些注释的默认消息。使用Hibernate的PlatformResourceBundleLocator中的内置功能,可以将多个JAR文件中的ValidationMessages.properties文件聚合到一个包中,从而支持这一点:

@Configuration
public class ValidationConfig {
    @Bean
    public LocalValidatorFactoryBean validator() {
        PlatformResourceBundleLocator resourceBundleLocator =
                new PlatformResourceBundleLocator(ResourceBundleMessageInterpolator.USER_VALIDATION_MESSAGES, null, true);

        LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
        factoryBean.setMessageInterpolator(new ResourceBundleMessageInterpolator(resourceBundleLocator));
        return factoryBean;
    }
}

jar1:验证消息。属性

com.example.CustomValidation1.message=My custom message 1

jar2:验证消息。属性

com.example.CustomValidation2.message=My custom message 2

有相当多的单位和;测试验证功能的项目中的集成测试。其中一些测试在Spring聚合消息验证程序bean中自动连接。一些测试(尤其是在应用程序使用多个ValidationMessages.properties之前的测试)不依赖于返回的确切消息,并且使用默认的Spring验证器,而不使用消息聚合。虽然更新旧的测试可能是有意义的,但出于优先级和可用时间的考虑,该任务已推迟到未来

运行应用程序时,消息聚合功能正在按预期工作。当我在本地机器上运行测试时,它也按预期工作。然而,当我的项目的测试通过Jenkins持续集成工具在构建服务器上运行时,一些验证测试失败

我已经根据JUnit测试类的运行顺序确定了失败的发生。测试在本地的运行顺序与在Jenkins上不同(这是允许的,因为Gradle JUnit插件不保证测试类的执行顺序)。具体来说,任何测试是否失败取决于使用消息聚合验证器的测试是否在使用没有消息聚合的验证器的测试之前运行

我已经能够将这个问题归结为一个简单的可在单个测试类中重新创建的失败,如下所示。为了示例的简单性,使用@CustomValidation1&@CustomValidation2已编写验证注释,以确保验证始终失败

import com.example.CustomValidation1;
import com.example.CustomValidation2;
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
import org.hibernate.validator.resourceloading.PlatformResourceBundleLocator;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import java.util.Set;
import java.util.stream.Collectors;

import static org.junit.Assert.assertEquals;

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ValidationMessageTests {
    private Validator aggregateMessageValidator = createAggregateMessageValidator();

    private Validator standardValidator = createBasicValidator();

    private Validator createBasicValidator() {
        LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
        factoryBean.afterPropertiesSet();
        return factoryBean;
    }

    private Validator createAggregateMessageValidator() {
        PlatformResourceBundleLocator resourceBundleLocator =
                new PlatformResourceBundleLocator(ResourceBundleMessageInterpolator.USER_VALIDATION_MESSAGES, null, true);

        LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
        factoryBean.setMessageInterpolator(new ResourceBundleMessageInterpolator(resourceBundleLocator));
        factoryBean.afterPropertiesSet();
        return factoryBean;
    }

    @Test
    public void test1() {
        Set<ConstraintViolation<MyInput>> violations = aggregateMessageValidator.validate(new MyInput());
        assertEquals(Set.of("My custom message 1", "My custom message 2"),
                violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.toSet()));
    }

    @Test
    public void test2() {
        Set<ConstraintViolation<MyInput>> violations = standardValidator.validate(new MyInput());
        assertEquals(2, violations.size());
    }

    @CustomValidation1
    @CustomValidation2
    private static class MyInput {
    }
}

test1test2之前运行时,两个测试都通过。但是,当test2被重命名为test0以便在test1之前运行时,test0通过,但test1失败,出现以下错误:

java.lang.AssertionError: 
Expected :[My custom message 1, My custom message 2]
Actual   :[My custom message 1, {com.example.CustomValidation2.message}]

为什么更改测试顺序会导致这些测试失败,我该如何修复它

该代码目前正在使用Spring Boot 2.2.4。使用Hibernate Validator 6.0.18发布。决赛


共 (1) 个答案

  1. # 1 楼答案

    这个问题的根本原因是默认情况下会缓存ResourceBundle。根据ResourceBundle JavaDocs

    Resource bundle instances created by the getBundle factory methods are cached by default, and the factory methods return the same resource bundle instance multiple times if it has been cached.

    由于这种缓存,无论哪个测试首先导致Hibernate Validator加载ValidationMessages.properties文件,都将确定哪个ResourceBundle用于所有后续测试。首先运行未使用自定义验证配置的测试时,将为ValidationMessages缓存非聚合的^{},而不是所需的^{}。当PlatformResourceBundleLocator加载资源束时,聚合逻辑将被忽略,因为已缓存的ResourceBundle将被使用

    解决办法很简单ResourceBundle有一个^{}方法来清除缓存,可以在Hibernate Validator检索包以创建验证消息之前调用该方法:

    @FixMethodOrder(MethodSorters.NAME_ASCENDING)
    public class ValidationMessageTests {
        @Before
        public void setup() {
            ResourceBundle.clearCache();
        }
    
        // The rest of this test class is unchanged
    }