반응형

셀 크기 조정 (참고로 자동으로하는거 찾음) ㅠ.ㅠ

for (int i=0;i<temp;i++) //autuSizeColumn after setColumnWidth setting!!
{
sheet.autoSizeColumn(i);
sheet.setColumnWidth(i, (sheet.getColumnWidth(i))+512 ); //이건 자동으로 조절 하면 너무 딱딱해 보여서 자동조정한 사이즈에 (short)512를 추가해 주니 한결 보기 나아졌다.
}

셀 높이 조정

row.setHeight((short)512);

반응형

'Java기초' 카테고리의 다른 글

JPA(Java Persistent API)란  (0) 2021.10.21
Java Garbage Collection (펌)  (0) 2015.12.16
gc g1  (0) 2015.12.10
gc 방식  (0) 2015.12.10
gc 후 메모리 사용순서  (0) 2015.12.10
반응형

 

 

JPA(Java Persistent API)란

 -JPA 는 자바 어플리케이션에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스이다.

 - java에서 DB를 건드려서 데이터 핸들링 하려고 할 경우 보통은 아래 3가지 형태로 가능하다.

    (크게 나눠서는 Persistent Framework를 사용하거나 아님 생짜로 DB 접속 이고, 세부적으로는 주로 3가지임)

    직접 핸들링) jdbc(java Database Connectivity)를  사용하여 DB에 접속 

    Mapper 핸들링) SQL Mapper 를 활용하여 DB에 접속

                    - SQL문을 직접 명시하고, Mapper를 통해 매개변수를 맵핑하여 DB를 핸들링함

    ORM) Object-Relation Mapping . 즉, 객체와의 관계를 통한 맵핑

                   - DB Table을 java Object 로 맵핑하여 객체간의 관계를 통하여 SQL문을 자동생성하여 DB를 핸들링함

                   - Method를 통해 데이터를 조작함

Hibernate란

  - jpa 구현체 중에 한가지임

  - jpa는 자바객체와 DB를 맵핑하기 위한 API를 제공하고,  Hibernate는 이 API(인터페이스)를 구현한 것임

 

반응형

'Java기초' 카테고리의 다른 글

poi  (0) 2022.11.24
Java Garbage Collection (펌)  (0) 2015.12.16
gc g1  (0) 2015.12.10
gc 방식  (0) 2015.12.10
gc 후 메모리 사용순서  (0) 2015.12.10
반응형

gc에 대해서 가장 개요및 명확한 설명을 한 글이어서 퍼온다.

작성자분이 펴낸 책도 읽기를 개인적으로 권고하고 싶다.

출처 : http://d2.naver.com/helloworld/1329


Java Garbage Collection

지극히 개인적이고 주관적인 판단 기준을 먼저 밝힌다면, 가비지 컬렉션(Garbage Collection, 이하 GC)에 대해 잘 알고 있을수록 실력이 좋은 Java 개발자라고 생각합니다. GC 과정에 관심을 가질 정도라면 규모가 일정 이상인 애플리케이션을 제작해 본 경험이 있을 것입니다. 또, 어떤 GC 알고리즘을 선택할 것인지 고민할 정도면 스스로 제작한 애플리케이션의 특징을 정확히 이해하고 있다고 볼 수 있습니다. 이러한 판단 기준이 보편적이지는 않지만, GC에 대한 이해는 훌륭한 Java 개발자가 되기 위한 필수 조건이라는 데에는 별다른 이견이 없을 것입니다. 이 글에서는 GC 이론을 되도록 쉽게 소개하겠습니다. 피가 되고 살이 되는 글이 되기를 바랍니다.

가비지 컬렉션 과정 - Generational Garbage Collection

GC에 대해서 알아보기 전에 알아야 할 용어가 있다. 바로 'stop-the-world'이다. stop-the-world란, GC을 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것이다. stop-the-world가 발생하면 GC를 실행하는 쓰레드를 제외한 나머지 쓰레드는 모두 작업을 멈춘다. GC 작업을 완료한 이후에야 중단했던 작업을 다시 시작한다. 어떤 GC 알고리즘을 사용하더라도 stop-the-world는 발생한다. 대개의 경우 GC 튜닝이란 이 stop-the-world 시간을 줄이는 것이다.

Java는 프로그램 코드에서 메모리를 명시적으로 지정하여 해제하지 않는다. 가끔 명시적으로 해제하려고 해당 객체를 null로 지정하거나 System.gc() 메서드를 호출하는 개발자가 있다. null로 지정하는 것은 큰 문제가 안 되지만, System.gc() 메서드를 호출하는 것은 시스템의 성능에 매우 큰 영향을 끼치므로 System.gc() 메서드는 절대로 사용하면 안 된다(다행히도 NHN에서 System.gc() 메서드를 호출하는 개발자를 보진 못했다).

Java에서는 개발자가 프로그램 코드로 메모리를 명시적으로 해제하지 않기 때문에 가비지 컬렉터(Garbage Collector)가 더 이상 필요 없는 (쓰레기) 객체를 찾아 지우는 작업을 한다. 이 가비지 컬렉터는 두 가지 가설 하에 만들어졌다(사실 가설이라기보다는 가정 또는 전제 조건이라 표현하는 것이 맞다).

  • 대부분의 객체는 금방 접근 불가능 상태(unreachable)가 된다.
  • 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.

이러한 가설을 'weak generational hypothesis'라 한다. 이 가설의 장점을 최대한 살리기 위해서 HotSpot VM에서는 크게 2개로 물리적 공간을 나누었다. 둘로 나눈 공간이 Young 영역과 Old 영역이다.

  • Young 영역(Yong Generation 영역): 새롭게 생성한 객체의 대부분이 여기에 위치한다. 대부분의 객체가 금방 접근 불가능 상태가 되기 때문에 매우 많은 객체가 Young 영역에 생성되었다가 사라진다. 이 영역에서 객체가 사라질때 Minor GC가 발생한다고 말한다.
  • Old 영역(Old Generation 영역): 접근 불가능 상태로 되지 않아 Young 영역에서 살아남은 객체가 여기로 복사된다. 대부분 Young 영역보다 크게 할당하며, 크기가 큰 만큼 Young 영역보다 GC는 적게 발생한다. 이 영역에서 객체가 사라질 때 Major GC(혹은 Full GC)가 발생한다고 말한다.

