【问题标题】:How to write non-encapsulated unit tests?如何编写非封装单元测试?
【发布时间】:2019-08-27 05:23:32
【问题描述】:

我有一个自动装配的变量

@Autowired
private DocumentConfig documentConfig;

我想用这个配置对象的各种状态来测试 DocumentService。我有哪些选择?最好的选择是什么?

第一个想法是这样的:

@Test
public void save_failure() {
    documentConfig.setNameRequired(true);
    /*
    testing code goes here
    */
    documentConfig.setNameRequired(false);
}

但我想更加确定变量在测试后被重置以不干扰其他测试,以确保只有这个测试会出错,如果它是问题的根源。

我的新想法是这样的:

@Before
public void after() { documentConfig.setNameRequired(true); }
@Test
public void save_failure() {
    /*
    testing code goes here
    */
}
@After
public void after() { documentConfig.setNameRequired(false); }

但是,这根本不起作用,因为之前和之后执行的是整个文件,而不是这个单一的测试。我不希望只为一个测试创建一个新文件。

我现在已经达成妥协:

@Test
public void save_failure() {
    documentConfig.setNameRequired(true);
    /*
    testing code goes here
    */
}
@After
public void after() { documentConfig.setNameRequired(false); }

它似乎可以满足我的所有需求,但我有几个问题。
假设nameRequired 开始为假,这是否保证不会干扰其他测试?
有什么办法可以让我更清楚吗?既是为了我未来的自己,也是为了他人。

【问题讨论】:

  • 你在测试什么? DocumentConfig,还是其他依赖 DocumentConfig 的类?如果是前者,请使用 try/finally。如果是后者,模拟 DocumentConfig。

标签: java unit-testing spring-boot junit


【解决方案1】:

目前尚不清楚您使用的是哪个测试框架。对于普通单元测试,通过 setter 或构造函数注入使值可注入。无论哪种方式最适合您的具体情况。

如果要注入很多(超过三个;-))这样的值,您可以考虑引入一个配置类来将所有这些值作为单个参数注入。

【讨论】:

    【解决方案2】:

    您可以在每次测试之前创建它。有点喜欢

    private DocumentConfig documentConfig;
    
    @Before
    public void createConfig() {
        documentConfig = new DocumentConfig(mockedParams);
    }
    

    【讨论】:

    • 我似乎理解这个答案,我喜欢它。但是@After 不会比@Before 更好吗?毕竟,目标是始终保持配置重置,除非在特定测试中。有了这样的改动,这其实和我自己的终极解决方案非常相似。编辑:我似乎还需要在定义中分配变量。
    • 没关系,我不明白。这似乎没有将此文件中的documentConfig 与 DocumentService(这些测试的主要对象)和其他文件中的自动连接的documentConfig 连接起来。
    • 另外:我猜你的意思是new DocumentConfig(mockedParams),对吗?
    【解决方案3】:

    一种常用的方法是设置一个虚拟的DocumentConfig 并将其注入到setUp() 方法中(用@Before 注释),以便在每个测试中重置整个上下文,例如:

    @Before
    public void setUp() {
        this.documentConfig = new DocumentConfig();
        this.documentConfig.setNameRequired(false);
        this.service = new DocumentService(this.documentConfig);
    }
    

    在这种情况下,我设置了一个简单的对象,nameRequiredfalse。我可能会删除该语句,因为boolean 字段默认为false

    如果您不使用构造函数注入,并且您没有documentConfig 的setter,则必须使用反射来注入该字段,例如:

    ReflectionTestUtils.setField(this.service, "documentConfig", this.documentConfig);
    

    在您的测试中,您现在可以编写如下内容:

    @Test
    public void save_failure() {
        this.documentConfig.setNameRequired(true);
        // TODO: Implement test
    }
    

    或者,您可以模拟DocumentConfig,这样您就不必依赖它的实现来测试DocumentService。我假设你在 DocumentService 的代码中的某处调用了 isNameRequired(),所以你可以这样模拟它:

    @Before
    public void setUp() {
        // Use a static import for Mockito.mock()
        this.documentConfig = mock(DocumentConfig.class);
        this.service = new DocumentService(this.documentConfig);
    }
    
    @Test
    public void save_failure()  {
        // Use a static import for Mockito.when()
        when(this.documentConfig.isNameRequired()).thenReturn(true); 
        // TODO: Implement test
    }
    

    由于这种模拟/注入设置经常发生,Mockito 也有自己的运行器,可让您摆脱 setUp() 方法,例如:

    @RunWith(MockitoJUnitRunner.class)
    public class DocumentServiceTest {
        @InjectMocks
        private DocumentService documentService;
        @Mock
        private DocumentConfig documentConfig;
    
        @Test
        public void save_failure()  {
            when(this.documentConfig.isNameRequired()).thenReturn(true); 
            // TODO: Implement test
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2012-02-03
      • 2019-01-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-17
      • 2012-01-06
      相关资源
      最近更新 更多