크리에이티브 커먼즈 라이선스
Creative Commons License

빌드의 통합은 형상관리도구, 워크스페이스의 소스코드 관리 도구, 배포 바이너리 버전 관리 도구 등을 통해서 상당 부분 자동화를 시킬 수 있다. 빌드는 단순히 형상관리에 있는 소스 코드들을 모두 가지고 와서 실행 파일로 컴파일을 만들어주는 의미만 포함되는 것은 아니다. 물론, 이러한 자동화 역시 전체 소스 코드의 깨짐 현상을 방지할 수 있고, 이를 지속적으로 반복적으로 수행한다면 소스 코드 깨짐 현상에 대한 위험은 줄일 수 있을 것이다.


문제는 전체 소스 코드의 크기가 적고, 어느 정도 인내할 수 있는 시간 동안 빌드를 한다면 괜찮겠지만, 소스 크기가 상당하고 많은 사람들이 만들어내는 소스 코드 (심지어 서로 다른 지역에 있는 사람들이 만들어내는 소스 코드와 같이 빌드하는 경우) 라면 상황이 달라진다.


실제 약 20 ~ 30만 라인을 소스 체크아웃에서부터 약 3분 정도 소요되고(물론, 물리적인 환경에 따라 다르기는 하겠지만, 여기서는 동일 환경에서 빌드한 수치들로 상대적인 수치로 받아들이면 된다), 약 70 ~ 80 만 라인은 약 15분 정도가 소요되고, 120 ~ 130 만 라인 소스 빌드 시간은 약 30분 가량이 든다. 이와 같은 수치로 본다면, 소스 코드의 증가는 빌드 시간의 단순한 증가를 의미하는 것도 있겠지만, 그 수치로 봐서도 기하급수적인 수치로 늘어날 것으로 예상해볼 수 있다. 실제 약 50만 라인 정도를 빌드하는 시간이 4분 정도 소요되었는데, 7 ~ 80 만으로 늘어나는 경우에는 그 수치가 2배를 훨씬 넘어서는 기간이 걸렸다. (빌드 시간은 내부 코드의 복잡도도 영향을 미치기 때문에 서로 다른 성격의 애플리케이션이나 컴포넌트를 비교하는 경우에는 수치적으로는 다소 차이가 발생할 수도 있다.)



위의 그림에서 LOC의 증가 정도(기울기)와 그에 따른 빌드 시간의 증가 정도(기울기)의 차이가 점점 늘어나는 것으로 볼 수 있다. 즉, 한번에 빌드하는 LOC가 증가할수록 그에 따른 빌드 시간의 증가는 더 급격히 늘어난다고 볼 수 있다.


통합 빌드의 시간이 길어졌을 때 통합하는 시간이 걸리는 것은 당연하며, 특히 빌드 중에 빌드 에러가 나서 다시 빌드를 해야하는 경우의 수가 많아질수록 그 시간이 더 늘어난다는 것이다. 예를 들어, 30분에 걸쳐서 빌드하는 애플리케이션의 경우, 거의 빌드 막바지인 25분 경과 시점에 빌드 에러가 발생한다면, 이를 조치하고 다시 처음부터 빌드를 해야하기 때문에 그 시간은 거의 2배인 1시간 가량을 소비해야 한다. 이러한 시간 소비는 그 시간 동안 형상관리에 다시 새로운 코드를 커밋하는 과정에서 재반복이 될 우려가 있으며, 심지어는 몇시간 동안 빌드를 못하는 경우도 있다.


이러한 문제들에 대한 해결책은 빠른 빌드를 위해서 필요하며, 단순히 빌드 자동화만 도입한다고 해결되는 부분은 아니다. 빌드 자동화의 장점과 더불어서 원칙이 있는 각 모듈(컴포넌트)간의 관계를 형성하여 빌드 단위를 최소한을 나누어서 애플리케이션을 구성하도록 해야 한다.


빌드 단위의 정의


빌드 단위를 정의하려면, 최소한 아키텍처에서 배포 단위를 결정해야 한다. 통상 배포 단위는 하나의 컴포넌트 단위로 매핑하며, 이는 컴파일된 바이너리 파일들의 묶음이기도 하다. 이 배포 단위는 서로 의존관계를 형성하고, 그 의존관계들로 인해서 배포 단위는 빌드의 순서를 결정하게 된다. 즉, 컴포넌트는 해당 비즈니스 로직의 성격으로 분류되어야 하며, 이러한 컴포넌트 유형 간의 의존관계 원칙이 최소한 정의되어야 한다.


