정상혁정상혁

2010년 10월 19일부터 22일까지 시카고에서 열리는 SpringOne2GX 2010 행사에 참석하고 있는 중입니다. 정리가 완벽하게 되지 않아도 중간중간 듣고 본 것들을 올리도록 하겠습니다.

수정이력

  1. (최초작성) 키노트의 내용을 발표 때 사용한 슬라이드 자료를 보면서 정리를 하려고 마음을 먹고 있었습니다. 그래서 특별히 메모도 하지 않고 있었는데, 키노트가 끝난 다음에서야 발표 자료가 아직 올라오지 않았다는 것을 알았습니다. 그래서 대략적으로 기억나는 것들을 같이 간 분들에게 물어보고 보완하면서 트위터에 올라온 글들 ( http://twitter.com/#!/search/s2gx )을 보고 회상한 내용으로 일단 정리합니다.

키노트의 제목은?

로드존슨은 첫장에 키노트의 제목을 표시하지 않고 '여기에 제목이 들어간다’는 슬라이드 템플릿에 있을법만 문구를 그대로 보여줍니다. 나름대로 웃음을 주려고 했던 의도도 있었지만, 지난 12개월동안 스프링 세계에 어떤 일이 일어났는지를 키노트에서 정리해야 하는데, 이번에는 그것이 쉽지 않았음을 이야기 합니다. 발표가 한참 지나서야 제목이라고 할만한 내용들을 이야기하는데, 간단히 정리하면 '스프링의 성공요인이였던 Portability, Productivity, Innovation을 다음 10년에도 새로운 영역에서 이어서 가고, 개발자들이 더욱 핵심 비지니스 가치의 전달에 전념할 수 있도록 돕는다’라고 말할 수 있었습니다.

그동안 스프링 세계에 있었던 일들..

키노트 발표는 이번 컨퍼런스의 주요 후원자인 Accenture, Google, Saleforce.com에 대한 스폰서들에 대한 감사의 이야기로 시작되었습니다. 이번 컨퍼런스를 통해 비니지스 어플리케이션을 전통적인 데이터 센터에서 클라우드 환경으로 올리겠다는 비전을 강조할 것이라고 미리 예상을 했었고, 인터넷 업계, 시스템통합, SaaS 업계의 최강자들인 이들 세 업체가 그런 비전을 공유하고 후원업체로 전면에 나와 있다는 생각이 들었습니다.

이어서 얼마전에 발표되었던 vFabric cloud platform의 그림을 보여줍니다.

이 아키텍처에서 모니터링은 Hyperic, 메시징큐는 RabbitMQ, 캐슁을 위한 데이터그리드는 Gemfire가 들어가는데, 작년부터 스프링소스가 인수합병을 통해서 확보한 솔류션들입니다.

이어서 이런 솔류션들이 현재 얼마나 중요한 시스템에서 쓰이고 있는지 레퍼런스를 알려줍니다. Gemfire는 NASA나 펜타곤에서, RabbitMQ는 인도의 수많은 인구들의 주민등록 정보를 관리하는 곳에 들어가 있다고 합니다.

그리고 다른 스프링 포트폴리오 프로젝트의 대표적인 발전도 정리해줍니다.

  • Spring 3.1 : Cache abstraction, 환경에 특화된 bean 설정

  • Spring Integration 2.0 : Spring Tools Suite를 통한 plugin지원, 더 많은 Adaptor 지원

  • Spring Web flow 3.0 : Java flow Definition 지원 (https://jira.springsource.org/browse/SWF-295 이 이슈를 말한 것으로 짐작됩니다.)

  • Grails : Spring Tools Suite에 추가된 Grails 지원 기능을 Demo로 보여주었습니다. 프로젝트 생성, Entity 생성, Grails command 입력등을 Eclipse 안에서 편하게 할 수 있고, Grails View에서 Grails에 구조에 맞는 디렉토리 구성을 더 편하게 볼 수 있었습니다.

개인적으로 Cache abstraction에 가장 관심이 가는데, 이 주제를 다룰 내일 유겐할러의 발표를 기대해 봅니다.

스프링의 지난 10년과 그 다음

로드존슨은 스프링 프레임웍이 이제 새로운 10년대를 맞이하고 있음을 강조합니다. 로드존슨이 직접 밝힌 스프링에서 가장 오래된 클래스는 RequstHandlerEvent.java로 2001년 1월 17일 처음 만들어졌습니다. (스프링의 오래된 클래스들에 대해서는 토비님이 쓰신 스프링코드의 역사 글에서도 재밌는 정보들을 찾을 수 있습니다.) 로드 존슨의 큰 아들의 다음 생일이 10번째 생일인데, 걔가 스프링보다는 나이가 어리다고 합니다.

그렇게 10년을 지나오면서 스프링을 성공으로 이끌었던 핵심가치들은 Portability, Productivity, Innovation의 3가지 단어로 설명했습니다.

Jetty, Tomcat을 포함한 많은 Application Server들에서 Spring 애플리케이션이 돌아갈 수 있었떤 Portability는 이제 다음 목표를 이제 Google App Engine, VMForce와 같은 클라우스 서버 영역으로까지 나아가고 있습니다. 그리고 Grails, Spring Roo, Spring Tools Suite를 이용한 생산성 향상도 계속되고 있습니다. Spring Roo에서는 GWT 지원, Database reverese-engineering 기능이 포함된 다는 것을 홍보했습니다. 바이트코드 삽입(Instrumentation)을 통한 Application Monitoring 도구인 Spring Insight를 운영환경에서 사용할 수 있도록 성능저하가 없는 버전을 준비하고 있다고 합니다.

스프링이 나아가는 새로운 영역들 중에 Social media 결합, NoSQL 저장소 지원, 모바일 환경의 다양한 Client 지원 기능등이 특히나 신선한 소식이였습니다.

새로운 영역들

NoSql

로드존슨은 NoSQL을 Not only SQL이라고 풀어줬습니다. 전통적인 데이터 저장소였던 RDB도 나름대로의 영역을 지키겠지만, 이제는 RDB만으로는 해결할 수 없는 문제들이 많아 생겼다는 것입니다. 그렇다고 RDB를 배제하는 것이 아니라는 것을 Not only SQL이라는 말로 강조했다고 느껴졌습니다.

이미 GORM에서는 Redis를 지원하는 Addon을 넣었다는 소식을 이미 들은 적이 있습니다.

이날은 neo4j(http://neo4j.org/)를 이용한 GraphDB 지원에 대한 코드를 보여줬습니다. Graph DB는 친구사이 관계 같은 것이 저장되는 social media같은 서비스에 적합한 구조인데, 스프링과 neo4j의 결합은 원래 neo4j의 API를 쓰지 않고 annotation으로 필드 간의 관계를 설정하는 것이였습니다. 이것도 내부적으로는 Aspect J를 이용해서 동작한다고 했습니다. Graph DB를 위한 annotation은 JPA annotation과 같이 쓰여서 Domain object에서 관계형 DB와 Graph DB에 연결되는 정보를 동시에 볼 수 있었습니다. 로드존슨은 이를 polyglot persistence, cross persistence라고 표현했습니다. 다수의 저장소를 활용할 때 Java Object가 그 연결정보의 구심점이 되는 모습이 간단한 코드에서 잘 보여졌습니다. Spring roo에서도 Neo4j를 위한 Addon이 들어갔다고 합니다.

Spring-data 프로젝트(http://git.springsource.org/spring-data)에서는 Graph DB이외에도 key-value, document, column 저장소들을 위한 하위 프로젝트가 진행되고 있었습니다.

Social Media와 다양한 client의 시대

로드존슨은 근래의 기술환경이 Mobile 환경의 다양한 Client와 브라우저에 대처해야 한다는 것을 상기시킵니다. 그리고 Twitter와 Facebook과 같이 Social Media와 연결된 개발도 중요한 이슈입니다. 스프링에서도 이에 대비하고 있는데, Spring-mobile(http://git.springsource.org/spring-mobile)과 Spring-social(http://git.springsource.org/spring-mobile%29%EA%B3%BC%20Spring-social%28http://git.springsource.org/spring-socialhttp://git.springsource.org/spring-social[http://git.springsource.org/spring-social] ) 프로젝트가 이와 관련되어 있습니다. 그리고 GreenHouse(http://git.springsource.org/greenhouse) 프로젝트가 이들을 활용한 실제 예제가 되는 프로젝트입니다. GreenHouse는 https://greenhouse.springsource.org/ URL로 들어가볼 수 있고, Spring 개발자들의 social network 기능을 할 수 있어 보였습니다. iPhone와 Android client가 있는데, iPhone client는 시뮬레이터를 통해서 데모를 보여줬습니다.

로드존슨은 지금까지 스프링이 많은 영역들을 지원해왔지만, 그 중에 빠진 연결점(Missing Link)가 있었고, 그 부분은 소스관리, 이슈관리, Contious Integration, 배포에 관한 영역이였다고 합니다. 개발자들의 각각의 도구를 설치한다고 많은 시간을 소모하고 있는데, 이제 그 영역도 Code to Cloud라는 통합솔류션을 제공한다고 합니다. 이 솔류션은 Git + Hudson + Bugzilra + Mylyn + STS을 엮은 것이였습니다. Tasktop이라는 업체와 제휴를 통해 이를 제공했고, 시연도 직접 Tasktop의 CEO가 나와서 했습니다.

정상혁정상혁

Static Import 설정

Organize import (Ctrl + Shift + O) 해도 static import의 *가 풀리지 않도록 하는 설정. Junit4 이후의 assert나, mock library, matcher 등을 사용할 때 편리하다.

  • Windows-Preference-Orgnize importNumber of static imports…​ 를 1로

organize-imports.jpg

Test 메소드와 Import에 대한 Template 설정

Window-Preference-Java-Editors-Template 에 자주 쓰는 템플릿을 추가한다.

각각의 항목의 의미는 다음과 같다

  • name : 축약해서 사용할 문자

  • context : 해당 템플릿을 사용하는 에디터 종류. 여기서는 Java코드를 입력하므로 'Java’로 선택한다.

  • pattern : 템플릿 내용

즉, Java에디터에서 'name' 으로 정한 문자열을 치고 Ctrl + Space를 누르면 'pattern’의 내용이 자동입력 된다.

templates.jpg

Test 메소드 추가

테스트 메소드를 추가할 때 필요한 library import로 메소드 선언을 한꺼번에 해주는 템플릿이다.이름을 spec으로 지정해서 쓰고 있다.

@${testType:newType(org.junit.Test)}

public void ${specDescription}() {
  // given ${cursor}

  // when
  // then
  ${staticImport:importStatic('org.junit.Assert.*', 'org.mockito.BDDMockito.*', 'org.hamcrest.CoreMatchers.*')}
}

Test 라이브러리 import

위의 메소드 추가 template에서 해주는 일 중 import를 하는 부분만 수행해준다. ti라는 이름으로 지정해서 쓰고 있다. (http://wiki.kwonnam.pe.kr/java/junit/staticimports 에서 참조했습니다.)

${is1:importStatic('org.hamcrest.CoreMatchers.*')}${is2:importStatic('org.junit.Assert.*')}${is5:importStatic('org.mockito.Mockito.*')}

Spring test 지원 Annotation 추가

Junit4에서 Spring의 Application context를 올리는 테스트를 할 때 필요한 Annotation과 import 선언을 추가해주는 Template이다. 'springtest’라는 이름으로 지정해서 쓰고 있다.

${:import('org.junit.runner.RunWith','org.springframework.test.context.ContextConfiguration','org.springframework.test.context.junit4.SpringJUnit4ClassRunner')}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/applicationContext.xml"})

Mockito 관련 템플릿 추가

JUnit에서 Mocito의 annotation을 편하게 쓸 수 있게 해주는 선언이다. 'mockrun’이라는 이름으로 지정해서 쓰고 있다.

${:import('org.mockito.runners.MockitoJUnitRunner')}
@RunWith(MockitoJUnitRunner.class)

Favorite 설정

자주 쓰는 static import 등록할 수 있음. 여기에 등록하면 미리 해당 라이브러리를 static import하지 않아도 Conentnt assist(Ctrl + Space)에서 에서 나오게 됨

  • Windows-Preference-…​…​- Favorites

favorites.jpg

Favorites에 등록을 추천하는 클래스 * org.junit.Assert. * org.hamcrest.CoreMatchers. * org.mockito.Mockito. * org.mockito.BDDMockito. * org.mockto.Matchers.*

자동생성되는 메소드에 UnsupportedOperationException 던지기 설정

자동생성 되어서 아직 구현되지 않은 메소드를 test fail로 인식하게 함 ( http://toby.epril.com/?p=706 참고)

  • Preference – Java – Code Style – Code Templates 안에 Code/Method Body에 아래 코드추가

throws new UnsupportedOperationException();

unsupported-operation-exception.jpg

테스트 코드 짤 때 자주 쓰는 단축키

코드 생성

  • Ctrl+ 1 : Quick fix

  • Ctrl + Shift + O : import 절에 없는 클래스를 추가하거나 정리

  • Ctrl + Shirt + M : import 추가. 클래스 import를 static import로 전환할 수 있음.

테스트 실행

  • Alt + Shift + X, T : JUnit으로 실행

  • Ctrl + F11 : Run (JUnit 실행 가능)

코드 사이를 이동

  • Ctrl + J : 테스트 코드와 실제 코드 사이를 이동 (moreUnit이 설치되어 있을 때)

  • Ctrl + Q : 가장 마지막에 편집한 코드가 있는 곳으로 돌아가기

  • Ctrl + T : 인터페이스에서 구현 클래스 찾을 때

  • Ctrl + Shift + 위아래 방향키 : method 단위로 커서 이동(method 하나만 test 실행할 때 사용 하기 좋음

리팩토링

  • Alt + Shift + R : 리팩토링 이름 바꾸기

  • Alt + Shift + V : 리팩토링 – 이동

  • Alt + Shift + M : 리팩토링 – 메소드 추출

  • Alt + Shift + I : 리팩토링 - 메서드 인라이닝 (추출의 반대)

  • Alt + Shift+ L : 리팩토링 local 변수 추출

More Unit

테스트 코드와 실제코드 사이를 왔다갔다 할 수 있게 하는 Eclipse plugin. TDD의 리듬 유지에 도움이 됨

Maven을 쓰고 있다면 설치후 Window-Preference-More Unit에서 아래 설정을 추가하는 것이 좋다.

  • Directory for testcases : src/test/java

  • Test Suffixes : Test

more-unit.jpg

Eclemma

Eclipse 내에서 Code Coverage 측정. http://blog.benelog.net/2212119 참조

STS를 쓴다면 Dashboard-Extensions에서 선택해서 설치해도 됨

정상혁정상혁

private 메소드를 어떻게 테스트해야 할지에 대한 질문은 테스트 코드 작성에 대한 자주 나오는 질문 중에 하나입니다. 이에 대한 답변을 간단히 정리해봅니다.

  • 많은 경우에 private method는 public 메소드에서 extract method 되어서 나온 것이므로 public을 통해서 간접적으로 테스트를 하는 것이 자연스럽습니다.

  • 그래도 private 영역만 따로 테스트를 해야지 더욱 다양한 테스트 케이스를 편하게 작성할 수 있다거나 디버깅이 쉬워진다면 설계를 개선하라는 신호로 해석할만만합니다. private 메소드가 하는 일이 크다는 신호로 해석하고 별도의 클래스로 분리하거나, 하위 클래스에서 상속을 해서 대체할 수 있는 가능성을 고려해서 protected로 해두는 것도 고려해 볼만합니다.

그럼에도 불구하고 private 메서드를 테스트해야한다면

설계 개선 등을 바로 할 수 상황이라 당장 private 메서드를 테스트해야할때 쓸수 있는 방법을 이어서 소개합니다.

  • 테스트만을 해당 메소드를 package private(default 접근자)나 protected로 바꾸어서 테스트해볼 수도 있습니다. 일반적으로 테스트를 위해서 production 코드의 접근 범위를 넓히는 것은 클래스의 노출 범위를 커지게 하므로 바람직하지 않을 수도 있습니다.

  • Reflection을 이용하면 강제적으로 private 메소드를 호출할 수 있습니다. 다만 이렇게 하면 메소드이름 부분이 String값으로 넘겨지게 되므로, compile time에 메소드명의 오타가 검증되지 못하고, refactoring으로 메소드명을 바꾸어도 자동으로 String으로 적힌 부분은 바뀌지 않는 단점이 있습니다. 부작용을 감수하고서라도 쓰겠다고 각오가 된 곳에 제한적으로 사용하기를 권장드립니다.

Reflection으로 private 메서드를 호출하는 방법들은 아래와 같습니다.

(1) PowerMock에서 Whitebox.invokeMethod(..) 메소드 활용

(3) 직접 Reflection 활용

package edu.tdd;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.junit.Test;

public class ReflectionCallUtilsTest {

    @Test
    public void testCallPrivate() throws SecurityException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        UnderTest ut = new UnderTest();
        String name = "jsh";
        String address = "서울시 마포구";
        invoke(ut, "print",name, address);
        assertThat(ut.isCalled(),is(true));
    }

    @Test
    public void testCallWithPrimitiveType() throws SecurityException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        UnderTest ut = new UnderTest();
        String name = "jsh";
        int age = 35;
        boolean printed = (Boolean)invoke(ut, "print", new Class<?>[]\{String.class, int.class}, name,age);
        assertThat(printed,is(true));
        assertThat(ut.isCalled(),is(true));
    }
    private Object invoke(Object ut, String methodName, Class<?>[] argTypes, Object ... args) throws SecurityException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Method method = ut.getClass().getDeclaredMethod(methodName, argTypes);
        method.setAccessible(true);
        return method.invoke(ut, args);
    }

    private Object invoke(Object ut, String methodName, Object ... args) throws SecurityException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        int argSize = args.length;
        Class<?>[] argTypes = new Class<?>[argSize];
        for(int i=0;i< argSize;i++){
            argTypes[i] = args[i].getClass();
        }
        return invoke(ut,methodName, argTypes, args);
    }

    static class UnderTest {
        private boolean called = false;
        public boolean isCalled(){
            return called;
        }
        private void print(String name, String address) {
            System.out.println(name);
            System.out.println(address);
            called = true;
        }
        private boolean print(String name, int age){
            System.out.println(name);
            System.out.println(age);
            called = true;
            return true;
        }
    }
}

필요하다면 test 코드 안에서 쓰이고 있는 invoke메소드를 따로 Util 클래스로 분리할 수도 있습니다.

위의 코드에서 Object .. args 넘어가는 부분이 primitive type이 포함되면 Object[] 로 바뀌는 과정에서 Wrapper class로 바뀌는 auto-boxing이 발생하게 됩니다. 그래서 매개변수에 primitive type이 있을 때는 invoke(Object ut, String methodName, Object …​ args) 사용하면 NoSuchMethodException이 발생하게 됩니다. 그럴 때는 type을 정확히 명시해 주는 invoke(Object ut, String methodName, Class<?>[] argTypes, Object …​ args) 을 사용하면 됩니다.

참고자료