bestsource

Junit 테스트를 사용하여 명령줄 인수를 Spring Boot 응용 프로그램에 전달

bestsource 2023. 7. 3. 23:02
반응형

Junit 테스트를 사용하여 명령줄 인수를 Spring Boot 응용 프로그램에 전달

저는 매우 기본적인 Spring Boot 애플리케이션을 가지고 있는데, 이것은 명령줄에서 인수를 예상하고 있지만, 그렇지 않으면 작동하지 않습니다.여기 코드가 있습니다.

@SpringBootApplication
public class Application implements CommandLineRunner {

    private static final Logger log = LoggerFactory.getLogger(Application.class);

    @Autowired
    private Reader reader;

    @Autowired
    private Writer writer;

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

    @Override
    public void run(String... args) throws Exception {

        Assert.notEmpty(args);

        List<> cities = reader.get("Berlin");
         writer.write(cities);
    }
}

이것은 저의 Junit 시험 수업입니다.

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

    @Test
    public void contextLoads() {
    }
}

지금이다,Assert.notEmpty()논쟁을 통과시키기 위한 명령하지만 지금은 같은 것을 위해 Junit test를 쓰고 있습니다.하지만, 저는 다음과 같은 예외 인상을 받았습니다.Assert.

2016-08-25 16:59:38.714 ERROR 9734 --- [           main] o.s.boot.SpringApplication               : Application startup failed

java.lang.IllegalStateException: Failed to execute CommandLineRunner
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:801) ~[spring-boot-1.4.0.RELEASE.jar:1.4.0.RELEASE]
    at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:782) ~[spring-boot-1.4.0.RELEASE.jar:1.4.0.RELEASE]
    at org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:769) ~[spring-boot-1.4.0.RELEASE.jar:1.4.0.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:314) ~[spring-boot-1.4.0.RELEASE.jar:1.4.0.RELEASE]
    at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:111) [spring-boot-test-1.4.0.RELEASE.jar:1.4.0.RELEASE]
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:117) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.boot.test.autoconfigure.AutoConfigureReportTestExecutionListener.prepareTestInstance(AutoConfigureReportTestExecutionListener.java:46) [spring-boot-test-autoconfigure-1.4.0.RELEASE.jar:1.4.0.RELEASE]
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) [junit-4.12.jar:4.12]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) [junit-4.12.jar:4.12]
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363) [junit-4.12.jar:4.12]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86) [.cp/:na]
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) [.cp/:na]
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459) [.cp/:na]
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678) [.cp/:na]
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382) [.cp/:na]
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192) [.cp/:na]
Caused by: java.lang.IllegalArgumentException: [Assertion failed] - this array must not be empty: it must contain at least 1 element
    at org.springframework.util.Assert.notEmpty(Assert.java:222) ~[spring-core-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.util.Assert.notEmpty(Assert.java:234) ~[spring-core-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at com.deepakshakya.dev.Application.run(Application.java:33) ~[classes/:na]
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:798) ~[spring-boot-1.4.0.RELEASE.jar:1.4.0.RELEASE]
    ... 32 common frames omitted

어떻게 매개변수를 전달할 수 있을까요?

@SpringBootTest가지다argsparam. 거기에 cli 인수를 전달할 수 있습니다.

https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/context/SpringBootTest.html#args-- 을 참조하십시오.

테스트에서 ApplicationContext를 주입하고 필요한 매개 변수를 사용하여 CommandLineRunner를 호출함으로써 SpringBoot에서 제대로 작동하는 Junit 테스트를 생성할 수 있는 방법을 찾았습니다.

최종 코드는 다음과 같습니다.

package my.package.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
class AsgardBpmClientApplicationIT {

    @Autowired
    ApplicationContext ctx;

    @Test
    public void testRun() {
        CommandLineRunner runner = ctx.getBean(CommandLineRunner.class);
        runner.run ( "-k", "arg1", "-i", "arg2");
    }

}

유감스럽게도 귀사의 솔루션은 귀하가 제시한 방식으로 작동하지 않을 것입니다(Spring에 대한 자체 테스트 프레임워크를 구현하기 전까지는).

이는 검정을 실행할 때 봄(검정)이SpringBootContextLoader보다 구체적으로) 고유한 방식으로 응용 프로그램을 실행합니다.그것은 예를 들어,SpringApplication호출합니다.run인수가 없는 메서드입니다.또한 사용하지 않습니다.main응용 프로그램에서 구현된 메서드입니다.