빌드 단위를 컴포넌트로 정하는 경우, 컴포넌트 유형별로 관리를 할 필요가 있으며, 각 유형을 묶어서 관리할 수 있는 체계가 필요하다. Maven을 사용하는 경우, 컴포넌트 유형을 관리하는 단위를 하나의 POM으로 묶어서 그 하위에 해당 컴포넌트들을 위치시켜 상위에서 빌드가 되면 동일한 유형의 컴포넌트들이 동시에 빌드할 수 있는 형태로 관리가 가능하다. 컴포넌트 유형은 크게 분류해보면 Business Data Type을 모아두는 형태, Utility 형태, Entity 형태, Process 형태 등으로 나눌 수 있다. 분류체계를 더 세분화시킬 수도 있지만, 기본적인 비즈니스 로직의 근간을 이루는 컴포넌트를 이와 같이 분류를 해볼 수 있다. 이 컴포넌트들은 한꺼번에 배포를 해도 되지만, 컴포넌트 유형 간의 의존관계 규칙을 강화시키거나 규정하면 해당 의존관계를 이루는 컴포넌트의 순서에 맞추어서 자동 빌드에 대한 프로세스를 나누어 볼 수 있다. 예를 들어, Process가 Entity나 Utility로의 의존관계만 형성되어 있다면, Entity나 Utility를 먼저 빌드/배포하고, Process를 그 후에 배포하도록 순서를 결정할 수 있다.


마찬가지로, 외부 시스템과 연결되는 부분을 모아두는 유형을 별도로 모아둘 수 있으며, UI와 연계되는 로직을 구성하는 UI 관련 컴포넌트를 별도의 유형으로 분류할 수도 있다. 이 경우에도 다른 컴포넌트 유형과의 의존관계를 고려해서 빌드 순서를 조정할 수도 있다.


빌드 프로세스 세팅


통상 빌드 자동화 도구(CI, Continuous Integration)를 사용하는 하나의 빌드 작업과 다른 빌드 작업을 연결시킬 수가 있으며, 이 빌드 작업은 여러개의 빌드 작업으로 분화(fork) 가능하다.


즉, 컴포넌트 유형의 빌드 순서가 A -> B -> (C | D | E) 라고 한다면, A의 빌드 작업이 종료함과 동시에 B의 빌드 작업을 연결해서 기동시킬 수 있으며, B의 빌드작업이 종료됨과 동시에 C, D, E의 빌드 작업이 동시에 수행하는 것이 가능하다.


이와 같이 하나의 애플리케이션을 구성하는 컴포넌트를 빌드하는데 빌드 프로세스 형태로 구성했을 때의 장점은 무엇보다 적은 코드의 빌드 작업이 수행되기 때문에 전체 코드를 빌드하는 것보다도 시간이 더 적게 걸릴 수 있으며, 중간에 빌드 에러가 발생되면, 멈춘 작업에서 에러를 수정하여 다시 이전에 수행된 작업을 다시 기동하지 않고 그 이후부터 다시 빌드를 시작할 수 있는 장점을 가진다.



위의 그림에서 위의 알파벳으로 표현된 부분은 제일 하단에 있는 하나의 애플리케이션을 구성하는 컴포넌트 유형을 분리하여 빌드 프로세스로 구성한 것이며, 그 순서는 오른쪽에 나타나 있다. 이들 각각의 빌드 작업들의 걸리는 시간의 총합은 총 10분 30초 정도가 되며, 전체를 하나의 빌드 작업으로 구성하는 경우에 약 14분 정도가 소요되었다. 즉, 약 1.5배의 시간이 더 걸리는 셈이다.


빌드를 이와 같이 애플리케이션 전체를 통합하여 하나의 작업을 만드는 것보다 가능하면 나누어서 빌드를 하도록 구성하는 편이 시간적인 비용 측면에서 크게 이익이 된다.


고려 사항(한계)


