JUnit을 이용한 테스트케이스를 실행한 결과를 api형태의 보고서로 또는 그래픽으로 비주얼하게 보여줄 수 있습니다.

http://www.okjsp.pe.kr/docs/report 에서 확인할 수 있습니다.

아울러 hudson에서도 다른 형태의 리포트가 나옵니다.
우측 상단의 파란색 그래프입니다.

더 자세한 정보도 볼 수 있습니다.

http://www.okjsp.pe.kr:8080/job/okjsp%20site%20build/

아주 오래 미뤄왔던 일을 해내었습니다. 이제 버그 잡아야겠습니다.
재미있는 경험을 했습니다. http://code.google.com/p/daysago 라는 간단한 프로젝트를 오래 전에 열었는데 처음으로 다른 사람의 피드백을 받았습니다. 고맙게도 버그있는 코드의 수정본까지 받았습니다. 해당 코드를 패치하고 테스트케이스를 돌려보니 다른 부분에서 영향을 받는 것을 확인하고, 로직을 다시 수정해서 회귀테스트를 잘 통과했습니다. 테스트 코드가 있다는 것이 든든하게 느껴지더군요. 물론 아주 작은 코드입니다.

버그는 몇 개월 전에 발견했지만 누군가 보고 관심을 두고 있다는 것이 버그를 패치하게 된 큰 동기였습니다. 자극을 주신 분께 감사드립니다.

고전적인 자바 클래스의 테스트는 main() 메소드에 값을 찍어보는 코드를 넣어서 콘솔에서 확인합니다. 이클립스에 익숙해진 덕에 JUnit에서 System.out.println() 집어 넣는 것은 초딩같은 습관이라고 생각을 "저만"했었습니다. 하지만, 찍어본다는 것 그리고 그것을 눈으로 확인하는 것은 굉장한 심리적 안정감을 주기는 합니다. 아직 assertEquals() 는 보이지 않는 신뢰가 필요하기 때문이죠.

httpunit의 테스트코드를 보니 재밌는 부분이 있었습니다. 상당히 많은 수의 테스트케이스에 main() 메소드가 있었고, 그 메인메소드는 JUnit을 기반으로 해당 테스트코드를 수행하도록 만들어주는 것이었습니다.


suite() 메소드를 inline 리팩토링하면 junit.textui.TestRunner.run( new TestSuite( EncodingTest.class ));  를 통해서 실행을 합니다. Java Application 으로 실행한 결과는 다음과 같습니다.

..............
Time: 3.813

OK (14 tests)

물론 이 클래스는 JUnit을 통해서도 실행됩니다.

다른 사람의 코드를 읽는 것, 프로그래머 소통의 시작이 아닐까 생각해봅니다.

참고로 HttpUnitTest 클래스의 상속구조입니다. ctrl+T 로 이클립스에서 볼 수 있습니다.


 

최근 TSS에 재밌는 기사가 하나 떴습니다. 테스트케이스가 공식적으로 개발과정에 포함되는지 아니면 비공식적으로 만들어지는지에 관한 조사였습니다. 2006년에 이어서 2년 후인 2008년 설문을 진행했습니다. 데이터는 다음과 같습니다.

Answers 2008 (2006)
Unit testing is not performed: 17% (13%)
Unit testing is informal: 40% (46%)
Unit tests cases are documented: 9% (11%)
Unit tests cases and their executions are documented: 14% (16%)
We use a Test Driven Development approach: 20% (14%)

