정상혁정상혁

Spring 3.0부터 추가된 SpEL(Spring E-pression Language)를 응용한 사례입니다.

application context 파일 선언만으로 특정 빈의 속성에 서버 이름을 넣으려면 어떻게 해야하냐는 질문을 받았습니다.

질문자는 아래의 bean설정에 "serverName"이라고 지정된 곳에 각각 실행되는 서버마다 다른 이름을 넣고 싶어했습니다.

persistentMessageStore 선언
<bean id="persistentMessageStore" class="org.springframework.integration.jdbc.JdbcMessageStore">
   <property name="region" value="serverName" />
....

</bean>

나름대로의 FactoryBean을 따로 만들거나 JavaConfig을 써도 쉽게 풀리는 문제이지만, SpEL을 활용하는 것이 기존 설정을 가장 적게 바꾸는 방식입니다.

아래와 같이 java.net.InetAddress.getLocalHost를 bean으로 등록합니다.

<bean id="localHost" class="java.net.InetAddress" factory-method="getLocalHost"/>

SPel이 잘 먹는지는 다음와 같이 테스트 할 수 있습니다.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class HostNameTest {
    @Value("#{localHost.hostName}") String hostName;
    @Test
    public void hostNameShouldBePrinted() throws Exception {
        String expectedHostName = InetAddress.getLocalHost().getHostName();
        assertThat(hostName,is(expectedHostName));
        System.out.println(hostName);
    }
}

위의 테스트 코드에서는 @ContextConfiuguration선언 뒤에 Application context 파일 위치를 지정하지 않았으므로, default로 참조되는 같은 패키지 디렉토리의 HostNameTest-context.xml에 'localHost' bean이 선언되어 있어야 겠죠.

제일 처음의 코드1의 bean선언에서도 마찬가지로 #{localHost.hostName} 을 참조할 수 있습니다.

<bean id="persistentMessageStore" class="org.springframework.integration.jdbc.JdbcMessageStore">
   <property name="region" value="#{localHost.hostName}" />
....

</bean>

SpEL을 활용하면 아래와 같이 bean사이의 연결을 더 유연하게 할 수도 있지만, 컴파일타임에 검증되지 않는다는 단점이 있습니다. 이를 보완하려면 오타를 검증할 수 있는 테스트 코드가 있어야 합니다.

다른 방법으로는, JavaConfig를 활용해서 직접 명시적으로 메소드를 호출해서 hostName을 주입해도 됩니다.

@Bean JdbcMessageStore persistentMessageStore(){
    JdbcMessageStore store = new JdbcMessageStore ();
    store.setRegion(localHost.getHostName());
    return store;
}

@Bean public InetAddress localHost() throws UnknownHostException {
    return  InetAddress.getLocalHost();
}
정상혁정상혁

파일 update를 처리하는 Servlet도 Spring의 MockHttpSerlvetRequest와 MockHttpServletResponse로 테스트 할 수 있습니다.

다음 링크에 있는 소스를 참고했습니다.

request의 content 속성에 파일내용 등을 포함시켜 주면 됩니다.

아래 예제에서 Assert부분은 화면에 출력되는 문자열을 검사하는 방식입니다. 테스트 하고자하는 목적에 따라 파일업로드가 되었을 때의 특정 위치에 파일이 생성된 것을 확인한다거나, 그 뒤에 호출되는 클래스를 행위검증하는 방식등을 다양하게 응용하실 수 있을 것입니다.

publicclassUploadServletTest {
    UploadServlet servlet =newUploadServlet() ;
    MockHttpServletRequest request =newMockHttpServletRequest();
    MockHttpServletResponse response =newMockHttpServletResponse();

    private static final String ENDLINE ="\r\n";

    private static final String BOUNDARY ="qWeRtY";

   @Test
    public void testUploadNormalFile() throws Exception {
        // given
        String fileContent ="for upload test";
        String fileName ="message.txt";
        String reqContent = createContentWithFile(fileName, fileContent);
        request.setContent(reqContent.getBytes());
        request.setContentType("multipart/form-data; boundary="+ BOUNDARY);
        request.setMethod("POST");

        // when
        servlet.service(request, response);

       // then
       String output = response.getContentAsString();
      assertTrue("정상적으로 업로드 되었을 때에는", output.contains("File size"));
    }

    @Test
   public void testUploadEmptyFile() throws Exception {
       // given
       String fileContent ="";
       String fileName ="message.txt";
       String reqContent = createContentWithFile(fileName, fileContent);
       request.setContentType("multipart/form-data; boundary="+ BOUNDARY);
       request.setMethod("POST");
       request.setContent(reqContent.getBytes());

       // when
       servlet.service(request, response);

      // then
      String output = response.getContentAsString();
      assertThat("빈 파일이 올라갔을 때에는", output, is("No binary data contains"));
    }

    private String createContentWithFile(String fileName, String fileContent) {
        StringBuilder reqContent =newStringBuilder();
        reqContent.append("--"+ BOUNDARY + ENDLINE);
        reqContent.append("Content-Disposition: form-data; name=\"myfile\";"
        +" filename=\""+ fileName +"\""+ ENDLINE);
        reqContent.append(ENDLINE);
        reqContent.append(fileContent);
        reqContent.append(ENDLINE);
        reqContent.append("--"+ BOUNDARY +"--"+ ENDLINE);
        return reqContent.toString();
    }
}