빌드를 자그마한 단위로 나누어서 작업을 만드는 경우, 도구의 지원에 따라서 분화(fork) 프로세스는 가능하지만, 병합(join) 프로세스를 지원을 하지 않는 경우가 있다. 위의 경우에도 H, J, L, N으로 나누어진 빌드 프로세스가 모두 O 작업으로 병합되지만, 실제로는 O가 4번 실행되는 형태로 빌드 작업으로 구성되어 있다. 하지만, 이렇다 하더라도 빌드 중간에 에러나는 부분에 있어서는 다시 A부터 실행하지 않아도 되기 때문에 큰 이익이 있으며, O가 4번 빌드된다고 하더라도 전체적으로 통합/빌드/배포 작업에 있어서 아무런 영향은 미치지는 않는다. 다만, 불필요한 작업이 발생할 뿐이다. CI 도구가 이러한 부분을 잘 지원해주거나, 혹은 내부적으로 플러그인이나 스크립트를 사용해서 이러한 불필요한 작업에 대해서는 여력이 된다면 보정을 해줄 수도 있을 것이다.


빌드 프로세스에 맞게 컴포넌트 유형 간의 의존관계가 형성되어 있어야 한다. 예를 들어, C 작업 후 D를 수행해야 하는데, C 유형의 컴포넌들이 D 작업 이후의 유형에 대해 의존관계를 형성한다면, 위의 빌드 프로세스는 조정을 해야 하거나 해당 의존관계를 다른 형태로 구성해야 한다. 이는 아키텍처 관점에서 컴포넌트 유형의 의존관계를 빌드를 통해서 규제하거나 강제하는 효과를 낳는다.


빌드 작업에 대한 인스턴스가 하나만 수행하도록 하는 경우에는 전체적인 시간에 대한 차이가 그리 크지는 않는다. 예를 들어, 위의 빌드 프로세스에서 H, J, L, N으로 나뉘어서 동시적으로 빌드 프로세스가 수행되지 않고, 하나의 빌드 작업만을 차례로 수행하게 한다면, 전체 빌드 시간은 차이가 얼마 나지 않을 수도 있다. 하지만, 이전에 언급했듯이 처음부터 다시 빌드하는 수고는 덜 수 있는 충분한 장점을 가진다.



빌드는 자동화시키려는 노력은 필요하며, 나름대로 원칙을 세워서 작은 단위로 빌드하려는 작업들이 필요하다. 또한, 빌드를 하는 시간을 빠르게 하면 할수록 개발자가 자신이 만든 코드에 대한 피드백을 빨리 받을 수 있는 장점을 가지게 된다. (실제로 에러 수정시 수정된 내용이 제대로 되었는지를 파악하는데 통합/빌드/배포의 긴 시간으로 아무런 진전을 못하는 경우도 많다)





저작자 표시 비영리 변경 금지
신고

'Homo Faber > Techniques' 카테고리의 다른 글

빌드 시간을 더 빠르게 하라  (0) 2012.05.12
신중하게 행동하라  (0) 2010.06.16
Spring의 AOP로 구현한 테스트 스파이(Spy)  (0) 2009.12.04
비즈니스 컴포넌트와 데이터 ownership  (4) 2009.10.05
m2eclipse 설치  (0) 2009.09.22
m2eclipse 소개  (0) 2009.09.22
크리에이티브 커먼즈 라이선스
Creative Commons License
- Seb Rose

"무엇을 하든지 간에, 신중하게 행동하고 결과를 생각하라" Anon

어떠한 반복주기(iteration)든지 초반에는 일정이 편안하게 느끼게 됩니다. 하지만, 그렇다 할지라도 특정 시간에는 압박 상황을 피할 수는 없습니다. "올바르게 수행할 것"과 "빨리 수행할 것" 사이에서 선택하라고 하면 나중에 수행한 내용을 수정할 것이라는 생각에 "빨리 수행할 것"을 종종 선호하게 됩니다. 여러분 자신과 팀, 고객에게 지금 빨리 수행하고 이후에 수정할 것이라고 약속할 때에는 여러분은 분명 그러한 의도를 가지고 있습니다. 그러나 그 다음 반복주기에서는 새로운 문제가 나타나고 여러분은 이에 몰두하게 되는 일이 비일비재합니다. 이와 같이 지연된 작업과 같은 유형을 기술적인 빚(technical debt)이라 하며 여러분에게 좋은 친구는 아닙니다.특별히 마틴 파울러(Martin Fowler)는 기술적인 빚에 대한 용어 정의에서 이를 의도적인 기술적인 빚(deliberate technical debt)이라고 했으며, 이는 부주의한 기술적인 빚(inadvertent technical debt)과는 혼동해서는 안됩니다.