그러나 응용프로그램을 테스트할 수 있도록 응용프로그램을 재팩터링할 수 있습니다.

(스프링을 사용하고 있기 때문에) 가장 쉬운 솔루션은 순수한 명령줄 인수 대신 스프링 구성 속성을 사용하여 구현할 수 있다고 생각합니다. (그러나 이 솔루션은 스프링의 주요 목적이기 때문에 "구성 인수"에 사용되어야 한다는 것을 알아야 합니다.)configuration properties메커니즘)

다음을 사용하여 매개 변수 읽기@Value주석:

@SpringBootApplication
public class Application implements CommandLineRunner {

    @Value("${myCustomArgs.customArg1}")
    private String customArg1;

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

    @Override
    public void run(String... args) throws Exception {

        Assert.notNull(customArg1);
        //...
    }
}

표본 검정:

@RunWith(SpringRunner.class)
@SpringBootTest({"myCustomArgs.customArg1=testValue"})
public class CityApplicationTests {

    @Test
    public void contextLoads() {
    }
}

명령줄 앱을 실행할 때는 사용자 지정 매개 변수를 추가하면 됩니다.

--myCustomArgs.customArg1=testValue

저는 SpringBoot를 그 방정식에서 제외할 것입니다.

테스트만 하면 됩니다.run방법, 스프링 부트를 거치지 않고, 당신의 목표는 스프링 부트를 테스트하는 것이 아니기 때문에, 그렇지 않나요? 제 생각에, 이 테스트의 목적은 회귀를 위한 것이고, 당신의 애플리케이션이 항상 그것을 던지도록 보장합니다.IllegalArgumentExceptionArg가 제공되지 않을 때?이전의 양호한 장치 테스트는 여전히 단일 방법을 테스트하는 데 사용됩니다.

@RunWith(MockitoJUnitRunner.class)
public class ApplicationTest {

    @InjectMocks
    private Application app = new Application();

    @Mock
    private Reader reader;

    @Mock
    private Writer writer;

    @Test(expected = IllegalArgumentException.class)
    public void testNoArgs() throws Exception {
        app.run();
    }

    @Test
    public void testWithArgs() throws Exception {
        List list = new ArrayList();
        list.add("test");
        Mockito.when(reader.get(Mockito.anyString())).thenReturn(list);

        app.run("myarg");

        Mockito.verify(reader, VerificationModeFactory.times(1)).get(Mockito.anyString());
        Mockito.verify(writer, VerificationModeFactory.times(1)).write(list);
    }
}

나는 Mockito를 사용하여 독자와 작가를 위한 모의실험을 했습니다.

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.9.0</version>
    <scope>test</scope>
</dependency>

스프링은 다음과 같습니다.ApplicationArguments.사용하다getSourceArgs()명령줄 인수를 검색합니다.

public CityApplicationService(ApplicationArguments args, Writer writer){        
    public void writeFirstArg(){
        writer.write(args.getSourceArgs()[0]);
    }
}

테스트에서 응용 프로그램 인수를 모의실험합니다.

@RunWith(SpringRunner.class)
@SpringBootTest
public class CityApplicationTests {
@MockBean
private ApplicationArguments args;

    @Test
    public void contextLoads() {
        // given
        Mockito.when(args.getSourceArgs()).thenReturn(new String[]{"Berlin"});

        // when
        ctx.getBean(CityApplicationService.class).writeFirstArg();

        // then
        Mockito.verify(writer).write(Matchers.eq("Berlin"));

    }
}