영역별 데이터 흐름을 그림으로 살펴보면 다음과 같다.

JavaGarbage1

그림 1 GC 영역 및 데이터 흐름도

위 그림의 Permanent Generation 영역(이하 Perm 영역)은 Method Area라고도 한다. 객체나 억류(intern)된 문자열 정보를 저장하는 곳이며, Old 영역에서 살아남은 객체가 영원히 남아 있는 곳은 절대 아니다. 이 영역에서 GC가 발생할 수도 있는데, 여기서 GC가 발생해도 Major GC의 횟수에 포함된다.

그렇다면 "Old 영역에 있는 객체가 Young 영역의 객체를 참조하는 경우가 있을 때에는 어떻게 처리될까?"라고 궁금해 하는 분도 더러 있을 것이다. 이러한 경우를 처리하기 위해서 Old 영역에는 512바이트의 덩어리(chunk)로 되어 있는 카드 테이블(card table)이 존재한다.

카드 테이블에는 Old 영역에 있는 객체가 Young 영역의 객체를 참조할 때마다 정보가 표시된다. Young 영역의 GC를 실행할 때에는 Old 영역에 있는 모든 객체의 참조를 확인하지 않고, 이 카드 테이블만 뒤져서 GC 대상인지 식별한다.

JavaGarbage2

그림 2 카드 테이블 구조

카드 테이블은 write barrier를 사용하여 관리한다. write barrier는 Minor GC를 빠르게 할 수 있도록 하는 장치이다. write barrirer때문에 약간의 오버헤드는 발생하지만 전반적인 GC 시간은 줄어들게 된다.

Young 영역의 구성

GC를 이해하기 위해서 객체가 제일 먼저 생성되는 Young 영역부터 알아보자. Young 영역은 3개의 영역으로 나뉜다.

  • Eden 영역
  • Survivor 영역(2개)

Survivor 영역이 2개이기 때문에 총 3개의 영역으로 나뉘는 것이다. 각 영역의 처리 절차를 순서에 따라서 기술하면 다음과 같다.

  • 새로 생성한 대부분의 객체는 Eden 영역에 위치한다.
  • Eden 영역에서 GC가 한 번 발생한 후 살아남은 객체는 Survivor 영역 중 하나로 이동된다.
  • Eden 영역에서 GC가 발생하면 이미 살아남은 객체가 존재하는 Survivor 영역으로 객체가 계속 쌓인다.
  • 하나의 Survivor 영역이 가득 차게 되면 그 중에서 살아남은 객체를 다른 Survivor 영역으로 이동한다. 그리고 가득 찬 Survivor 영역은 아무 데이터도 없는 상태로 된다.
  • 이 과정을 반복하다가 계속해서 살아남아 있는 객체는 Old 영역으로 이동하게 된다.

이 절차를 확인해 보면 알겠지만 Survivor 영역 중 하나는 반드시 비어 있는 상태로 남아 있어야 한다. 만약 두 Survivor 영역에 모두 데이터가 존재하거나, 두 영역 모두 사용량이 0이라면 여러분의 시스템은 정상적인 상황이 아니라고 생각하면 된다.

이렇게 Minor GC를 통해서 Old 영역까지 데이터가 쌓인 것을 간단히 나타내면 다음과 같다.

JavaGarbage3

그림 3 GC 전과 후의 비교

참고로, HotSpot VM에서는 보다 빠른 메모리 할당을 위해서 두 가지 기술을 사용한다. 하나는 bump-the-pointer라는 기술이며, 다른 하나는 TLABs(Thread-Local Allocation Buffers)라는 기술이다.

bump-the-pointer는 Eden 영역에 할당된 마지막 객체를 추적한다. 마지막 객체는 Eden 영역의 맨 위(top)에 있다. 그리고 그 다음에 생성되는 객체가 있으면, 해당 객체의 크기가 Eden 영역에 넣기 적당한지만 확인한다. 만약 해당 객체의 크기가 적당하다고 판정되면 Eden 영역에 넣게 되고, 새로 생성된 객체가 맨 위에 있게 된다. 따라서, 새로운 객체를 생성할 때 마지막에 추가된 객체만 점검하면 되므로 매우 빠르게 메모리 할당이 이루어진다.

그러나 멀티 스레드 환경을 고려하면 이야기가 달라진다. Thread-Safe하기 위해서 만약 여러 스레드에서 사용하는 객체를 Eden 영역에 저장하려면 락(lock)이 발생할 수 밖에 없고, lock-contention 때문에 성능은 매우 떨어지게 될 것이다. HotSpot VM에서 이를 해결한 것이 TLABs이다.

각각의 스레드가 각각의 몫에 해당하는 Eden 영역의 작은 덩어리를 가질 수 있도록 하는 것이다. 각 쓰레드에는 자기가 갖고 있는 TLAB에만 접근할 수 있기 때문에, bump-the-pointer라는 기술을 사용하더라도 아무런 락이 없이 메모리 할당이 가능하다.

간단하게 Young 영역에 대한 GC에 대해서 알아보았다. 위에서 이야기한 두 가지 기술(bump-the-pointer, TLABs)을 반드시 기억하고 있을 필요는 없다. 몰라도 쇠고랑 안차고 경찰 출동 안한다. 그러나 Eden 영역에 최초로 객체가 만들어지고, Survivor 영역을 통해서 Old 영역으로 오래 살아남은 객체가 이동한다는 사실은 꼭 기억하기 바란다.

Old 영역에 대한 GC