Participants: 384 (2006: 460)
Ending date: October 2008 (February 2006)
Source: Methods & Tools (http://www.methodsandtools.com/)

TDD 접근법을 사용하는 것은 20%로 증가한 것에 비해 단위테스트에 대한 노력은 오히려 감소했습니다. 그리고 의외인 것은 외국에서도 아직 단위테스트가 보편적이지 않다는 것이었습니다.

오픈소스 프로젝트 하나를 앞서 살펴봤습니다. 여기에는 test라는 테스트케이스를 모아놓은 폴더가 있었습니다. 소스의 양은 얼마나 될까요. httpunit 이라는 단위테스트용 프로젝트이기 때문에 좀 더 많이 있을 거라 예상했습니다.
예상대로 제가 회사에서 만든 테스트코드들과는 비교할 수 없이 많더군요.

요기까지가 패키지 하나에 들어있는 소스 코드들입니다. 이 코드들을 테스트하는 테스트케이스는 다음과 같습니다.
그리 많은 것은 아니지만 대략 1/5 정도 테스트케이스의 클래스들이 있지않나 생각됩니다. 각 테스트케이스 내의 테스트 메소드들이 애플리케이션에 대한 코드 커버리지를 책임지겠지요.

테스트케이스를 모아놓은 것이 테스트스위트입니다. *Suite.java 로 찾아보면 다음과 같은 스위트들이 보입니다.

빌드 스크립트에서도 테스트 관련 배치작업을 찾을 수 있습니다.



test 타겟을 실행해보니 다음과 같은 결과가 나오는군요.
test:
     [java] .........................................
     [java] .........................................
     [java] .........................................
     [java] .........................................
     [java] .........................................
     [java] .........................................
     [java] .........................................
     [java] .........................................
     [java] .........................................
     [java] .....................................F....
     [java] .........................................
     [java] .........................................
     [java] .........................................
     [java] .........................................
     [java] .........................................
     [java] .........................................
     [java] .........................................
     [java] .........................................
     [java] .....................................
     [java] Time: 22.656
     [java] There was 1 failure:
     [java] 1) testCookieAge(com.meterware.httpunit.cookies.CookieTest)junit.framework.AssertionFailedError: cookie2 expiration expected:<1223798608260> but was:<1112124642000>
     [java]  at com.meterware.httpunit.cookies.CookieTest.testCookieAge(CookieTest.java:202)
     ...
     [java]  at com.meterware.httpunit.HttpUnitSuite.main(HttpUnitSuite.java:49)
     [java] FAILURES!!!
     [java] Tests run: 775,  Failures: 1,  Errors: 0
BUILD SUCCESSFUL
Total time: 36 seconds

디버깅을 할 것은 아니지만, 이렇게 테스트케이스를 775가지 이상 갖고 있는 애플리케이션을 관리하는 것은 좀 더 수월하지 않을까 생각됩니다. 작업환경의 차이에서 발생하는 버그도 찾을 수 있고, 코드 변경으로 인한 틀어짐도 놓치지 않기 때문입니다.

읽어주셔서 감사합니다. 알찬 11월 되시길...
앞서 오픈소스를 통해서 소스 저장소에 등록되는 디렉토리 구조를 살펴보았습니다. 그리고 프로젝트 소스를 일괄처리하는 build.xml 도 살짝 들여다 보았습니다. 이클립스 툴을 쓰면 다 되는 작업을 왜 Ant 빌드스크립트를 만들어야하는지 질문을 종종 들어봤습니다. 지속적인 통합 빌드 같이 주기적으로 반복되는 작업이나 단계가 복잡한 작업들은 빌드스크립트가 효과적입니다. 클릭클릭하는 일도 지겨울 때가 있거든요. 이클립스 작업의 자동화가 빌드 스크립트라고 생각하시면 될 것입니다.

지난 번 소스 가져오기(import) 이후로 소스의 빌드 경로를 잡아보려고 합니다. 로컬pc에서 컴파일되나 안되나 확인하는 것이죠. 소스가 패키지에 맞게 제자리에 있어야 할 것이고, 참조하는 jar파일의 경로도 함께 지정해줘야 합니다.

일단 이클립스 자바 프로젝트에서 소스 폴더를 추가합니다. 자바소스가 있는 폴더를 소스폴더라고 합니다. *.java 파일과 *.properties 파일들이 위치합니다. httpunit-1.7 아래 src 폴더가 바로 소스폴더입니다. 그리고 httpunit-1.7 아래 test 라는 곳도 소스 폴더로 추가합니다. src 아래 있는 클래스들을 테스트하는 테스트케이스들의 소스 폴더입니다.

자동 빌드가 일어나면서 에러가 무진장 일어납니다. jar 파일 연결이 안되서 그렇습니다.

왼쪽 패키지 익스플로러에서 프로젝트를 선택하고 Properties 창을 엽니다. 컨텍스트 메뉴에서 제일 아래 Properties 메뉴를 선택하면 됩니다. 단축키는 alt+Enter
Java Build Path 항목을 선택하고 Libraries 탭을 클릭합니다.

우측의 Add JARs... 버튼을 클릭해서 httpunit-1.7/jars 폴더의 *.jar 파일들을 선택합니다.