기술적인 빚은 대출과 비슷합니다. 여러분은 대출을 통해서 짧은 기간 동안에는 이익을 보게되지만, 이를 완전히 갚기 전까지 이자를 지불해야 합니다. 코드 상에서의 손쉬운 방법들은 기능을 추가하거나 리팩토링하는 것을 더 어렵게 만듭니다. 결함과 깨지기 쉬운 테스트 케이스를 증가시키는 원인입니다. 손쉬운 방법을 그대로 두는 시간이 길어질수록 상황이 더 악화됩니다. 여러분이 원래 코드를 수정하기 위해 작업을 수행하는 시점까지 원래의 문제 위에 코드를 리팩토링하고 수정하는 것을 더 어렵게 만드는 결코 바람직하지 못한 여러차례의 설계로 덧붙여진 덩어리가 만들어질 수 있습니다. 사실 실제로 기존 코드를 수정하려고 되돌아가는 일은 어떤 것이 매우 나빠져서 수정해야만 하는 시점인 경우가 대부분입니다. 그리고 그때까지도 코드를 수정하기 너무나 어려워서 정말로 시간이나 위험을 감수할 수 없는 경우가 종종 있습니다.

마감일정을 맞추거나 기능의 일부분을 구현하기 위해 기술적인 빚을 져야 하는 경우가 여러번 있습니다. 이러한 상황에 빠지지 않도록 하십시오. 하지만 만일 상황이 어쩔 수 없이 기술적인 빚을 지도록 한다면, 일단 그대로 진행하십시오. 그렇지만 (이 부분이 중요한 부분입니다) 기술적인 빚을 추적하고 빠른 시간 안에 이를 되갚거나 기능을 빠르게 완성하십시오.(번다운 차트에서 빠르게 기울기를 낮추십시오.) 빚을 지는 것에 대해 결정을 한 순간 이를 잊어버리지 않기 위해서 작업 카드를 작성하고 이슈 추적 시스템에 등록하십시오.

만일 그 다음 반복주기에서 빚에 대한 부분을 갚는 일정을 만들었다면, 비용을 줄어들 것입니다. 갚지 않은 빚을 그대로 둔다는 것은 이자가 증가하는 것이고 그 이자는 비용을 가시화시키도록 추적되어야 합니다. 이와 같이 함으로써 프로젝트의 기술적인 빚에 대한 비즈니스 가치에 대한 영향을 강조하게 되며 빚에 대한 변제의 적절한 우선순위를 가능하게 합니다. 빚을 계산하는 방법과 그에 대한 이자를 추적하는 방법은 특정 프로젝트에 따라 다르지만, 반드시 추적해야 합니다.

가능한 한 바로 기술적인 빚을 갚으십시오. 그렇지 않으면 경솔한 짓이 될 것입니다.


저작자 표시 비영리 변경 금지
신고

'Homo Faber > Techniques' 카테고리의 다른 글

빌드 시간을 더 빠르게 하라  (0) 2012.05.12
신중하게 행동하라  (0) 2010.06.16
Spring의 AOP로 구현한 테스트 스파이(Spy)  (0) 2009.12.04
비즈니스 컴포넌트와 데이터 ownership  (4) 2009.10.05
m2eclipse 설치  (0) 2009.09.22
m2eclipse 소개  (0) 2009.09.22
크리에이티브 커먼즈 라이선스
Creative Commons License

테스트 스파이는 테스트 더블(double) 의 한 유형으로, 대상 시스템 내의 특정 컴포넌트의 간접적인 결과 호출을 검증하기 위한 장치이다. 즉, 테스트 스파이는 해당 컴포넌트의 행위를 검증하기(behaviour verification) 위한 테스트 장치라고 볼 수 있다.
테스트 스파이는 다음과 같은 상황에서 사용된다.
  • 대상 시스템의 간접적인 결과를 검증하고자 하지만, 사전에 모든 속성의 값들을 예상할 수 없는 경우
  • Mock 객체를 사용해서 충분하게 예상을 하지 못한다고 생각할때 이를 보여주기 위한 검증을 필요한 경우
  • 동등성 비교와 같은 검증시 단정(assertion) 메소드를 사용해서 제어하지 못하는 경우