Old 영역은 기본적으로 데이터가 가득 차면 GC를 실행한다. GC 방식에 따라서 처리 절차가 달라지므로, 어떤 GC 방식이 있는지 살펴보면 이해가 쉬울 것이다. GC 방식은 JDK 7을 기준으로 5가지 방식이 있다.

  • Serial GC
  • Parallel GC
  • Parallel Old GC(Parallel Compacting GC)
  • Concurrent Mark & Sweep GC(이하 CMS)
  • G1(Garbage First) GC

이 중에서 운영 서버에서 절대 사용하면 안 되는 방식이 Serial GC다. Serial GC는 데스크톱의 CPU 코어가 하나만 있을 때 사용하기 위해서 만든 방식이다. Serial GC를 사용하면 애플리케이션의 성능이 많이 떨어진다.

그럼 각 GC 방식에 대해서 살펴보자.

Serial GC (-XX:+UseSerialGC)

Young 영역에서의 GC는 앞 절에서 설명한 방식을 사용한다. Old 영역의 GC는 mark-sweep-compact이라는 알고리즘을 사용한다. 이 알고리즘의 첫 단계는 Old 영역에 살아 있는 객체를 식별(Mark)하는 것이다. 그 다음에는 힙(heap)의 앞 부분부터 확인하여 살아 있는 것만 남긴다(Sweep). 마지막 단계에서는 각 객체들이 연속되게 쌓이도록 힙의 가장 앞 부분부터 채워서 객체가 존재하는 부분과 객체가 없는 부분으로 나눈다(Compaction).

Serial GC는 적은 메모리와 CPU 코어 개수가 적을 때 적합한 방식이다.

Parallel GC (-XX:+UseParallelGC)

Parallel GC는 Serial GC와 기본적인 알고리즘은 같지다. 그러나 Serial GC는 GC를 처리하는 스레드가 하나인 것에 비해, Parallel GC는 GC를 처리하는 쓰레드가 여러 개이다. 그렇기 때문에 Serial GC보다 빠른게 객체를 처리할 수 있다. Parallel GC는 메모리가 충분하고 코어의 개수가 많을 때 유리하다. Parallel GC는 Throughput GC라고도 부른다.

다음 그림은 Serial GC와 Parallel GC의 스레드를 비교한 그림이다.JavaGarbage4

그림 4 Serial GC와 Parallel GC의 차이 (이미지 출처: "Java Performance", p. 86)

Parallel Old GC(-XX:+UseParallelOldGC)

Parallel Old GC는 JDK 5 update 6부터 제공한 GC 방식이다. 앞서 설명한 Parallel GC와 비교하여 Old 영역의 GC 알고리즘만 다르다. 이 방식은 Mark-Summary-Compaction 단계를 거친다. Summary 단계는 앞서 GC를 수행한 영역에 대해서 별도로 살아 있는 객체를 식별한다는 점에서 Mark-Sweep-Compaction 알고리즘의 Sweep 단계와 다르며, 약간 더 복잡한 단계를 거친다.

CMS GC (-XX:+UseConcMarkSweepGC)

다음 그림은 Serial GC와 CMS GC의 절차를 비교한 그림이다. 그림에서 보듯이 CMS GC는 지금까지 설명한 GC 방식보다 더 복잡하다.

JavaGarbage5

그림 5 Serial GC와 CMS GC(이미지 출처)

초기 Initial Mark 단계에서는 클래스 로더에서 가장 가까운 객체 중 살아 있는 객체만 찾는 것으로 끝낸다. 따라서, 멈추는 시간은 매우 짧다. 그리고 Concurrent Mark 단계에서는 방금 살아있다고 확인한 객체에서 참조하고 있는 객체들을 따라가면서 확인한다. 이 단계의 특징은 다른 스레드가 실행 중인 상태에서 동시에 진행된다는 것이다.

그 다음 Remark 단계에서는 Concurrent Mark 단계에서 새로 추가되거나 참조가 끊긴 객체를 확인한다. 마지막으로 Concurrent Sweep 단계에서는 쓰레기를 정리하는 작업을 실행한다. 이 작업도 다른 스레드가 실행되고 있는 상황에서 진행한다.

이러한 단계로 진행되는 GC 방식이기 때문에 stop-the-world 시간이 매우 짧다. 모든 애플리케이션의 응답 속도가 매우 중요할 때 CMS GC를 사용하며, Low Latency GC라고도 부른다.

그런데 CMS GC는 stop-the-world 시간이 짧다는 장점에 반해 다음과 같은 단점이 존재한다.

  • 다른 GC 방식보다 메모리와 CPU를 더 많이 사용한다.
  • Compaction 단계가 기본적으로 제공되지 않는다.

따라서, CMS GC를 사용할 때에는 신중히 검토한 후에 사용해야 한다. 그리고 조각난 메모리가 많아 Compaction 작업을 실행하면 다른 GC 방식의 stop-the-world 시간보다 stop-the-world 시간이 더 길기 때문에 Compaction 작업이 얼마나 자주, 오랫동안 수행되는지 확인해야 한다.

G1 GC

마지막으로 G1(Garbage First) GC에 대해서 알아보자. G1 GC를 이해하려면 지금까지의 Young 영역과 Old 영역에 대해서는 잊는 것이 좋다.

다음 그림에서 보다시피, G1 GC는 바둑판의 각 영역에 객체를 할당하고 GC를 실행한다. 그러다가, 해당 영역이 꽉 차면 다른 영역에서 객체를 할당하고 GC를 실행한다. 즉, 지금까지 설명한 Young의 세가지 영역에서 데이터가 Old 영역으로 이동하는 단계가 사라진 GC 방식이라고 이해하면 된다. G1 GC는 장기적으로 말도 많고 탈도 많은 CMS GC를 대체하기 위해서 만들어 졌다.JavaGarbage6

그림 6 G1 GC의 레이아웃(이미지 출처: "The Garbage-First Garbage Collector" (TS-5419), JavaOne 2008, p. 19)

G1 GC의 가장 큰 장점은 성능이다. 지금까지 설명한 어떤 GC 방식보다도 빠르다. 하지만, JDK 6에서는 G1 GC를 early access라고 부르며 그냥 시험삼아 사용할 수만 있도록 한다. 그리고 JDK 7에서 정식으로 G1 GC를 포함하여 제공한다.