오호, 에러가 줄기는 했는데, 하나 남았네요. fnfe를 클릭해서 해당 소스를 열어보겠습니다. 흠, 겁을 상실했군요. 에~ 뭐 꼭 고친다는 뜻은 아닙니다. ^^;

소스를 살짝 보니 캐릭터셋 문제로 Roger Lindsj 이름 옆에 이상한 문자가 생긴 것 같네요. 그것 때문에 아랫줄 if라인이 주석 줄로 따라 올라온 듯 합니다.

if 앞에서 엔터로 줄바꿔주니 고쳐졌습니다. ^^; 잠시 우쭐~

Problems 탭에 에러는 싹 사라졌군요. Warnings는 살짝 봐주기로 하죠. ^^;

소스 폴더가 추가된 모습입니다.

작업공간은 에러 없는 코드로 관리되는 것이 개발자 심리에 좋다고 생각을 합니다. ^^;
6월 14일 발표할 내용의 일부입니다.
JUnit에 관한 책들은 많이 있습니다.

사용자 삽입 이미지


2008년 제9회 JCO 자바컨퍼런스에서 발표할 내용입니다.
대략의 줄거리는 다음과 같습니다.

레거시 코드 관리 전략
허광남
GS이숍

목차
레거시 코드란?
남이 짠 코드 빨리 알아보기
리팩토링 방법
테스트케이스라굽쇼?
인터페이스의 역할
복사할 것인가 상속할 것인가
기능 추가 방법
팀역량

수많은 방법론 중에
개발 방법론은 넘쳐나지만 유지보수에 관한 방법론은 어디에?
소프트웨어의 변경주기는 짧아지고 있다.
웹 비즈니스 애플리케이션은 기민하고 안정적으로 변경하는 것이 관리의 목표

레거시 코드
누군가 나에게 맡겨놓은 코드
자기가 직접 짜지 않은 누군가가 만들어 놓은 코드
많은 의문의 그림자와 중압감을 내포한 코드
얽히고 설킨, 아둔하게 짜놓은, 갈아엎고 싶지만   그럴 자신이 안 생기는 코드

레거시 코드
기능 하나 추가하려면 몇 일 밤 새게 만드는 코드
어느 누구도 선뜻 나서지 않는 코드
손을 대면 댈수록 나락으로 빠져버리는 코드
“차라리 날 죽여줘”라고 절규하게 만드는 코드
개선한다는 생각 자체가 몸서리처지는 코드

남이 짠 코드 빨리 알아보기
들여쓰기 (Indent)
Code Formatter
IDE 코드 네비게이션 기능
- F3
- ctrl+K
- 형광펜

남이 짠 코드 빨리 알아보기
팀원간 용어의 일관성
변수명, 함수명의 기능에 따른 변경
- buy() -> order()
남이 짠 코드 빨리 알아보기
프레임워크 사용시 명명규칙은 절대적
- /order/order.jsp
- /js/order/order.js
- /css/order/order.css
- OrderController.java
- OrderService.java
Open Resources(ctrl+shift+R)
Convention over Configuration

리팩토링 방법
Re + Factor + ing
- 소스(Factor)의 재(Re)구성(ing)
Martin Fowler
- Refactoring, 1999
- 외부 동작은 바꾸지 않으면서    내부 구조를 개선하는 방법

사연 많은 JSP 리팩토링
JSP 파일 하나에 11,000 라인의 코드
7년간 산전화수전화 다 겪은 전설의 코드
그와 결부된 사촌형제 코드들 각각 6,7천라인
1차 프로젝트 Java와 Javascript의 분리
자바스크립트의 분리
객체를 이용하기

자바스크립트의 분리
함수에 파라미터를 통해 전달

자바스크립트의 분리
JSP, Javascript 스파게티 분리
리팩토링 고려사항
소스의 증가
- 선별적 js로딩으로 효율화
보안이슈
- 권한 및 로직 유효성 체크는 이중화 또는 서버단으로 이동
JS캐쉬 이슈
- 자주 변경되는 내용은 jsp 안에서 처리
- js파일에 날짜별 버전
서버단과 클라이언트단 혼재된 로직
- 로직을 단순화하거나 jsp에서 처리하고 분리하지 않는다.

JSP,JS 분리 효과
서버단의 속도 개선효과
  1005.352223 -> 757.8979948

javascript를 잘 하는?전문가가??js 파일을 따로 최적화 작업할 수 있다. (관리 포인트의 전문성 확보)