Maciej Marczuk이 제안한 것처럼, 저도 Springs를 사용하는 것을 선호합니다.Environment명령줄 인수 대신 속성을 입력합니다. 구문을 할 수 에는 스프링 구문을 할 수 없습니다.--argument=value당신은 자신의 것을 쓸 수 있습니다.PropertySource으로 채운 " " " " " 에 합니다.ConfigurableEnvironment그러면 모든 클래스에서 스프링 환경 속성만 사용하면 됩니다.

예.

public class ArgsPropertySource extends PropertySource<Map<String, String>> {

    ArgsPropertySource(List<CmdArg> cmdArgs, List<String> arguments) {
        super("My special commandline arguments", new HashMap<>());

        // CmdArgs maps the property name to the argument value.
        cmdArgs.forEach(cmd -> cmd.mapArgument(source, arguments));
    }

    @Override
    public Object getProperty(String name) {
        return source.get(name);
    }
}


public class SetupArgs {

    SetupArgs(ConfigurableEnvironment env, ArgsMapping mapping) {           
        // In real world, this code would be in an own method.
        ArgsPropertySource = new ArgsPropertySource(mapping.get(), args.getSourceArgs());
        environment
            .getPropertySources()
            .addFirst(propertySource);
    }
}

BTW:

저는 답변을 제시하기에 충분한 평판 포인트가 없기 때문에 여전히 여기에 열심히 배운 교훈을 남기고 싶습니다.

CommandlineRunner그렇게 좋은 대안은 아닙니다.이왕이면run()메서드는 항상 스프링 컨텍스트 생성 직후에 실행됩니다.심지어 시험 수업에서도.테스트가 시작되기 전에 실행됩니다.

필요한 것은 다음과 같습니다.

@SpringBootTest(args = "test")
class YourApplicationTests {
  @Test
  public void contextLoads() {
  }
}

답변에서 언급한 것처럼 스프링 부트는 현재 사용하는 DefaultApplicationArguments를 가로채거나 대체할 수 있는 방법을 제공하지 않습니다.이 문제를 해결하기 위해 사용한 자연스러운 부팅 방법은 러너 로직을 향상시키고 몇 가지 자동 배선된 속성을 사용하는 것이었습니다.

먼저 속성 구성 요소를 만들었습니다.

@ConfigurationProperties("app") @Component @Data
public class AppProperties {
    boolean failOnEmptyFileList = true;
    boolean exitWhenFinished = true;
}

... 속성 구성 요소를 러너에 자동 배선했습니다.

@Service
public class Loader implements ApplicationRunner {

    private AppProperties properties;

    @Autowired
    public Loader(AppProperties properties) {
        this.properties = properties;
    }
    ...

...에서.그리고, 그 안에서.run해당 된 경우에만 은 " assert"("assert")입니다.true일반 애플리케이션 사용의 경우:

@Override
public void run(ApplicationArguments args) throws Exception {
    if (properties.isFailOnEmptyFileList()) {
        Assert.notEmpty(args.getNonOptionArgs(), "Pass at least one filename on the command line");
    }

    // ...do some loading of files and such

    if (properties.isExitWhenFinished()) {
        System.exit(0);
    }
}

이를 통해 유닛 테스트에 적합한 방식으로 실행할 수 있도록 속성을 조정할 수 있습니다.

@RunWith(SpringRunner.class)
@SpringBootTest(properties = {
        "app.failOnEmptyFileList=false",
        "app.exitWhenFinished=false"
})
public class InconsistentJsonApplicationTests {

    @Test
    public void contextLoads() {
    }

}

나는 필요했습니다.exitWhenFinished내 특정 주자가 보통 전화를 한 이후로.System.exit(0)그런 식으로 빠져나오면 장치 테스트는 반자동 상태가 됩니다.

6월 5일에는 @SpringBootTest(args = "20210831")에 있는 args parm을 사용하여 Commandline 인수를 전달할 수 있습니다.

@ExtendWith(SpringExtension.class)
@SpringBootTest(args = "20210831")
@TestPropertySource("/application-test.properties")
@ContextConfiguration(classes = {Test.class, TestConfig.class})
public class AppleTest {

언급URL : https://stackoverflow.com/questions/39144136/using-junit-test-to-pass-command-line-argument-to-spring-boot-application

반응형