그러나 JDK 7을 실서비스에서 사용하려면 많은 검증 기간(1년은 필요하다는 생각이다)을 거쳐야 할 것으로 보이기 때문에, G1 GC를 당장 사용하고 싶어도 더 기다리는 것이 좋다는 것이 개인적인 생각이다. JDK 6에서 G1 GC를 적용했다가 JVM Crash가 발생했다는 말도 몇 번 들었기에 더더욱 안정화될 때까지 기다리는 것이 좋겠다.

마치며

이번 글에서는 Java의 GC에 대해서 아주 간단하게(?) 살펴보았다. 다음 글에서는 Java의 GC 상황을 모니터링하는 방법과 GC 튜닝 방법을 알아볼 예정이다.

마지막으로 한 가지 더 말하고 싶은 것이 있다. 어떤 서비스에서 A라는 GC 옵션을 적용해서 잘 동작한다고 그 GC 옵션이 다른 서비스에서도 훌륭하게 적용되어 최적의 효과를 볼 수 있다고 생각하지 말라는 것이다.

만약 애플리케이션에서 만들어지는 모든 객체의 크기와 종류가 같다면 회사에서 사용하는 모든 WAS의 GC 옵션을 동일하게 설정할 수 있다. 하지만, 각 서비스의 WAS에서 생성하는 객체의 크기와 생존 주기가 모두 다르고, 장비의 종류도 다양하다. WAS의 스레드 개수와 장비당 WAS 인스턴스 개수, GC 옵션 등은 지속적인 튜닝과 모니터링을 통해서 해당 서비스에 가장 적합한 값을 찾아야 한다. 이 이야기는 필자의 경험에서 나온 이야기가 아니고, 2010년 JavaOne에서 Oracle JVM을 만드는 엔지니어들이 한 말이다.

참고 자료

이 글의 내용과 그림은 다음의 자료를 참고했다.


반응형

'Java기초' 카테고리의 다른 글

poi  (0) 2022.11.24
JPA(Java Persistent API)란  (0) 2021.10.21
gc g1  (0) 2015.12.10
gc 방식  (0) 2015.12.10
gc 후 메모리 사용순서  (0) 2015.12.10
반응형

반응형

'Java기초' 카테고리의 다른 글

JPA(Java Persistent API)란  (0) 2021.10.21
Java Garbage Collection (펌)  (0) 2015.12.16
gc 방식  (0) 2015.12.10
gc 후 메모리 사용순서  (0) 2015.12.10
jvm 메모리  (0) 2015.12.10
반응형

반응형

'Java기초' 카테고리의 다른 글

Java Garbage Collection (펌)  (0) 2015.12.16
gc g1  (0) 2015.12.10
gc 후 메모리 사용순서  (0) 2015.12.10
jvm 메모리  (0) 2015.12.10
단위 테스트 활용 방법: JUnit 참조 가이드  (0) 2014.11.25
반응형

반응형

'Java기초' 카테고리의 다른 글

gc g1  (0) 2015.12.10
gc 방식  (0) 2015.12.10
jvm 메모리  (0) 2015.12.10
단위 테스트 활용 방법: JUnit 참조 가이드  (0) 2014.11.25
Browser별 file name깨짐현상  (0) 2014.08.13
반응형


반응형

'Java기초' 카테고리의 다른 글

gc 방식  (0) 2015.12.10
gc 후 메모리 사용순서  (0) 2015.12.10
단위 테스트 활용 방법: JUnit 참조 가이드  (0) 2014.11.25
Browser별 file name깨짐현상  (0) 2014.08.13
replace replaceAll 차이  (0) 2014.08.01
반응형

