Java 如何测试Spring boot应用程序的主类

Java 如何测试Spring boot应用程序的主类,java,spring-boot,junit,Java,Spring Boot,Junit,我有一个springboot应用程序,其中我的@SpringBootApplication初学者类看起来像一个标准类。因此,我为我的所有功能创建了许多测试,并将摘要发送到查看我的覆盖范围 对于我的初级课程,库贝告诉我,我只有60%的保险。因此,平均覆盖率不如预期 我的测试类只是默认类 @RunWith(SpringRunner.class) @SpringBootTest(classes = ElectronicGiftcardServiceApplication.class) public

我有一个
springboot
应用程序,其中我的
@SpringBootApplication
初学者类看起来像一个标准类。因此,我为我的所有功能创建了许多测试,并将摘要发送到查看我的覆盖范围

对于我的初级课程,库贝告诉我,我只有60%的保险。因此,平均覆盖率不如预期

我的测试类只是默认类

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ElectronicGiftcardServiceApplication.class)
public class ElectronicGiftcardServiceApplicationTests {

    @Test
    public void contextLoads() {
    }
}

那么,如何在应用程序的starter类中测试我的主类呢?

您可以这样做

@Test
public void applicationContextLoaded() {
}

@Test
public void applicationContextTest() {
    mainApp.main(new String[] {});
}
我有同样的目标(有一个运行main()方法的测试),我注意到简单地添加一个像@fg78nc said这样的测试方法实际上会“启动”应用程序两次:一次是通过spring boot测试框架,一次是通过显式调用
mainApp.main(新字符串[]{})
,我觉得这并不优雅