js 변경시 기존 jsp를 수정하지 않아도 된다.

전체 소스의 가독성과 이해력 향상 (jsp, js)

인터페이스의 역할

복사할 것인가 상속할 것인가

테스트케이스라굽쇼?

Edit & Pray
        vs
Cover & Modify

Backup, Cover Fire!
Acrobatic Net

기능 추가 방법

팀역량
깨진 유리창 비유
코드리뷰, 짝 프로그래밍
- 버그의 최소화
- 코딩 공감대 확장
- 더 이상 겁나지 않는 휴일 전화
낳은 정보다는 기른 정

정리
레거시 코드는 비운의 코드입니다.
좋은 유모를 만나서 제대로 리팩토링하면 버그 없고, 건강하게 자랄 수 있습니다.
함께 키우려면 코드 리뷰, 짝 프로그래밍 등을 이용하면 됩니다.
잘 키운 레거시 코드 하나, 열 개발자 안 부럽다

참고자료
Working Effectively With Legacy Code, Michael C. Feathers, Prentice Hall, 2005
리팩토링, 마틴 파울러, 대청, 2002
소프트웨어 공학의 사실과 오해, Robert L. Glass, 인사이트, 2004
GS이숍 주문리팩토링 보고서, 김현,김현기, GS이숍, 2008

CCL


Working Effectively With Legacy Code 내용 중에 테스트 가능한 프로그래밍 기법이 있는데, 재밌는 예제였습니다.
올바른 값을 가져오는지 테스트하기 위해서 탐침(probe)과도 같은 클래스를 만들어냅니다. 보통 메소드 안에서 흘러가는 흐름을 테스트 때문에 감지를 위한 것이지요.

package net.okjsp;
public class Car {
 public void 급정거(int level) {
  // ...
  System.out.println("버럭! "+level+"번");
  // ...
 }
}

저 부분을 테스트하기 위해서 코드를 분리합니다.

package net.okjsp;
public class Car {
 public void 급정거(int level) {
  // ...
  CarSpeaker speaker = new CarSpeaker();
  speaker.yell("버럭! "+level+"번");

  // ...
 }
}

CarSpeaker 는 내용을 감지하기 위한 미끼역할을 하게 됩니다.
package net.okjsp;
public class CarSpeaker {
 public void yell(String string) {
  System.out.println(string);
 }
}

테스트를 위해서 인터페이스를 뽑아냅니다.
package net.okjsp;
public class CarSpeaker implements Speaker {
 public void yell(String string) {
  System.out.println(string);
 }
}

package net.okjsp;
public interface Speaker {
 public void yell(String string);
}


그리고 speaker를 외부에서 지정할 수 있도록 필드로 만듭니다. 지정은 생성자에서 담당합니다.
package net.okjsp;
public class Car {
 private Speaker speaker;
 
 public Car(Speaker speaker) {
  this.speaker = speaker;
 }
 
 public void 급정거(int level) {
  // ...
  speaker.yell("버럭! "+level+"번");
  // ...
 }
}

이렇게 코드의 구성이 끝나면 감지를 전담하는 FakeSpeaker를 만들 수 있습니다.
package net.okjsp;
public class FakeSpeaker implements Speaker {
 String string;
 public void yell(String string) {
  this.string = string;
 }

 
 public String getString() {
  return string;
 }
}

테스트케이스는 다음과 같이 됩니다.
package net.okjsp;
import junit.framework.TestCase;
public class CarTest extends TestCase {
 public void test급정거() {
  FakeSpeaker speaker = new FakeSpeaker();
  Car car = new Car(speaker);
  car.급정거(1);
  assertEquals("버럭! 1번", speaker.getString());

 }
}


처음 코드 보다 설계는 복잡해졌지만, 테스트를 자동화하는 효과를 얻었습니다.
선택의 시간입니다. 그냥 System.out.println(">>>>"+level); 로 원본 클래스를 바꿔서 테스트할 것입니까, 아니면 테스트 케이스를 위해서 소스 코드의 설계를 바꿔서 하시겠습니까.

참고자료
Working Effectively With Legacy Code, Michael C. Features, Prentice Hall, P23-28
까오기님이 정리한 글
http://kkaok.pe.kr/servlet/KBoard?cmd=view&tableName=kmytip&flag=0&search=&findword=&gotoPage=1&seq=124

+ Recent posts