(저자는 독자가 JUnit 대한 기본적인 지식이 있을 것이라고 가정하고 이 글을 적었다. 만약 해당 지식이 없다면 우선 웹페이지http://junit.sourceforge.net/doc/cookbook/cookbook.htm 읽기를 추천한다. 저자는 글을 통해 개발자가 단위 테스트를 작성할 때 고려해야 사항들을 기술 것이다.)

 

비용을 천문학적으로 증가시킴에도 불구하고 프로젝트에 정말 아무런 도움이 되지도 않는 단위 테스트를 작성하기란 정말 쉽다.”


이 글의 핵심 내용 : 

·      단위 테스트는 버그를 찾기 위한 것이 아니다.

·      좋은 단위 테스트를 작성하기 위한 팁들

·      하나의 테스트 케이스는 단위 기능중 하나의 시나리오만 테스트하라. 

·      불필요한 검증 구문은 작성하지 마라.

·      각 테스트는 독립적이어야 한다.

·      테스트에 필요한 모든 외부서비스와 상태들은 스텁으로 제공되야 한다.

·      시스템 설정파일에 관한 단위 테스트를 작성하지마라.

·      단위 테스트 케이스의 이름은 명확하고 일관되게 테스트의 의미를 반영해야한다.

·      외부 시스템이나 서비스에 대한 의존성이 가장 낮은 메소드들에 대해 테스트를 먼저 작성하라. 그리고 확장         해 가라.

·      프라이빗 메소드를 포함한 모든 메소드들은 가시범위에 상관없이 적절한 단위 테스트들을 작성해야 한다.

·      각각의 단위 테스트 메소드는 정확히 하나의 검증구문을 가져야 한다.

·      예상된 예외 사항을 테스트하는 단위 테스트 코드를 작성하라.

·      가장 적합한 검증 구문을 사용하라.

·      검증 구문 파라미터들은 적합한 순서대로 배치하라.

·      테스트를 위한 코드는 제품 코드에서 분리되어야 한다.

·      단위 테스트 내에서 아무것도 출력하지 마라.

·      정적 변수를 테스트 클래스에 사용하지 마라.

·      예외 발생시 단순히 테스트를 실패하기 위한 catch 구문을 작성하지 마라.

·      간접적인 테스트에 의존하지 마라.

·      위 테스트를 자동으로 실행하게 빌드 스크립트를 작성해라.

·      단위 테스트들의 실행을 생략하지 마라.(@Ignore 어노테이션을 사용하지 마라.)

·      테스트 결과를 XML 형태로 출력하라.

 

소프트웨어 개발에서 단위 테스트Unit testing는 구현코드의 개별 단위의 적합성 혹은 정확성을 확인 하기 위한 방법이다. 이 단위의 정의는 테스트 시나리오에 따라 다를 수 있다 

예를 들어서 C와 같은 절차적 프로그래밍 언어에서는 하나의 단위가 일반적으로 하나의 프로시저 또는 함수이다. 하지만 객체지향 언어에서는 하나의 메소드가 될 수 있다. 단위 테스트에서 하나의 테스트 단위는 테스트 가능한 가장 작은 부분으로 생각하면 무난하다.



단위 테스트는 버그를 찾기 위한 것이 아니다

Unit testing is not about finding bugs

 

단위 테스트의 의도를 정확히 이해하는 것은 중요하다. 단위 테스트는 단순히 버그를 찾기 위한 효과적인 방법이 아니다. 정의에 따르면 단위 테스트는 시스템의 각각의 단위들을 개별적으로 조사하는 것이다. 시스템이 구현되어 실제 환경에서 동작할 때 모든 단위들은 완벽하게 하나의 유기체로 동작해야한다. 하지만 시스템은 각 독립적으로 테스트되는 단위의 단순한 결합 그 이상으로 복잡하고 또한 에러가 발생하기 쉽다. 콤포넌트 X, Y가 독립적으로 잘 작동한다는 것이 이 콤포넌트들이 서로 호환된다든가 혹은 정확하게 조합되어졌다는 것은 아니다.

 

따라서 단순히 버그를 찾기 위한 것이라면 일반적으로 검증자가 일일이 테스트 하듯이 전체 시스템을 실제 통합환경에서 실행하는 것이 훨씬 효과적인 테스트가 될 수 있다. 그리고 이러한 테스트를 자동화 한 것을 통합 테스트라고 하는데, 이것은 일반적으로 단위 테스트와는 다른 기술들을 사용한다.

 

“단위 테스트는 TDD(Test Driven Development)에서 그러한 것처럼 반드시 시스템 디자인 단계의 일부분으로 보아야 한다.” 이렇게 함으로써 시스템 디자이너는 시스템의 가장 작은 단위 모듈을 인식할 수 있고 또한 개별적으로 테스트 할 수 있다.



Tips for writing great unit tests

하나의 테스트 케이스는 단위 기능 중 하나의 시나리오만 테스트하라

Test only one code unit at a time

 

단위 테스트 작성시 가장 중요하게 인식할 점은 테스트 단위가 복수의 테스트 시나리오들을 가질 수 있다는 것이다. 그리고 모든 테스트 시나리오들은 독립적인 테스트 코드로 작성되어져야 한다. 예를 들어 매개변수를 가지고 처리한 값을 돌려주는 함수의 테스트 케이스를 작성한다고 하면, 다음과 같은 테스트 시나리오가 가능할 것이다.

 

1.     첫 번째 파라미터가 널 값일 경우 예외 객체를 반환해야한다.

2.     두 번째 파라미터가 널 값일 경우 예외 객체를 반환해야한다.

3.     두 개의  파라미터 모두가 널 값일 경우 예외 객체를 반환해야한다.

4.     파라미터가 정상 범위 안일 경우 작업 실행 후 결과 값을 반환해야한다.

 

이러한 세분화된 테스트 케이스들은 코드를 수정하거나 리택토링시 효과적이다. 왜냐하면 단위 테스트만 수행하면 코드의 수정이 코드의 의도된 기능을 망가뜨렸는지 확인할 수 있기 때문이다. 또한 기능을 수정한다면 최소한의 테스트 코드만 수정하면 되기 때문이다.


불필요한 검증 구문을 작성하지 마라.

Don't make unnecessary assertions


단위 테스트는 시스템의 특정 단위가 어떻게 동작하는지에 대한 디자인 스펙이지 단순히 단위 내의 코드가 행하는 모든 것을 관찰하는 것이 아니다.

 

단위 내의 모든 것에 대해 검증구문을 작성하지 마라. 대신 테스트 할려고 하는 하나의 시나리오에 집중해라. 테스트 코드를 이렇게 작성하지 않으면 하나의 이유로 여러 테스트 케이스가 실패 할 수 있다. 결국 이것은 프로젝트에 아무런 도움이 되지 않는다. (왜냐하면 테스트 코드를 보고 문제점을 찾을 수 없기 때문이다.)


테스트는 독립적이어야 한다.

Make each test independent to all the others 

다른 테스트에 의존적인 꼬리에 꼬리를 무는 단위 테스트를 작성하지 마라. 이러한 테스트들은 테스트의 근본적인 실패 원인을 테스트 결과를 통해 알 수 없다. 결국 별도의 디버깅 작업을 수행해야 한다. 또한 이러한 상호 의존적인 테스트 코드는 유지보수도 번거롭다. 왜냐하면 하나의 테스트 코드를 수정할 경우 의존성을 가지고 있는 다른 코드로 수정해야 할 경우가 생기기 때문이다.

 

테스트의 선결 조건을 설정하기 위해서는 @Before/@After와 같은 테스트 프레임워크가 제공하는 어노테이션을 사용해라. 만약 서로 다른 테스트 구문을 위해 @Before/@After 어노테이션 구문 안에서 여러 가지 다른 셋팅을 해야 한다면 별도의 새로운 테스트 클래스를 생성하는 것을 고려해라.


모든 외부 서비스와 상태들에 테스트 더블을 사용해라. 

Mock out all external services and state

그렇게 하지 않으면 공통된 외부 조건를 사용하는 테스트 구문들의 결과가 서로에게 영향을 미친다. 결국 테스트 구문 실행 순서에 따라 테스트 결과가 달라지거나 네트워크 망이나 데이터베이스의 조건에 따라 결과가 달라진다.

 

게다가 외부 서비스의 버그들로 인해서 테스트 결과가 실패로 끝날 수 있다.

 

(테스트 구문이 정적 변수들을 변화시키게 하지마라. 어쩔 수없이 변화시켜야 한다면 적어도 테스트 시작 바로 전에 이 변수들을 초기화 시켜라)


시스템 설정파일에 관한 단위 테스트를 작성하지 마라

Don’t unit-test configuration settings

 

정의에 따르면 시스템 설정은 단위 테스트의 범위가 아니다(그래서 그러한 설정값은 별도의 설정파일로 분리된다). 시스템 설정값에 대한 단위 테스트를 작성하고 싶다면 설정값을 읽는 모듈에 대한 하나 혹은 두 개의 경우만 테스트해라.

 

시스템 설정에 대한 모든 경우수를 테스트를 하는 건 결국 하나 밖에 증명하지 못한다“난 복사 후 붙여넣기 할줄 알아요”


단위 테스트 케이스의 이름은 명확하고 일관되게 테스트의 의미를 반영해야한다

Name your unit tests clearly and consistently

 

이것은 언제나 명심하고 실천해야하는 중요한 점이다. 테스트 케이스의 이름은 항상 테스트의 의도가 무엇인지 반영해야한다. 단순하게 테스트 할려고 하는 단위의 클래스와 메소드의 조합을 테스트 케이스의 이름으로 사용하는건 좋은 생각이 아니다. 클래스나 메소드의 이름을 변경해야 하는 경우가 생길 때 테스트 케이스들의 이름도 매번 수정해야 한다.

 

그러나 단위 테스트 케이스의 이름이 단위의 기능을 반영하는 논리적인 이름이라면 단위 기능이 바뀌지 않는 경우에는 테스트 케이스 이름은 언제나 동일하다.

 

아래는 테스트 케이스 이름의 좋은 예들이다.

1) TestCreateEmployee_NullId_ShouldThrowException

