복붙노트

[SPRING] junit 테스트를 사용하여 Spring Boot 애플리케이션에 명령 줄 인수 전달하기

SPRING

junit 테스트를 사용하여 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 테스트를 작성하고 있습니다. 그러나 나는 Assert에서 예외적으로 raise를 얻는다.

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

어떤 생각, 매개 변수를 전달하는 방법?

해결법

  1. ==============================

    1.필자는 ApplicationBase를 내 테스트에 삽입하고 필요한 매개 변수로 CommandLineRunner를 호출하여 SpringBoot와 잘 작동하는 Junit 테스트를 만드는 방법을 찾았습니다.

    필자는 ApplicationBase를 내 테스트에 삽입하고 필요한 매개 변수로 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");
        }
    
    }
    
  2. ==============================

    2.스프링 부트를 방정식에서 벗어나겠습니다.

    스프링 부트를 방정식에서 벗어나겠습니다.

    스프링 부트를 테스트하지 않고 스프링 부트를 거치지 않고 실행 방법을 테스트하기 만하면됩니다. 나는이 테스트의 목적이 회귀에 더 중요하다는 것을 가정하고 args가 제공되지 않을 때 응용 프로그램이 항상 IllegalArgumentException을 throw하는지 확인합니다. 오래된 단위 테스트는 여전히 한 가지 방법을 테스트하는 데 사용됩니다.

    @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를 사용하여 Reader와 Writer를 위해 mock을 주입했습니다.

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

    3.귀하의 솔루션은 귀하가 제시 한 방식으로 작동하지 않을 것입니다 (귀하가 Spring을위한 자체 테스트 프레임 워크를 구현할 때까지).

    귀하의 솔루션은 귀하가 제시 한 방식으로 작동하지 않을 것입니다 (귀하가 Spring을위한 자체 테스트 프레임 워크를 구현할 때까지).

    이것은 테스트를 실행할 때 Spring (더 구체적으로 테스트 한 SpringBootContextLoader)이 독자적인 방식으로 애플리케이션을 실행하기 때문입니다. SpringApplication을 인스턴스화하고 인자없이 run 메소드를 호출한다. 또한 응용 프로그램에 구현 된 주요 메서드를 사용하지 않습니다.

    그러나 응용 프로그램을 리팩터링하여 테스트 할 수 있습니다.

    제 생각에는 (Spring을 사용하고 있기 때문에) 가장 쉬운 해결책은 순수한 명령 행 인수 대신에 스프링 설정 특성을 사용하여 구현 될 수 있다고 생각합니다. (하지만 스프링 구성 속성 메커니즘의 주된 목적이기 때문에이 솔루션을 "구성 인수"대신 사용해야한다고 알고 있어야합니다.

    @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

  4. ==============================

    4.귀하의 코드에서 autowire Spring ApplicationArguments. getSourceArgs ()를 사용하여 명령 행 인수를 검색하십시오.

    귀하의 코드에서 autowire Spring ApplicationArguments. getSourceArgs ()를 사용하여 명령 행 인수를 검색하십시오.

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

    테스트에서 ApplicationArguments를 조롱하십시오.

    @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 구문 --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 () 메소드 alwyas는 스프링 컨텍스트 생성 직후에 실행되기 때문에 시험 수준에서도. 시험이 시작되기 전에 실행됩니다.

  5. ==============================

    5.이 답변에서 언급했듯이, Spring Boot는 현재 사용하는 DefaultApplicationArguments를 가로 채거나 대체하는 방법을 제공하지 않습니다. 이 문제를 해결하는 데 사용했던 자연 부팅 방법은 내 주자 논리를 향상시키고 자동 실행되는 속성을 사용하는 것입니다.

    이 답변에서 언급했듯이, Spring Boot는 현재 사용하는 DefaultApplicationArguments를 가로 채거나 대체하는 방법을 제공하지 않습니다. 이 문제를 해결하는 데 사용했던 자연 부팅 방법은 내 주자 논리를 향상시키고 자동 실행되는 속성을 사용하는 것입니다.

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

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

    ... 내 주자에게 속성 구성 요소를 autowired :

    @Service
    public class Loader implements ApplicationRunner {
    
        private AppProperties properties;
    
        @Autowired
        public Loader(AppProperties properties) {
            this.properties = properties;
        }
        ...
    

    ... 그리고 실행 중에는 해당 속성이 활성화 될 때만 assert'ed가되며, 이는 일반 응용 프로그램 사용에 대해 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() {
        }
    
    }
    

    내 특정 러너가 일반적으로 System.exit (0)을 호출하고 그 방법을 빠져 나오면 단위 테스트가 반 실패 상태로 남았 기 때문에 exitWhenFinished 파트가 필요했습니다.

  6. from https://stackoverflow.com/questions/39144136/using-junit-test-to-pass-command-line-argument-to-spring-boot-application by cc-by-sa and MIT license