테스트 스파이에 대한 구현은 해당 로직을 대상 컴포넌트로 주입을 함으로써 비즈니스 로직 수행 중에 주입된 테스트 스파이를 수행하게 함으로써 해당 결과를 검증할 수 있다. 하지만, 이와 같은 방법으로 하는 경우, 결국 비즈니스 로직에 테스트 스파이가 주입되는 메소드나 속성이 만들어져야 한다. 따라서, 설계에 대한 조정이 필요한 작업이며, 대부분이 이러한 노력은 잘못하면 기존 로직에 손상을 입힐 수 있는 결과가 만들어질 수 있다.

이러한 방식의 테스트 스파이 주입을 Spring의 AOP를 사용한다면 어느정도 구현 로직과 테스트 구조를 달리해서 테스트 수행할 수 있으며, 기존 로직의 변경을 막을 수 있다.

Spring에서는 메소드의 실행 전후, 그리고, 예외 발생시에 해당 이벤트들을 capture하는 메커니즘을 제공한다.
이 인터페이스들은 org.springframework.aop.MethodBeforeAdvice, org.springframework.aop.AfterReturningAdvice, org.springframework.aop.ThrowsAdvice 이다. 이를 구현한 테스트 스파이 구현체를 만들고, 테스트 spring 설정 파일을 만들어서 이를 대상 컴포넌트에 proxy로 만들게끔 하고, 테스트 케이스에서 테스트를 수행하면 된다.

MethodBeforeAdvice는 메소드 수행 전에 호출되는 메소드를 가진 인터페이스로 그 메소드는 다음과 같다.

public void before(Method method, Object[] params, Object target) throws Throwable;

테스트 대상 컴포넌트의 메소드의 정보를 가진 method와 메소드가 가진 파라미터 정보인 params, 그리고 대상 컴포넌트의 인스턴스인 target을 파라미터로 가지고 있다.

AfterReturningAdvice는 메소드 수행 후에 호출되는 메소드를 가진 인터페이스로 그 메소드는 다음과 같다.

public void afterReturning(Object returnValue, Method method, Object[] params, Object target) throws Throwable;

이 메소드는 위의 before 메소드가 가진 파라미터 이외에 returnValue라는 메소드의 반환값을 가지고 있다.

마지막으로 예외 발생시에 사용되는 ThrowsAdvice 는 아무런 구현 메소드를 가지고 있지 않다. 이 인터페이스의 구현체에서는
public void afterThrowing(Throwable t) 라는 메소드를 구현하게 되면, 예외 발생시 이 메소드를 호출하게 된다.

테스트 스파이를 구현한 뒤에, 테스트 실행시 설정하는 spring XML 파일에서는 다음과 같이 선언한다.

<bean id="testSpy" class="com.company.test.spy.TesSpy"/>

<bean id="audienceAdvisor" class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
  <property name="advice" ref="testSpy" />
  <property name="expression" value="execution(* *.someMethodName(..))" />
</bean>

이상과 같이 AspectJ를 proxy로 사용하게끔 설정한 다음에 expression에서 대상이 되는 메소드를 설정하면 된다.

기존 테스트 케이스에서는 위의 설정이 포함된 spring XML을 같이 로딩하여 테스트를 수행하게 되면, 테스트 스파이를 통해 해당 메소드의 수행의 내용을 검증해볼 수 있다.

이와 같이 Spring의 AOP를 사용하게 되면, 메소드 행위에 대한 내용을 검증할 수는 있지만, 만일 해당 메소드의 로직이 길어질 경우, 그 내부를 검토하기란 쉽지 않다. 이 경우, 해당 메소드를 테스트하기 쉬운 단위로 나누어서 (private 메소드 형태) 이를 적용해볼 수 있다. 하지만, private 메소드를 구현하는 경우의 테스트는 꼭 Spring의 AOP를 사용하지 않더라도, reflection을 사용해서 테스트하는 방법도 있다.

결국, 테스트는 테스트를 수행하기 적당한 크기의 로직으로 나뉘어질 때 더 쉽고 간단한 방법을 통해 검증을 할 수 있게 된다.

저작자 표시 비영리 동일 조건 변경 허락
신고

'Homo Faber > Techniques' 카테고리의 다른 글

빌드 시간을 더 빠르게 하라  (0) 2012.05.12
신중하게 행동하라  (0) 2010.06.16
Spring의 AOP로 구현한 테스트 스파이(Spy)  (0) 2009.12.04
비즈니스 컴포넌트와 데이터 ownership  (4) 2009.10.05
m2eclipse 설치  (0) 2009.09.22
m2eclipse 소개  (0) 2009.09.22

+ Recent posts

티스토리 툴바