2) TestCreateEmployee_NegativeId_ShouldThrowException

3) TestCreateEmployee_DuplicateId_ShouldThrowException

4) TestCreateEmployee_ValidId_ShouldPass


외부 시스템이나 서비스에 대한 의존성이 가장 낮은 메소드들에 대해 테스트를 먼저 작성하라. 그리고 확장해 가라

Write tests for methods that have the fewest dependencies first, and work your way up

 

예를 들어 Employee 모듈을 테스트 한다고 하자. 가장 먼저 Employee 모듈을 생성하는 코드부터 테스트를 작성하자. 왜냐하면 이 시나리오가 가장 낮은 외부 의존성을 가지기 때문이다. 이 시나리오가 성공한다면,  데이터베이스에 접근하는 테스트 코드를 추가하자.

 

데이터베이스에 Employee 정보를 가질려면 먼저 Employee 모듈을 생성하는 테스트 시나리오를 통과해야 한다. 만약 모듈을 생성하는 코드에 버그가 있다면 훨씬 빨리 발견할 수 있다.


프라이빗 메소드를 포함한 모든 메소드들은 가시범위에 상관없이 적절한 단위 테스트들을 작성해야 한다

All methods, regardless of visibility, should have appropriate unit tests


사실 이것은 논란거리이다. 소스의 코드의 핵심적인 부분은 그것이 프라이빗 메소드들이라도 반드시 테스트 되어져야 한다. 이 메소드들은 몇몇 클래스에서 호출 되어지는 어떤 결정적인 알고리즘을 가질 수 있다. 그러나 이것은 중요한 역활을 한다. 따라서 이 메소드들이 의도대로 동작하는 것을 확인해야한다.


각각의 단위 테스트 메소드는 정확히 하나의 검증구문을 가져야 한다

Aim for each unit test method to perform exactly one assertion


(번역자주 : 동의 하지 않음. 기능의 정확성을 확인하기 위해서는 복수의 검증구문이 필요할 수 있음.)


예상된 예외 사항을 테스트하는 단위 테스트 코드를 작성하라

Create unit tests that target exceptions

 

예상된 예외 사항을 검증하기 위한 테스트 케이스를 작성해야 하는 경우도 있다. 이때에는 아래와 같이 try/catch 구문으로 결과를 검증할려고 하지마라.


try{

          methodOccursDomainSpecificException();

          assertFail(Exception should be occurred);

}

catch(DomainSpecificException e){

}


대신 아래와 같이JUNIT이 제공하는 아래의 방법을 사용해라.


1.

@Test(expected=SomeDomainSpecificException.SubException.class)


가장 적합한 검증 구문을 사용하라

Use the most appropriate assertion methods


각 테스트 케이스에 사용할수 있는 많은 검증 구문이 있을 수 있다. 하지만 각 경우마다 이유와 의도에 맞게 가장 적합한 것을 사용하라.


검증 구문 파라미터들은 적합한 순서대로 배치하라

Put assertion parameters in the proper order

 

검증 구문은 대개 두 개의 파라미터를 취한다. 첫 번째 것은 테스트 결과가 패스할 때 기대하는 정상 값이고, 두 번째 것은 테스트 결과 값이다. 이 두 값들을 순서대로 작성하라. 이렇게 하면 테스트 실패시 에러 메세지를 통해 무엇이 잘못 되었는지 쉽게 확인할 수 있다.


테스트를 위한 코드는 제품 코드에서 분리되어야 한다

Ensure that test code is separated from production code

 

빌드 스크립트에서 테스트를 위한 코드는 실제 제품 코드와 같이 전달되지 않게 하라.


테스트 코드 내에서 아무것도 출력하지마라

Do not print anything out in unit tests

 

만약 테스트 케이스가 가이드 라인들에 따라 제대로 작성 되어졌다면 별도의 출력문이 필요하지 않다. 만약 출력문에 대한 필요성을 느낀다면 테스트 코드를 재검증 하라. 무엇인가 테스트 코드 중 잘못된 점이 있다.


정적 변수를 테스트 클래스에 사용하지 마라만약 사용했다면 각 테스트 케이스 실행시마다 재 초기화 해라

Do not use static members in a test class. If you have used then re-initialize for each test case


 이미 각 테스트들 간의 독립성에 대해 중요성을 언급했다. 따라서 절대 스트틱 변수들을 사용하지 마라. 만약 어쩔수 없이 사용해야 하는 경우가 발생한다면 각 테스트 케이스의 시작 전에 반드시 재 초기화 시켜라.


예외 발생시 단순히 테스트를 실패하기 위한 catc구문을 작성하지 마라. 

Do not write your own catch blocks that exist only to fail a test


 테스트 코드 내에 어떤 메소드가 예외를 발생시킬 수 있다면 단순히 테스트를 실패하기 위해 catch 구문을 사용하지 마라. 대신 throws 구문을 테스트 코드 선언시에 사용하라. 되도록이면 별도의 특정 예외 객체보다는 일반적이 Exception 예외 객체를 사용해라. 이 가이드 라인은 테스트 커버러지 증가에도 도움이 된다.


간접적인 테스트들에 의존하지 마라

Do not rely on indirect testing

 

하나의 테스트가 본래 의도된 시나리오 외 또 다른 시나리오도 테스트 한다고 가정하지마라. 이것은 혼란만 가져온다. 대신 또 다른 시나리오에 대한 추가적인 테스트 케이스를 작성하라.


단위 테스트를 자동으로 실행하게 빌드 스크립트를 작성해라

Integrate Testcases with build script

 

테스트 케이스들이 빌드 스크립트를 통해 자동적으로 실행되어지게 하라. 이것은 테스트 실행환경과 애플리케이션의 신뢰성을 높여준다.


단위 테스트들의 실행을 생략하지 마라

Do not skip unit tests

 

만약 단위 테스트의 코드가 적절하지 않다면 코드를 삭제해라. @Ignore 어노테이션을 사용하지마라. 소스코드의 부적절한 테스트 케이스를 포함하는건 아무런 도움이 되지 않는다.


테스트 결과를 XML 형태로 출력하라

Capture results using the XML formatter

 

즐거운 것은 좋은 것이다. 이 가이드 라인은 테스트 케이스 작성을 즐겁게 한다예를 들어 JUNIT을 빌드 스크립트와 통합시켜서 XML­로 테스트 결과를 출력 후 이것을 ­추가적인 포맷팅 작업을 통해 테스트 결과를 보기 좋게 바꿀 수 있다(번역자주 : JUNIT 프레임워크에서 제공하는 그린/레드UI 디스플레이를 의미하는듯 함.)



결론 

Conclusion


의심할 여지없이 단위 테스트는 소프트웨어 프로젝트 결과물의 품질을 월등히 향상시킬 수 있다. 소프트웨어 공학 관련 많은 학자들이 단위 테스트를 작성하는 것이 작성하지 않는 것보다 훨씬 효율적이라고 주장한다. 하지만 난 이 의견에 반대한다. 테스트 케이스들은 훌륭한 자산이다. 하지만 반대도 성립한다. 즉 형편없이 작성된 테스트 케이들은 프로젝트에 아무런 도움이 되지않는 골칫거리에 불과하다. 이러한 단위 테스트 코드의 품질은 개발자들이 단위 테스트의 목적과 철학을 얼마나 잘 이해하는지에 달려있는 것 같다.

 

독자 여러분이 그동안 저자가 서술한 가이드 라인을 잘 이해하고 그것을 잘 따른다면 다음 단위 테스트시 변화된 자신의 코드를 경험할 수 있을 것이다.


Happy Learning !!

 

출처 : http://jinson.tistory.com/191

반응형

'Java기초' 카테고리의 다른 글

gc 후 메모리 사용순서  (0) 2015.12.10
jvm 메모리  (0) 2015.12.10
Browser별 file name깨짐현상  (0) 2014.08.13
replace replaceAll 차이  (0) 2014.08.01
java stack trace  (0) 2014.05.09
반응형

파일 다운로드 기능 구현시에 

파일명에....브라우저별로 한글파일 및 기호 포함시 깨지는 현상에 대해 해결하기 위해 아래와 같은 내용을 검색하였다.

ㅜㅜ 원작자에게는 죄송하게도... 출처 주소를 기억하지 못해 출처를 표기 못해 너무나 죄송하다.


솔직히 브라우저별로 원인은 정확히 모르겠으나, 해당 메소드 적용시에 4가지 브라우저에서 파일명 깨짐현상이 해결됨을 테스트를 통해 확인하였다.


원작자에게 감사드립니다. 꾸벅!



... 코드는 다음과 같은 방식으로 구현되어야 한다.


// 빈 간, 특수문자, 한글에 대한 테스트

String filename = "바+보 는=바보.-똥개.txt";

String disposition = getDisposition(filename, "UTF-8", getBrowser(request););

char[] content = 적당히......;

 

response.addHeader("Content-disposition", disposition);

 

// 2009. 2. 19 추가

if ("Opera".equals(getBrowser(request))){

    response.setContentType("application/octet-stream;charset=UTF-8");

}


Writer out = response.getWriter();
out.write(content); // <-- 자기 방식으로 넣으세요.. 대충 쓴 거니..
out.flush();



