簡體   English   中英

如何測試 Spring-boot 應用程序的主類

[英]How to test main class of Spring-boot application

我有一個spring-boot應用程序,其中我的@SpringBootApplication入門類看起來像一個標准類。 因此,我為我的所有功能創建了許多測試,並將摘要發送到sonarqube以查看我的覆蓋范圍。

對於我的初學者課程,Sonarqube 告訴我,我只有 60% 的覆蓋率。 所以平均覆蓋率不如預期。

在此處輸入圖像描述

我的測試類只是默認類。

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

    @Test
    public void contextLoads() {
    }
}

那么如何在我的應用程序的起始類中測試我的主類呢?

所有這些答案似乎都是多余的。
您不會為了讓度量工具滿意而添加測試。
加載應用程序的 Spring 上下文需要時間 不要在每個開發人員構建中添加它只是為了在您的應用程序中贏得大約 0.1% 的覆蓋率。
在這里,您不僅僅涵蓋來自 1 個公共方法的 1 個語句 通常編寫數千條語句的應用程序中,它不代表任何覆蓋范圍。

第一個解決方法:使您的 Spring Boot 應用程序類內部沒有聲明 bean。 如果您有它們,請將它們移動到配置類中(以使它們仍然被單元測試覆蓋)。 然后在測試覆蓋配置中忽略你的 Spring Boot 應用程序類。

第二種解決方法:如果您確實需要覆蓋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[] {});
   }
}

你可以做這樣的事情

@Test
public void applicationContextLoaded() {
}

@Test
public void applicationContextTest() {
    mainApp.main(new String[] {});
}

我有相同的目標(有一個運行 main() 方法的測試),我注意到簡單地添加一個像@fg78nc 這樣的測試方法實際上會“啟動”應用程序兩次:一次通過spring boot測試框架,一次通過顯式調用mainApp.main(new String[] {}) ,我覺得這並不優雅。

我最終編寫了兩個測試類:一個帶有@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[] {});
  }
}

總的來說,應用程序還是啟動了兩次,但是因為現在有兩個測試類。 當然,僅使用這兩種測試方法似乎有點過頭了,但通常會在SpringBootApplicationTest類中添加更多測試,以利用@SpringBootTest設置。

我在這里以不同的方式解決了。 由於這個方法只是作為 Spring 運行的橋梁,我用@lombok.Generated注釋了該方法,現在聲納在計算測試覆蓋率時忽略了它。

其他@Generated注釋,如javax.annotation.processing.Generatedjavax.annotation.Generated也可能有效,但我現在無法測試,因為我的問題單已關閉。

package com.stackoverflow;

import lombok.Generated;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    @Generated
    public static void main(String... args) {
        SpringApplication.run(Application.class, args);
    }

}

您可以 Mock SpringApplication因為這是被測方法的依賴項。 看看這里如何。 IE

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

import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;

@RunWith(PowerMockRunner.class)
public class ElectronicGiftcardServiceApplicationTest {

    @Test
    @PrepareForTest(SpringApplication.class)
    public void main() {
        mockStatic(SpringApplication.class);
        ElectronicGiftcardServiceApplication.main(new String[]{"Hello", "World"});
        verifyStatic(SpringApplication.class);
        SpringApplication.run(ElectronicGiftcardServiceApplication.class, new String[]{"Hello", "World"});
    }

}

除了上面的答案,如果您使用的是 JUnit 5 和 Mockito 3.4+,這里是 SpringBoot 應用程序的主要方法的單元測試:

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" }); });

}   

當我們調用 ElectronicGiftCardServiceApplication.main() 時,它會驗證 SpringApplication 類上的靜態方法 run() 是否使用預期的 String 數組調用。

與 awgtek 和 Ramji Sridaran 的想法相同,但他們的解決方案適用於 JUnit 4。

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <mainClass>your.awesome.package.Application</mainClass> 
    </configuration>
</plugin>

如果您的目標是 100% 的覆蓋率,那么您可以做的一件事就是根本沒有 main 方法。 您仍然需要使用@SpringBootApplication注釋的類,但它可以為空。

但請注意,因為它有其缺點,並且其他依賴於main的工具可能會損壞。

這個 SpringApplication 的簡單模擬測試不調用任何方法,而只是測試啟動應用程序。 [使用 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"});
    }
}

盡管這個問題已經得到了廣泛的回答,但我有一個在這里沒有涉及到的用例可能很有趣。 我在啟動時驗證了一些屬性,我想斷言如果這些屬性配置錯誤,應用程序將無法啟動。 在 JUnit4 中,我可以這樣做:

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

    @Test(expected=ConfigurationPropertiesBindException.class)
    public void contextShouldNotLoadWhenPropertiesIncorrect() {
    }
}

但是在 JUnit5 中,您不能再將“預期”值添加到您的 @Test 注釋中,您必須以不同的方式進行操作。 因為我想用一組不正確的屬性啟動應用程序,所以我需要傳入哪個配置文件作為 main() 參數。 我在任何地方都找不到這個文檔,但是通過 main() 方法傳入參數需要您在參數前面加上雙連字符,並用等號分隔鍵和值。 完整的測試如下所示:

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));
    }
}

使用junit

import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.springframework.boot.SpringApplication;

import static org.assertj.core.api.Assertions.*;
class WebsiteApplicationTests {
    
    @Test
    void testApplication() {
        MockedStatic<SpringApplication> utilities = Mockito.mockStatic(SpringApplication.class);
        utilities.when((MockedStatic.Verification) SpringApplication.run(WebsiteApplication.class, new String[]{})).thenReturn(null);
        WebsiteApplication.main(new String[]{});
        assertThat(SpringApplication.run(WebsiteApplication.class, new String[]{})).isEqualTo(null);
    }
}

pom.xml中添加這些依賴項

<dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-inline</artifactId>
            <version>${mockito.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>3.8.0</version>
            <scope>test</scope>
        </dependency>

如果想法是從聲納掃描中排除 SpringApplication 類(這是推薦的做法),您可以在 build.gradle 中使用以下配置將其排除

plugins {
     id 'org.sonarqube' version '3.4.0.2513'
 }
 
 
sonarqube {
    properties {
       property "sonar.exclusions", "**/*Application.java"
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM