저작권 안내: 저작권자표시 Yes 상업적이용 No 컨텐츠변경 No

스프링5 입문

JSP 2.3

JPA 입문

DDD Start

인프런 객체 지향 입문 강의

'런타임 클래스변경'에 해당되는 글 1건

  1. 2013.04.18 WAS 배포 없이 Glue 프레임워크의 Activity(Diagram) 코드를 JUnit으로 테스트하기 (2)

최근에 시작한 프로젝트는 모사의 기간계 시스템의 기능을 개선하는 작업이다. 이 기간계 시스템은 다음의 기술들을 사용한다.

  • 클라이언트 영역
    • 마이플랫폼
  • 서버 영역
    • 글루(glue) 프레임워크 (포스데이터에서 만든 프레임워크)
      • 스프링 프레임워크 기반 (DB 연동, 트랜잭션 처리 등을 글루 프레임워크에서 확장)
    • 글루를 위한 마이플랫폼용 어댑터
개발과정은 주로 다음과 같다.
  • 서버사이드
    • 사용할 SQL 쿼리를 XML 파일에 추가한다.
    • 글루의 AD(ActivityDiagram)에서 Activity 및 Activity 흐름을 정의한다.
      • 이 Activity에서 DAO를 이용해서 쿼리를 실행한다.
  • 클라이언트 사이드
    • 화면을 만든다.
    • 화면에서 특정 이벤트(조회 버튼 클릭 등)가 발생하면 서버에 특정 Activity 흐름 실행을 요청하는 코드를 작성한다.
    • 응답을 받아 화면에 반영하는 코드를 작성한다.
스프링 설정 파일에는 다음과 같은 것들이 정의되어 있다.
  • DataSource / 모든 Activity에서 사용할 DAO / 트랜잭션 매니저

테스트하는 과정


코드를 만들면 개발자가 만든 코드가 잘 동작하는지 확인을 하게 되는데, 이 과정은 아래와 같다.

  1. 로컬에 웹로직을 실행환다.
  2. 서버 어플리케이션을 war로 묶어 웹로직에 배포한다. (평균 20~30초 정도 소요)
  3. 마이플랫폼 화면을 실행한다.
  4. 화면 조작을 통해 서버와 통신을 하고, 이 과정에서 눈으로 확인한다. (5초에서 심하면 1분 이상 소요)

그리고, 서버 영역에서 쿼리 변경, Glue AD 변경, 직접 구현한 Activity 클래스 변경 등이 발생하면 2-4 과정을 다시한다. 문제는 아주 사소한 쿼리 변경 조차도 이게 정상적으로 동작하는지 확인하려면 보통 1분 이상 소요된다는 점이다. 웹로직에 배포하고, 클라이언트를 실행해서 화면 조작을 통해 눈으로 확인해야 한다. 이거 참,, 참을 수 없는 상황이다.


원하는 것


원하는 건 이거다.

  • 서버 영역 코드가 잘 동작하는 지 확인하기 위해서 클라이언트를 최대한 사용하지 않는 것
  • 웹로직에 배포 없이 서버 영역 코드를 확인하는 것.
즉, 마이플랫폼을 최대한 사용하지 않고, 웹로직 서버를 실행하지 않으면서 서버 코드를 테스트하고 싶은 것이다. 서버 영역의 코드를 테스트하기 위해 마이플랫폼 코드까지 작성해야 하고, 마임플랫폼에서 서버를 호출해 주어야 하다보니, 뭔가 잘 안되면 이게 서버 문제인지 마이플랫폼 코드를 잘못 작성한 건지 확인하는데에도 시간이 많이 소요된다.

테스트 대상

대부분 DB 데이터 중심으로 작업이 이루어지기 때문에, 쿼리 자체는 오렌지나 SQLDeveloper와 같은 오라클 클라이언트 도구를 통해서 거의 검증이 된다. 마이플랫폼을 통해서 확인하고 싶은 것은 서버쪽 글루 AD가 올바르게 동작하는지 확인하는 것이다.

글루를 이용해서 프로그래밍 해 본 경험이 있다면 다음과 같은 Activity Diagram을 그려봤을 것이다. (난 처음 그려봤다.) 클라이언트(마이플랫폼이나 웹브라우저)에서 요청을 보내면, 글루 프레임워크는 요청에서 지정한 Activity Diagram(AD) 코드를 선택한 뒤에 그 코드에 정의된 순서에 맞춰 차례대로 Activity를 실행한다.


위 그림에서 한 개의 타원이 각각 1개의 Activity를 의미하며, 클라이언트의 요청이 들어올 경우 시작부터 끝에 이르기까지 순차적으로 연결된 Activity 들이 실행된다. 클라이언트가 요청을 보내면 위 그림에서 굵은 선으로 표시한 것과 비슷하게 시작 지점부터 끝 지점까지 실행이되는데, 바로 이 하나의 흐름이 정상적으로 동작하는 지 확인하기 위해 클라이언트를 실행하는 것이다.


JUnit을 테스트 실행의 어려움


약간(?)의 분석 과정을 거쳐 글루 AD의 한 흐름을 테스트하려면 다음의 코드를 실행하면 된다는 것을 알아내었다. (글루 프레임워크 소스는 없었기에 디컴파일러의 힘도 좀 빌렸다.)


// AD를 선택하고 차례대로 실행해주는 컨트롤러

PosBizControlIF controller = PosBizController.getController();


PosContext posContext = new PosContext();


// 클라이언트의 요청 정보를 생성

posContext.put("ServiceName", "A1101010010F-service");

posContext.put("cobSysList", "1");

posContext.put("CODE_ID", "C51010");


// AD 실행

controller.doAction(posContext);


// 결과 확인

PosRowSet rowSet = (PosRowSet) posContext.get("ds_sysList");


그런데, 문제는 PosBizControllIF의 구현 클래스인 PosBizController가 사용하는  초기화 코드에 있다. 이 초기화 코드는 최종적으로 스프링의 XmlBeanFactory를 생성하는데, 이 때 사용하는 스프링 설정 경로가 아래와 같이 하드코딩되어 있다.


// PosBizController의 생성자 부분의 코드

((PosServiceLoader)PosContext.getBeanFactory().getBeanObject("serviceLoader"));  


// PosContext.getBeanFactory()

public static PosBeanFactory getBeanFactory() {

    ...

    PosBeanFactory factory = new PosBeanFactoryImpl();

    ...

}


// 하드 코딩 되어 있는 PosBeanFactoryImpl

public class PosBeanFactoryImpl implements PosBeanFactory {

    protected XmlBeanFactory ctx;


    public PosBeanFactoryImpl() {

        ctx = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));

    }


위 클래스들은 제공되는 jar 파일에 포함되어 있고 소스도 없기 때문에 (위 내용은 디컴파일해서 알아냈다), 소스를 수정할 수 없다. (디컴파일한 코드를 수정해서 다시 밀어넣고 싶지 않았고, 이 jar 파일을 사용하는 다른 프로젝트가 수십개에 달한다.)


제품용 코드와 테스트용 코드를 별도 소스폴더로 구분하고 각각 다른 바이너리 디렉토리에 생성되도록 한 뒤에 테스트용 클래스패스의 우선순위를 높여주면, 일단 같은 파일 이름을 사용하면서 테스트 코드에서 테스트용 스프링 설정을 사용할 수 있다. 하지만, 여전히 다른 이름을 가진 스프링 설정 파일을 사용할 수는 없다.


javassit를 이용한 클래스 변경


그래서, 런타임에 클래스를 조작해서 설정 파일을 변경하는 방법을 도전해봤다. 클래스 변경 도구로는 여러 프로젝트에서 사용되는 javassit를 선택했다. 약간의 테스트를 거쳐 변경에 성공했다. 다행히 내가 하려는 건 간단하게 할 수 있었다. 실제 코드는 다음과 같다.


public abstract class AbstractPosBizControllerTest {


    protected PosBizControlIF controller;


    @Before

    public void init() throws Exception {

        ClassPool pool = ClassPool.getDefault();

        CtClass cc = pool.get("com.posdata.glue.bean.PosBeanFactoryImpl");

        CtConstructor c = cc.getDeclaredConstructor(null);

        c.insertAfter("this.ctx = new org.springframework.beans.factory.xml.XmlBeanFactory("+

            "new org.springframework.core.io.ClassPathResource("+

            "\"applicationContextForUnitTest.xml\"));");

        cc.toClass();


        controller = PosBizController.getController();

    }

}


위 코드에서 c.insertAfter()는 해당 생성자에 지정한 코드를 추가하는 것으로 결과적으로 PosBeanFactoryImpl 클래스의 생성자를 다음과 같이 변경해준다.


    public PosBeanFactoryImpl() {

        ctx = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));

        ctx = new XmlBeanFactory(new ClassPathResource("applicationContextForUnitTest.xml"));

    }


따라서, ctx는 최종적으로 applicationContext.xml 파일이 아닌 applicationContextForUnitTest.xml 파일을 사용해서 BeanFactory를 생성하게 된다. 각 테스트 코드는 앞서 작성한 추상 클래스를 상속받아 로컬에서 서버배포 없이 AD 테스트를 수행할 수 있게 되었다.


public class SomeADTest extends AbstractPosBizControllerTest {


    @Test

    public void shouldHaveResultRows() {

        PosContext posContext = new PosContext();

        posContext.put("ServiceName", "SomeAD-service");

        posContext.put("cobSysList", "1");

        posContext.put("CODE_ID", "C51010");

        controller.doAction(posContext);


        PosRowSet rowSet = (PosRowSet) posContext.get("ds_sysList");

        assertTrue(rowSet.count() > 0);

    }


}


이제 쿼리 설정 XML 파일의 사소한 변경, 글루 AD의 사소한 변경 등 서버의 사소한 변경 이후 제대로 동작하는 지 테스트하기 위해 낭비되는 시간(war 묶기->웹로직 배포->컨텍스트재시작, 마이플랫폼 클라이언트 UI 클릭질, 눈으로 확인: 보통 1분 이상)을 줄일 수 있게 되었다. 이는 곧 개발 속도의 향상이고, 이는 다시 빠른 퇴근과 잉여시간 발생으로 이어지리라!


Posted by 최범균 madvirus

댓글을 달아 주세요

  1. 박병상 2013.06.07 15:51 신고  댓글주소  수정/삭제  댓글쓰기

    ㅋ. 최차장님 안녕하세요.
    전에 유지보수하던 박병상입니다.
    GLUE 쪽을 건드리시고 계시네요.. ㅎㅎ

    저때 저걸 할수 있었다면 좋았을텐데.. ㅎ
    건강하세요.. ㅎ