最后我编写了两个测试类:一个带有
@SpringBootTest
注释和空的测试方法applicationContextLoaded(),另一个没有调用main方法的
@SpringBootTest
(仅
RunWith(SpringRunner.class)

SpringBootApplicationTest

package example;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.boot.test.context.SpringBootTest;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootApplicationTest {

  @Test
  public void contextLoads() {
  }
}
package example;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
public class ApplicationStartTest {
  @Test
  public void applicationStarts() {
    ExampleApplication.main(new String[] {});
  }
}
应用程序开始测试

package example;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.boot.test.context.SpringBootTest;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootApplicationTest {

  @Test
  public void contextLoads() {
  }
}
package example;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
public class ApplicationStartTest {
  @Test
  public void applicationStarts() {
    ExampleApplication.main(new String[] {});
  }
}

总的来说,应用程序仍然启动了两次,但因为现在有两个测试类。当然,仅使用这两种测试方法似乎有些过分,但通常会利用
@springbootstest
设置向类
springbootsapplicationtest
添加更多的测试。

所有这些答案似乎都有些过分。
添加测试并不是为了让度量工具满意。
加载应用程序的Spring上下文需要时间。不要在每个开发人员构建中添加它,只是为了在应用程序中赢得约0.1%的覆盖率。
这里您不必仅涵盖1个公共方法中的1条语句。在应用程序中,如果通常编写数千条语句,那么就覆盖范围而言,它并不代表什么

第一个解决方法:使您的Spring Boot应用程序类内部没有声明bean。如果您有它们,请在配置类中移动它们(为了使它们仍然包含在单元测试中)。然后忽略

第二种解决方法:如果您确实需要涵盖
main()
调用(例如,出于组织原因),则为其创建一个测试,但创建一个集成测试(由持续集成工具执行,而不是在每个开发人员构建中执行),并清楚地记录测试类的用途:

import org.junit.Test;

// Test class added ONLY to cover main() invocation not covered by application tests.
public class MyApplicationIT {
   @Test
   public void main() {
      MyApplication.main(new String[] {});
   }
}

org.springframework.boot
springbootmaven插件
你的.awesome.package.Application
如果你的目标是100%的覆盖率,你可以做的一件事就是根本没有一个主方法。您仍然需要一个用
@SpringBootApplication
注释的类,但它可以是空的


但是要注意,它有其缺点,依赖于
main
的其他工具可能会损坏。

您可以模拟
SpringApplication
,因为这是测试方法的依赖项。看看怎么做。 即


我用另一种方式解决了这个问题。由于该方法只是Spring运行的桥梁,我用
@lombok.Generated
注释了该方法,现在sonar在计算测试覆盖率时忽略了它

其他
@Generated
注释,如
javax.annotation.processing.Generated
javax.annotation.Generated
也可以工作,但我现在无法测试,因为我的问题单已关闭

package com.stackoverflow;
导入lombok.Generated;
导入org.springframework.boot.SpringApplication;
导入org.springframework.boot.autoconfigure.springboot应用程序;
@SpringBoot应用程序
公共类应用程序{
@产生
公共静态void main(字符串…参数){
SpringApplication.run(Application.class,args);
}
}

尽管这个问题已经得到了广泛的回答,但我有一个可能很有趣的用例,这里没有介绍。我正在启动时验证一些属性,我想断言,如果这些属性配置错误,应用程序将无法启动。在JUnit4中,我可以这样做:

@ActiveProfiles("incorrect")
@SpringBoot
public class NetworkProbeApplicationTest {

    @Test(expected=ConfigurationPropertiesBindException.class)
    public void contextShouldNotLoadWhenPropertiesIncorrect() {
    }
}
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindException;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class NetworkProbeApplicationTest {

    @Test
    public void contextShouldNotLoadWhenPropertiesIncorrect() {
        Exception exception = assertThrows(ConfigurationPropertiesBindException.class, () -> {
            SpringApplication.run(NetworkProbeApplication.class, "--spring.profiles.active=incorrect");
        });

        String expectedMessage = "Error creating bean with name 'dnsConfiguration': Could not bind properties to 'DnsConfiguration' : prefix=dns";

        assertTrue(exception.getMessage().contains(expectedMessage));
    }
}
但是在JUnit5中,您不能再将“预期”值添加到@Test注释中,您必须以不同的方式进行添加。由于我想用一组不正确的属性启动应用程序,我需要传入哪个配置文件用作main()参数。我在任何地方都找不到这个文档,但是通过main()方法传入参数需要使用双连字符作为参数前缀,并使用等号分隔键和值。完整的测试如下所示:

@ActiveProfiles("incorrect")
@SpringBoot
public class NetworkProbeApplicationTest {

    @Test(expected=ConfigurationPropertiesBindException.class)
    public void contextShouldNotLoadWhenPropertiesIncorrect() {
    }
}
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindException;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class NetworkProbeApplicationTest {

    @Test
    public void contextShouldNotLoadWhenPropertiesIncorrect() {
        Exception exception = assertThrows(ConfigurationPropertiesBindException.class, () -> {
            SpringApplication.run(NetworkProbeApplication.class, "--spring.profiles.active=incorrect");
        });

        String expectedMessage = "Error creating bean with name 'dnsConfiguration': Could not bind properties to 'DnsConfiguration' : prefix=dns";

        assertTrue(exception.getMessage().contains(expectedMessage));
    }
}

这个针对SpringApplication的简单模拟测试不调用任何方法,只测试starter应用程序。[使用PowerMockRunner.class]

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.boot.SpringApplication;

@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", "javax.management.*"})
public class JobsAppStarterTest {

    @Test
    @PrepareForTest(SpringApplication.class)
    public void testSpringStartUp() {
        PowerMockito.mockStatic(SpringApplication.class);
        SpringApplication.run(JobsAppStarter.class, new String[] {"args"});
        JobsAppStarter.main(new String[] {"args"});
    }
}

除了上面的答案之外,这里还有一个SpringBoot应用程序主要方法的单元测试,用于测试您是否使用JUnit 5和Mockito 3.4+:

try (MockedStatic<SpringApplication> mocked = mockStatic(SpringApplication.class)) {
            
   mocked.when(() -> { SpringApplication.run(ElectronicGiftCardServiceApplication.class, 
      new String[] { "foo", "bar" }); })
         .thenReturn(Mockito.mock(ConfigurableApplicationContext.class));
            
   ElectronicGiftCardServiceApplication.main(new String[] { "foo", "bar" });
            
   mocked.verify(() -> { SpringApplication.run(ElectronicGiftCardServiceApplication.class, 
      new String[] { "foo", "bar" }); });

}   
try(MockedStatic mocked=mockStatic(SpringApplication.class)){
mocked.when(()->{SpringApplication.run(ElectronicGiftCardServiceApplication.class),
新字符串[]{“foo”,“bar”};})
.thenReturn(Mockito.mock(ConfigurableApplicationContext.class));
ElectronicGiftCardServiceApplication.main(新字符串[]{“foo”,“bar”});
mocked.verify(()->{SpringApplication.run(ElectronicGiftCardServiceApplication.class),
新字符串[]{“foo”,“bar”};});
}   
当我们调用ElectronicGiftCardServiceApplication.main()时,它验证SpringApplication类上的静态方法run()是否使用预期的字符串数组调用


与awgtek和Ramji Sridaran的想法相同,但他们的解决方案适用于JUnit 4。

您可以使用mockito和verif模拟
SpringApplication