... 위의 코드에서 호출하는 메소드들이다.


 

            private String getBrowser(HttpServletRequest request) {
                String header = request.getHeader("User-Agent");
                if (header.indexOf("MSIE") > -1) {
                    return "MSIE";
                } else if (header.indexOf("Chrome") > -1) {
                    return "Chrome";
                } else if (header.indexOf("Opera") > -1) {
                    return "Opera";
                }
                return "Firefox";
            }

            private String getDisposition(String filename, String browser)

                                                                            throws Exception {
                String dispositionPrefix = "attachment;filename=";
                String encodedFilename = null;
                if (browser.equals("MSIE")) {
                    encodedFilename = URLEncoder.encode(filename, "UTF-8")
                            .replaceAll("\\+", "%20");
                } else if (browser.equals("Firefox")) {
                    encodedFilename =

                 "\"" + new String(filename.getBytes("UTF-8"), "8859_1") + "\"";
                } else if (browser.equals("Opera")) {
                    encodedFilename =

                 "\"" + new String(filename.getBytes("UTF-8"), "8859_1") + "\"";
                } else if (browser.equals("Chrome")) {
                    StringBuffer sb = new StringBuffer();
                    for (int i = 0; i < filename.length(); i++) {
                        char c = filename.charAt(i);
                        if (c > '~') {
                            sb.append(URLEncoder.encode("" + c, "UTF-8"));
                        } else {
                            sb.append(c);
                        }
                    }
                    encodedFilename = sb.toString();
                } else {
                    throw new RuntimeException("Not supported browser");
                }

                return dispositionPrefix + encodedFilename;
            }

간단히 설명하자면,

1. MSIE는 URLEncoder.encode 를 이용해 UTF-8로 인코드해 주기만 하면 된다.

    (아, 공백 문제는 해결해야 함)

    보통 new String(filename.getBytes("MS949"), "8859_1")  를 이용하는데,

    이건 철저하게 국내용이다. 한글 Windows에서 실행한 IE/FF 라면 보통 이게 먹히겠지만

    외국 로케일을 적용한 경우에는 여지없이 파일명이 깨지므로

    가능하다면 어떤 경우에든 먹히는 해법을 시도하는 것이 좋다.

    이게 의심된다면 자신이 만든 어플리케이션을 유닉스에서 LANG 환경변수를 바꿔가면서

    시도해 보자.

    (혹은 Windows라면 언어 설정을 일본어나 다른 언어로 바꾸고 시도해 보자 - 09.01.13 추가)

    (이에 대한 부가설명이 http://blog.naver.com/anabaral/130042610256 에 있다 - 09.02.15 추가)


2. Firefox는 URLEncoder로 인코드한 문자열을 받아들이지 못한다.

    대신 UTF-8 혹은 브라우저가 실행되는 인코딩으로 인코드된 바이너리는 받아들이므로

    일반적으로 통용되는 방식을 사용한다.

    new String(filename.getBytes("UTF-8"), "8859_1") 

    보통은 IE에서와 마찬가지로 위에 MS949를 쓰지만, 고맙게도(?) Firefox는

    위의 인코딩을 UTF-8로만 설정해도 알아서 디코드 해 준다.

    아마도 '브라우저 인코딩' 혹은 '기본 인코딩'으로 시도하고 인식이 안되면 UTF-8을 시도하는 듯.

    다만 공백이 있으면 공백 뒤가 무시되어 잘리므로 앞뒤로 따옴표 " " 를 적용해야 한다.


3. Opera는 상당히 독특한데, Opera는 브라우저 인코딩에 맞추어 파일명을 디코드해 저장한다.

    게다가 URLEncoder 로 인코드한 내용도 안 먹힌다.

    그래서 이 경우는 정답이 없다.

    다만 사용자가 별다른 설정을 하지 않는다면 브라우저 인코딩은 그 전까지 접했던 페이지의

    인코딩을 따라가므로 우리가 일관된 인코딩 정책을 가져가고 그에 맞춰 세팅해 주면 된다.

    위의 예제에서는 그 표준이 UTF-8 이었다.

    최근에(2009.2.19)에 테스트한 바로는 Opera는 다운로드 당시의 ContentType 헤더에 정해준

    charset에 민감하다. 그래서 Content-Type 의 charset과 문자열 인코딩의 charset을 일치

    시켜주면 다운로드에 문제가 없다.

    (그래서 다른 브라우저에서의 UTF-8과는 의미가 다르다)


4. 구글 Chrome도 써보았는데, 이건 URLEncoder 인코드 문자열을 받아들이지만 뭔가 부족하다.

    몇몇 글자들(+, = 등)이 깨지기 때문이다.

    그래서 ASCII 문자(0x00 ~ 0x7e)로 간주되는 부분들은 encode하지 않는 방향으로 진행한다.


5. Safari는 지금까지는 답이 없다..

   무조건 latin1(8859_1)으로 인식하니..


이 예제의 테스트는

Internet Explorer 6,

Internet Explorer 7,

Firefox 2.0,

Firefox 3.0,

Opera 9.62,

Chrome 1.0

( 실패했지만 Safari 3.2.1)

에서 진행했다.


위의 테스트로 빈 칸, 특수문자, 한글에 대한 테스트가 가능했다.

테스트에 협조해 주신 liquidbird 님께 심심한 감사를 표한다.

반응형

'Java기초' 카테고리의 다른 글

jvm 메모리  (0) 2015.12.10
단위 테스트 활용 방법: JUnit 참조 가이드  (0) 2014.11.25
replace replaceAll 차이  (0) 2014.08.01
java stack trace  (0) 2014.05.09
JVM 메모리  (0) 2014.04.30
반응형

두 메소드는 jdk 버젼에 따라 다른 API가 제공되었다.

 

1.4

replace는  replace(char, char)

replaceAll 은 replaceAll(String, String)

 

1.5

replace는 replace(char, char)   ------> 1.4와 동일

replace는 replace(CharSequence, CharSequence)   ------>추가된 api (replaceAll 과 같은 기능인 셈?)

 

replaceAll는 replaceAll(String regex, String replacement) 

 

 

정리해보면

String 값에 대해서 replacement 하는 건, (1.5이후) replace와 replaceAll 모두 가능

그러나, regex(정규식)에 대해 replacement 는 replaceAll 만 가능하다

 

 

반응형

'Java기초' 카테고리의 다른 글

단위 테스트 활용 방법: JUnit 참조 가이드  (0) 2014.11.25
Browser별 file name깨짐현상  (0) 2014.08.13
java stack trace  (0) 2014.05.09
JVM 메모리  (0) 2014.04.30
정규식 특수문자  (0) 2013.07.15

+ Recent posts