참고로 Spring에서는 MockMultipartHttpServletRequest와 MockMultipartFile 같은 첨부파일에 특화된 테스트 전용 클래스를 제공하기는 하지만, Spring MVC를 사용하지 않는 그냥 Servlet에서는 위의 방식처럼 MockHttpServletRequest을 사용해야 합니다.

정상혁정상혁

image

"컴퓨터가 내 의도를 이해했는지 표시하는 신호등을 설치하고, 그 신호에 의지해서 사람이 더 읽기 편하고 고치기 쉬운 코드를 개발하는 기법입니다.

처음에는 내가 만드는 프로그램 조각이 어떤 상황에서 어떤 결과를 내는지 설명하는 글을 씁니다. 이 것을 명세나 테스트라고 부르는데, 그 것도 컴퓨터가 해석하고 실행가능한 형식으로 만듭니다. 그 설명을 컴퓨터가 실행해서 조건과 결과가 의도한대로 되었는지 신호등으로 알려주게 합니다.  다음에는 그 설명과 맞아떨어지는 코드를 작성합니다. 신호등은 파란색이 되었다면  코드에 실린 사람의 의도를 모두 컴퓨터가 잘 받아들였다는 의미입니다. 파란색은 다음 발걸음을 내딪을 수 있다는 신호이기도 합니다.

컴퓨터를 이해시킨 다음에는 사람이 더 편안하게 볼 수 있는 코드로 다듬어야 합니다. 다시 코드를 보는 사람이 헷갈리지 않도록 중복을 없애고, 코드의 의도를 더 확실하게 표현하도록 이름을 바꾸거나 프로그램 조각의 일부를 빼내서 정리합니다. 충분히 자신이 있어서 처음부터 컴퓨터와 사람에게 모두 충분한 코드를 만들었다면, 발걸음을 크게 해서 바로 그 다음 설명과 신호등을 만듭니다. 교통 신호등처럼 시간이 고정되어 있지 않고, 신호 사이의 간격, 신호등이 확인하는 설명의 크기와 다음 신호등을 만드는 시기을 마음대로 조절해도 됩니다. 그런데 하다보면 자주 파란불을 보고 싶어서 간격을 짧게 만들고 싶어집니다.

이런 과정을 거치다보면 여러 가지를 얻습니다. 신호등이 연결된 설명서는 컴퓨터와 사람이 모두 이해할 수 있는 문서입니다. 그리고 잘 설명되고, 검증될 수 있는 코드들은 역할과 책임이 명확해서 다음에 기능을 추가하거나, 수정을 할 때 더 적은 노력이 들어갑니다. 중간 중간 내부에 설치된 신호등을 한꺼번에 켜보면 엑스레이 사진처럼 프로그램 속이 들여다 보입니다. 깊숙히 박혀 있는 오류를 찾아내는 시간도 줄여줍니다. 무엇보다 빨간 불, 파란불을 왔다갔다 하다보면 프로그래밍이 더 역동적인 일이 됩니다. 컴퓨터와 반응을 주고 받는 것이 마치 게임과 비슷해 집니다. 파란불을 볼때마다 뭔가 끝을 내고 성취했다고 느껴지고 칭찬을 받는 기분으로 그 다음 작업을 할 수 있는 힘을 얻습니다. 그래서 프로그래밍이 더 재미있어지고, 집중도 잘 됩니다. "

신입사원 교육을 하면서 주로 유명인들이 이야기한 TDD의 여러 측면들을 인용해서 설명을 했습니다. 그런데 정말 저만의 단어로 TDD를 설명한다면 어떻게 할까. 스스로 궁금해졌습니다. 적고 나니 계속 다듬어야할 것 같네요.