태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.


http://java.sun.com/blueprints/corej2eepatterns/Patterns/index.html
top

Trackback Address :: http://www.ssial.com/trackback/323 관련글 쓰기

Write a comment


top

Trackback Address :: http://www.ssial.com/trackback/321 관련글 쓰기

Write a comment


http://java.sun.com/docs/books/tutorial/deployment/jar/basicsindex.html
top

Trackback Address :: http://www.ssial.com/trackback/311 관련글 쓰기

Write a comment


http://java.sun.com/products/java-media/jai/current.html
top

Trackback Address :: http://www.ssial.com/trackback/281 관련글 쓰기

Write a comment


한글 API

JAVA/Basic 2009/04/21 19:21
http://xrath.com/javase/ko/6/docs/ko/api/
top

Trackback Address :: http://www.ssial.com/trackback/280 관련글 쓰기

Write a comment


 

 

printf() 메소드의 옵션

형식


입력

출력

형식


입력

출력

문자

%c

'A'

A(char)

시간, 요일

%tm

Calendar 타입

01~12(월)

정수

%c

65

A(char)

%tl(엘), %tI

1~12, 0·~12(시간)

%d

65

65(10진수)

%tk,%tH

0~23, 00~23(시간)

%h,%x

65

41(16진수)

%tM

00~59(분)

%o

65

101(8진수)

%tS

00~60(초)

실수

%f

65.65

65.650000

%tb, %th

Jan, Feb, ....(1월,...)

%a

65.65

0x1.069999999ap6

%tB

January, February,...(1월,...)

%e

65.65

6.565000e+01

%tY, %y

2004, 04(년도)

%g

65.65

65.6500

%te, %td

1~31, 01~31(일)

시간

요일

%tA

Calendar

타입

Sunday(일요일)

%tT

“%tm/%tM:%tS"

%ta

Sun(일)

%tD

"%tm/%td/%td"

%tj

001~366(일)

%tF

"%ta %tb %td"

%tR

“%tH:%tM"

%tc

"%ta %tb %td %tT %tZ %tY"

top

Trackback Address :: http://www.ssial.com/trackback/279 관련글 쓰기

Write a comment


유니코드표

JAVA/Basic 2009/04/08 09:08
 

� , �
 ,
 ,
 ,
 ,
 ,
 ,
 ,
 ,
	 ,                 (수평 탭)

 ,                (줄 삽입)
 ,
 ,

 ,
 ,
 ,
 ,
 ,
 ,
 ,
 ,
 ,
 ,
 ,
 ,
 ,
 ,
 ,
 ,
 ,
 ,
 ,
  ,         (여백)
! , !
" , "  (태그에서 "로도 쓸 수 있음)
# , #
$ , $
% , %
& , & (&로 쓸 수 있음)
' , '
( , (
) , )
* , *
+ , +
, , ,
- , -
. , .
/ , /
0 , 0
1 , 1
2 , 2
3 , 3
4 , 4
5 , 5
6 , 6
7 , 7
8 , 8
9 , 9
: , :
&#59; , ;
&#60; , < (&lt;)
&#61; , =
&#62; , > (&gt;)
&#63; , ?
&#64; , @
&#65; , A
&#66; , B
&#67; , C
&#68; , D
&#69; , E
&#70; , F
&#71; , G
&#72; , H
&#73; , I
&#74; , J
&#75; , K
&#76; , L
&#77; , M
&#78; , N
&#79; , O
&#80; , P
&#81; , Q
&#82; , R
&#83; , S
&#84; , T
&#85; , U
&#86; , V
&#87; , W
&#88; , X
&#89; , Y
&#90; , Z
&#91; , [
&#92; , \
&#93; , ]
&#94; , ^
&#95; , _
&#96; , `
&#97; , a
&#98; , b
&#99; , c
&#100; , d
&#101; , e
&#102; , f
&#103; , g
&#104; , h
&#105; , i
&#106; , j
&#107; , k
&#108; , l
&#109; , m
&#110; , n
&#111; , o
&#112; , p
&#113; , q
&#114; , r
&#115; , s
&#116; , t
&#117; , u
&#118; , v
&#119; , w
&#120; , x
&#121; , y
&#122; , z
&#123; , {
&#124; , |
&#125; , }
&#126; , ~
&#127; ,
&#128; , €
&#129; , 
&#130; , ‚
&#131; , ƒ
&#132; , „
&#133; , …
&#134; , †
&#135; , ‡
&#136; , ˆ
&#137; , ‰
&#138; , Š
&#139; , ‹
&#140; , Œ
&#141; , 
&#142; , Ž
&#143; , 
&#144; , 
&#145; , ‘
&#146; , ’
&#147; , “
&#148; , ”
&#149; , •
&#150; , –
&#151; , —
&#152; , ˜
&#153; , ™
&#154; , š
&#155; , ›
&#156; , œ
&#157; , 
&#158; , ž
&#159; , Ÿ
&#160; ,            (Non-breaking space, 태그에서 &nbsp;)
&#161; , ¡   (거꾸로된 느낌표, &iexcl;)
&#162; , ¢  (센트 기호, &cent;)
&#163; , £  (&pound;)
&#164; , ¤  (현재 환율, &curren;)
&#165; , ¥   (엔, &yen;)
&#166; , ¦    (&brvbar;)
&#167; , §   (섹션 기호, &sect;)
&#168; , ¨   (움라우트, &uml;)
&#169; , ©   (&copy;)
&#170; , ª    (&ordf;)
&#171; , «     (&laquo;)
&#172; , ¬    (&not;)
&#173; , ­        (Soft hypen, &shy;)
&#174; , ®   (등록상표, &reg;)
&#175; , ¯     (Macron accent, &macr;)
&#176; , °    (Degree sign, &deg;)
&#177; , ±   (&plusmn;)
&#178; , ²    (&sup2;)
&#179; , ³    (&sup3;)
&#180; , ´    (Acute accent, &acute;)
&#181; , µ    (Micro sign, &micro;)
&#182; , ¶    (문단기호, &para;)
&#183; , ·     (중간에 점, &middot;)
&#184; , ¸     (Cedilla, &cedil;)
&#185; , ¹     (&sup1;)
&#186; , º     (Masculine ordinal, &ordm;)
&#187; , »     (&raquo;)
&#188; , ¼     (&frac14;)
&#189; , ½     (&frac12;)
&#190; , ¾     (&frac34;)
&#191; , ¿     (거꾸로된 물음표, &iquest;)

 

아래부터는 192번~255번까지 표로 나타낸 것.


 

À

&#192;

&Agrave;

Capital A, grave accent

Á

&#193;

&Aacute;

Capital A, acute accent

Â

&#194;

&Acirc;

Capital A, circumflex accent

Ã

&#195;

&Atilde;

Capital A, tilde

Ä

&#196;

&Auml;

Capital A, dieresis or umlaut mark

Å

&#197;

&Aring;

Capital A, ring (Angstrom)

Æ

&#198;

&AElig;

Capital AE diphthong (ligature)

Ç

&#199;

&Ccedil;

Capital C, cedilla

È

&#200;

&Egrave;

Capital E, grave accent

É

&#201;

&Eacute;

Capital E, acute accent

Ê

&#202;

&Ecirc;

Capital E, circumflex accent

Ë

&#203;

&Euml;

Capital E, dieresis or umlaut mark

Ì

&#204;

&Igrave;

Capital I, grave accent

Í

&#205;

&Iacute;

Capital I, acute accent

Î

&#206;

&Icirc;

Capital I, circumflex accent

Ï

&#207;

&Iuml;

Capital I, dieresis or umlaut mark

Ð

&#208;

&ETH;

Capital Eth, Icelandic

Ñ

&#209;

&Ntilde;

Capital N, tilde

Ò

&#210;

&Ograve;

Capital O, grave accent

Ó

&#211;

&Oacute;

Capital O, acute accent

Ô

&#212;

&Ocirc;

Capital O, circumflex accent

Õ

&#213;

&Otilde;

Capital O, tilde

Ö

&#214;

&Ouml;

Capital O, dieresis or umlaut mark

×

&#215;

&times;

Multiply sign

Ø

&#216;

&Oslash;

Capital O, slash

Ù

&#217;

&Ugrave;

Capital U, grave accent

Ú

&#218;

&Uacute;

Capital U, acute accent

Û

&#219;

&Ucirc;

Capital U, circumflex accent

Ü

&#220;

&Uuml;

Capital U, dieresis or umlaut mark

Ý

&#221;

&Yacute;

Capital Y, acute accent

Þ

&#222;

&THORN;

Capital Thorn, Icelandic

ß

&#223;

&szlig;

Small sharp s, German (sz ligature)

à

&#224;

&agrave;

Small a, grave accent

á

&#225;

&aacute;

Small a, acute accent

â

&#226;

&acirc;

Small a, circumflex accent

ã

&#227;

&atilde;

Small a, tilde

ä

&#228;

&auml;

Small a, dieresis or umlaut mark

å

&#229;

&aring;

Small a, ring

æ

&#230;

&aelig;

Small ae diphthong (ligature)

ç

&#231;

&ccedil;

Small c, cedilla

è

&#232;

&egrave;

Small e, grave accent

é

&#233;

&eacute;

Small e, acute accent

ê

&#234;

&ecirc;

Small e, circumflex accent

ë

&#235;

&euml;

Small e, dieresis or umlaut mark

ì

&#236;

&igrave;

Small i, grave accent

í

&#237;

&iacute;

Small i, acute accent

î

&#238;

&icirc;

Small i, circumflex accent

ï

&#239;

&iuml;

Small i, dieresis or umlaut mark

ð

&#240;

&eth;

Small eth, Icelandic

ñ

&#241;

&ntilde;

Small n, tilde

ò

&#242;

&ograve;

Small o, grave accent

ó

&#243;

&oacute;

Small o, acute accent

ô

&#244;

&ocirc;

Small o, circumflex accent

õ

&#245;

&otilde;

Small o, tilde

ö

&#246;

&ouml;

Small o, dieresis or umlaut mark

÷

&#247;

&divide;

Division sign

ø

&#248;

&oslash;

Small o, slash

ù

&#249;

&ugrave;

Small u, grave accent

ú

&#250;

&uacute;

Small u, acute accent

û

&#251;

&ucirc;

Small u, circumflex accent

ü

&#252;

&uuml;

Small u, dieresis or umlaut mark

ý

&#253;

&yacute;

Small y, acute accent

þ

&#254;

&thorn;

Small thorn, Icelandic

ÿ

&#255;

&yuml;

Small y, dieresis or umlaut mark


&#256; , Ā
&#257; , ā
&#258; , Ă
&#259; , ă
&#260; , Ą
&#261; , ą
&#262; , Ć
&#263; , ć
&#264; , Ĉ
&#265; , ĉ
&#266; , Ċ
&#267; , ċ
&#268; , Č
&#269; , č
&#270; , Ď
&#271; , ď
&#272; , Đ
&#273; , đ
&#274; , Ē
&#275; , ē
&#276; , Ĕ
&#277; , ĕ
&#278; , Ė
&#279; , ė
&#280; , Ę
&#281; , ę
&#282; , Ě
&#283; , ě
&#284; , Ĝ
&#285; , ĝ
&#286; , Ğ
&#287; , ğ
&#288; , Ġ
&#289; , ġ
&#290; , Ģ
&#291; , ģ
&#292; , Ĥ
&#293; , ĥ
&#294; , Ħ
&#295; , ħ
&#296; , Ĩ
&#297; , ĩ
&#298; , Ī
&#299; , ī
&#300; , Ĭ
&#301; , ĭ
&#302; , Į
&#303; , į
&#304; , İ
&#305; , ı
&#306; , IJ
&#307; , ij
&#308; , Ĵ
&#309; , ĵ
&#310; , Ķ
&#311; , ķ
&#312; , ĸ
&#313; , Ĺ
&#314; , ĺ
&#315; , Ļ
&#316; , ļ
&#317; , Ľ
&#318; , ľ
&#319; , Ŀ
&#320; , ŀ
&#321; , Ł
&#322; , ł
&#323; , Ń
&#324; , ń
&#325; , Ņ
&#326; , ņ
&#327; , Ň
&#328; , ň
&#329; , ʼn
&#330; , Ŋ
&#331; , ŋ
&#332; , Ō
&#333; , ō
&#334; , Ŏ
&#335; , ŏ
&#336; , Ő
&#337; , ő
&#338; , Œ
&#339; , œ
&#340; , Ŕ
&#341; , ŕ
&#342; , Ŗ
&#343; , ŗ
&#344; , Ř
&#345; , ř
&#346; , Ś
&#347; , ś
&#348; , Ŝ
&#349; , ŝ
&#350; , Ş
&#351; , ş
&#352; , Š
&#353; , š
&#354; , Ţ
&#355; , ţ
&#356; , Ť
&#357; , ť
&#358; , Ŧ
&#359; , ŧ
&#360; , Ũ
&#361; , ũ
&#362; , Ū
&#363; , ū
&#364; , Ŭ
&#365; , ŭ
&#366; , Ů
&#367; , ů
&#368; , Ű
&#369; , ű
&#370; , Ų
&#371; , ų
&#372; , Ŵ
&#373; , ŵ
&#374; , Ŷ
&#375; , ŷ
&#376; , Ÿ
&#377; , Ź
&#378; , ź
&#379; , Ż
&#380; , ż
&#381; , Ž
&#382; , ž
&#383; , ſ
&#384; , ƀ
&#385; , Ɓ
&#386; , Ƃ
&#387; , ƃ
&#388; , Ƅ
&#389; , ƅ
&#390; , Ɔ
&#391; , Ƈ
&#392; , ƈ
&#393; , Ɖ
&#394; , Ɗ
&#395; , Ƌ
&#396; , ƌ
&#397; , ƍ
&#398; , Ǝ
&#399; , Ə
&#400; , Ɛ
&#401; , Ƒ
&#402; , ƒ
&#403; , Ɠ
&#404; , Ɣ
&#405; , ƕ
&#406; , Ɩ
&#407; , Ɨ
&#408; , Ƙ
&#409; , ƙ
&#410; , ƚ
&#411; , ƛ
&#412; , Ɯ
&#413; , Ɲ
&#414; , ƞ
&#415; , Ɵ
&#416; , Ơ
&#417; , ơ
&#418; , Ƣ
&#419; , ƣ
&#420; , Ƥ
&#421; , ƥ
&#422; , Ʀ
&#423; , Ƨ
&#424; , ƨ
&#425; , Ʃ
&#426; , ƪ
&#427; , ƫ
&#428; , Ƭ
&#429; , ƭ
&#430; , Ʈ
&#431; , Ư
&#432; , ư
&#433; , Ʊ
&#434; , Ʋ
&#435; , Ƴ
&#436; , ƴ
&#437; , Ƶ
&#438; , ƶ
&#439; , Ʒ
&#440; , Ƹ
&#441; , ƹ
&#442; , ƺ
&#443; , ƻ
&#444; , Ƽ
&#445; , ƽ
&#446; , ƾ
&#447; , ƿ
&#448; , ǀ
&#449; , ǁ
&#450; , ǂ
&#451; , ǃ
&#452; , DŽ
&#453; , Dž
&#454; , dž
&#455; , LJ
&#456; , Lj
&#457; , lj
&#458; , NJ
&#459; , Nj
&#460; , nj
&#461; , Ǎ
&#462; , ǎ
&#463; , Ǐ
&#464; , ǐ
&#465; , Ǒ
&#466; , ǒ
&#467; , Ǔ
&#468; , ǔ
&#469; , Ǖ
&#470; , ǖ
&#471; , Ǘ
&#472; , ǘ
&#473; , Ǚ
&#474; , ǚ
&#475; , Ǜ
&#476; , ǜ
&#477; , ǝ
&#478; , Ǟ
&#479; , ǟ
&#480; , Ǡ
&#481; , ǡ
&#482; , Ǣ
&#483; , ǣ
&#484; , Ǥ
&#485; , ǥ
&#486; , Ǧ
&#487; , ǧ
&#488; , Ǩ
&#489; , ǩ
&#490; , Ǫ
&#491; , ǫ
&#492; , Ǭ
&#493; , ǭ
&#494; , Ǯ
&#495; , ǯ
&#496; , ǰ
&#497; , DZ
&#498; , Dz
&#499; , dz
&#500; , Ǵ
&#501; , ǵ
&#502; , Ƕ
&#503; , Ƿ
&#504; , Ǹ
&#505; , ǹ
&#506; , Ǻ
&#507; , ǻ
&#508; , Ǽ
&#509; , ǽ
&#510; , Ǿ
&#511; , ǿ
&#512; , Ȁ
&#513; , ȁ
&#514; , Ȃ
&#515; , ȃ
&#516; , Ȅ
&#517; , ȅ
&#518; , Ȇ
&#519; , ȇ
&#520; , Ȉ
&#521; , ȉ
&#522; , Ȋ
&#523; , ȋ
&#524; , Ȍ
&#525; , ȍ
&#526; , Ȏ
&#527; , ȏ
&#528; , Ȑ
&#529; , ȑ
&#530; , Ȓ
&#531; , ȓ
&#532; , Ȕ
&#533; , ȕ
&#534; , Ȗ
&#535; , ȗ
&#536; , Ș
&#537; , ș
&#538; , Ț
&#539; , ț
&#540; , Ȝ
&#541; , ȝ
&#542; , Ȟ
&#543; , ȟ
&#544; , Ƞ
&#545; , ȡ
&#546; , Ȣ
&#547; , ȣ
&#548; , Ȥ
&#549; , ȥ
&#550; , Ȧ
&#551; , ȧ
&#552; , Ȩ
&#553; , ȩ
&#554; , Ȫ
&#555; , ȫ
&#556; , Ȭ
&#557; , ȭ
&#558; , Ȯ
&#559; , ȯ
&#560; , Ȱ
&#561; , ȱ
&#562; , Ȳ
&#563; , ȳ
&#564; , ȴ
&#565; , ȵ
&#566; , ȶ
&#567; , ȷ
&#568; , ȸ
&#569; , ȹ
&#570; , Ⱥ
&#571; , Ȼ
&#572; , ȼ
&#573; , Ƚ
&#574; , Ⱦ
&#575; , ȿ
&#576; , ɀ
&#577; , Ɂ
&#578; , ɂ
&#579; , Ƀ
&#580; , Ʉ
&#581; , Ʌ
&#582; , Ɇ
&#583; , ɇ
&#584; , Ɉ
&#585; , ɉ
&#586; , Ɋ
&#587; , ɋ
&#588; , Ɍ
&#589; , ɍ
&#590; , Ɏ
&#591; , ɏ
&#592; , ɐ
&#593; , ɑ
&#594; , ɒ
&#595; , ɓ
&#596; , ɔ
&#597; , ɕ
&#598; , ɖ
&#599; , ɗ
&#600; , ɘ
&#601; , ə
&#602; , ɚ
&#603; , ɛ
&#604; , ɜ
&#605; , ɝ
&#606; , ɞ
&#607; , ɟ
&#608; , ɠ
&#609; , ɡ
&#610; , ɢ
&#611; , ɣ
&#612; , ɤ
&#613; , ɥ
&#614; , ɦ
&#615; , ɧ
&#616; , ɨ
&#617; , ɩ
&#618; , ɪ
&#619; , ɫ
&#620; , ɬ
&#621; , ɭ
&#622; , ɮ
&#623; , ɯ
&#624; , ɰ
&#625; , ɱ
&#626; , ɲ
&#627; , ɳ
&#628; , ɴ
&#629; , ɵ
&#630; , ɶ
&#631; , ɷ
&#632; , ɸ
&#633; , ɹ
&#634; , ɺ
&#635; , ɻ
&#636; , ɼ
&#637; , ɽ
&#638; , ɾ
&#639; , ɿ
&#640; , ʀ
&#641; , ʁ
&#642; , ʂ
&#643; , ʃ
&#644; , ʄ
&#645; , ʅ
&#646; , ʆ
&#647; , ʇ
&#648; , ʈ
&#649; , ʉ
&#650; , ʊ
&#651; , ʋ
&#652; , ʌ
&#653; , ʍ
&#654; , ʎ
&#655; , ʏ
&#656; , ʐ
&#657; , ʑ
&#658; , ʒ
&#659; , ʓ
&#660; , ʔ
&#661; , ʕ
&#662; , ʖ
&#663; , ʗ
&#664; , ʘ
&#665; , ʙ
&#666; , ʚ
&#667; , ʛ
&#668; , ʜ
&#669; , ʝ
&#670; , ʞ
&#671; , ʟ
&#672; , ʠ
&#673; , ʡ
&#674; , ʢ
&#675; , ʣ
&#676; , ʤ
&#677; , ʥ
&#678; , ʦ
&#679; , ʧ
&#680; , ʨ
&#681; , ʩ
&#682; , ʪ
&#683; , ʫ
&#684; , ʬ
&#685; , ʭ
&#686; , ʮ
&#687; , ʯ
&#688; , ʰ
&#689; , ʱ
&#690; , ʲ
&#691; , ʳ
&#692; , ʴ
&#693; , ʵ
&#694; , ʶ
&#695; , ʷ
&#696; , ʸ
&#697; , ʹ
&#698; , ʺ
&#699; , ʻ
&#700; , ʼ
&#701; , ʽ
&#702; , ʾ
&#703; , ʿ
&#704; , ˀ
&#705; , ˁ
&#706; , ˂
&#707; , ˃
&#708; , ˄
&#709; , ˅
&#710; , ˆ
&#711; , ˇ
&#712; , ˈ
&#713; , ˉ
&#714; , ˊ
&#715; , ˋ
&#716; , ˌ
&#717; , ˍ
&#718; , ˎ
&#719; , ˏ
&#720; , ː
&#721; , ˑ
&#722; , ˒
&#723; , ˓
&#724; , ˔
&#725; , ˕
&#726; , ˖
&#727; , ˗
&#728; , ˘
&#729; , ˙
&#730; , ˚
&#731; , ˛
&#732; , ˜
&#733; , ˝
&#734; , ˞
&#735; , ˟
&#736; , ˠ
&#737; , ˡ
&#738; , ˢ
&#739; , ˣ
&#740; , ˤ
&#741; , ˥
&#742; , ˦
&#743; , ˧
&#744; , ˨
&#745; , ˩
&#746; , ˪
&#747; , ˫
&#748; , ˬ
&#749; , ˭
&#750; , ˮ
&#751; , ˯
&#752; , ˰
&#753; , ˱
&#754; , ˲
&#755; , ˳
&#756; , ˴
&#757; , ˵
&#758; , ˶
&#759; , ˷
&#760; , ˸
&#761; , ˹
&#762; , ˺
&#763; , ˻
&#764; , ˼
&#765; , ˽
&#766; , ˾
&#767; , ˿
&#768; , ̀
&#769; , ́
&#770; , ̂
&#771; , ̃
&#772; , ̄
&#773; , ̅
&#774; , ̆
&#775; , ̇
&#776; , ̈
&#777; , ̉
&#778; , ̊
&#779; , ̋
&#780; , ̌
&#781; , ̍
&#782; , ̎
&#783; , ̏
&#784; , ̐
&#785; , ̑
&#786; , ̒
&#787; , ̓
&#788; , ̔
&#789; , ̕
&#790; , ̖
&#791; , ̗
&#792; , ̘
&#793; , ̙
&#794; , ̚
&#795; , ̛
&#796; , ̜
&#797; , ̝
&#798; , ̞
&#799; , ̟
&#800; , ̠
&#801; , ̡
&#802; , ̢
&#803; , ̣
&#804; , ̤
&#805; , ̥
&#806; , ̦
&#807; , ̧
&#808; , ̨
&#809; , ̩
&#810; , ̪
&#811; , ̫
&#812; , ̬
&#813; , ̭
&#814; , ̮
&#815; , ̯
&#816; , ̰
&#817; , ̱
&#818; , ̲
&#819; , ̳
&#820; , ̴
&#821; , ̵
&#822; , ̶
&#823; , ̷
&#824; , ̸
&#825; , ̹
&#826; , ̺
&#827; , ̻
&#828; , ̼
&#829; , ̽
&#830; , ̾
&#831; , ̿
&#832; , ̀
&#833; , ́
&#834; , ͂
&#835; , ̓
&#836; , ̈́
&#837; , ͅ
&#838; , ͆
&#839; , ͇
&#840; , ͈
&#841; , ͉
&#842; , ͊
&#843; , ͋
&#844; , ͌
&#845; , ͍
&#846; , ͎
&#847; , ͏
&#848; , ͐
&#849; , ͑
&#850; , ͒
&#851; , ͓
&#852; , ͔
&#853; , ͕
&#854; , ͖
&#855; , ͗
&#856; , ͘
&#857; , ͙
&#858; , ͚
&#859; , ͛
&#860; , ͜
&#861; , ͝
&#862; , ͞
&#863; , ͟
&#864; , ͠
&#865; , ͡
&#866; , ͢
&#867; , ͣ
&#868; , ͤ
&#869; , ͥ
&#870; , ͦ
&#871; , ͧ
&#872; , ͨ
&#873; , ͩ
&#874; , ͪ
&#875; , ͫ
&#876; , ͬ
&#877; , ͭ
&#878; , ͮ
&#879; , ͯ
&#880; , Ͱ
&#881; , ͱ
&#882; , Ͳ
&#883; , ͳ
&#884; , ʹ
&#885; , ͵
&#886; , Ͷ
&#887; , ͷ
&#888; , ͸
&#889; , ͹
&#890; , ͺ
&#891; , ͻ
&#892; , ͼ
&#893; , ͽ
&#894; , ;
&#895; , Ϳ
&#896; , ΀
&#897; , ΁
&#898; , ΂
&#899; , ΃
&#900; , ΄
&#901; , ΅
&#902; , Ά
&#903; , ·
&#904; , Έ
&#905; , Ή
&#906; , Ί
&#907; , ΋
&#908; , Ό
&#909; , ΍
&#910; , Ύ
&#911; , Ώ
&#912; , ΐ
&#913; , Α
&#914; , Β
&#915; , Γ
&#916; , Δ
&#917; , Ε
&#918; , Ζ
&#919; , Η
&#920; , Θ
&#921; , Ι
&#922; , Κ
&#923; , Λ
&#924; , Μ
&#925; , Ν
&#926; , Ξ
&#927; , Ο
&#928; , Π
&#929; , Ρ
&#930; , ΢
&#931; , Σ
&#932; , Τ
&#933; , Υ
&#934; , Φ
&#935; , Χ
&#936; , Ψ
&#937; , Ω
&#938; , Ϊ
&#939; , Ϋ
&#940; , ά
&#941; , έ
&#942; , ή
&#943; , ί
&#944; , ΰ
&#945; , α
&#946; , β
&#947; , γ
&#948; , δ
&#949; , ε
&#950; , ζ
&#951; , η
&#952; , θ
&#953; , ι
&#954; , κ
&#955; , λ
&#956; , μ
&#957; , ν
&#958; , ξ
&#959; , ο
&#960; , π
&#961; , ρ
&#962; , ς
&#963; , σ
&#964; , τ
&#965; , υ
&#966; , φ
&#967; , χ
&#968; , ψ
&#969; , ω
&#970; , ϊ
&#971; , ϋ
&#972; , ό
&#973; , ύ
&#974; , ώ
&#975; , Ϗ
&#976; , ϐ
&#977; , ϑ
&#978; , ϒ
&#979; , ϓ
&#980; , ϔ
&#981; , ϕ
&#982; , ϖ
&#983; , ϗ
&#984; , Ϙ
&#985; , ϙ
&#986; , Ϛ
&#987; , ϛ
&#988; , Ϝ
&#989; , ϝ
&#990; , Ϟ
&#991; , ϟ
&#992; , Ϡ
&#993; , ϡ
&#994; , Ϣ
&#995; , ϣ
&#996; , Ϥ
&#997; , ϥ
&#998; , Ϧ
&#999; , ϧ
----------

 

다음은 한글 유니코드 표

 

사용법 : &#번호;

 

 UNICODE HANGUL SYLLABLES

44032: 가각갂갃간갅갆갇갈갉갊갋갌갍갎갏감갑값갓갔강갖갗갘같갚갛 :44059

44060: 개객갞갟갠갡갢갣갤갥갦갧갨갩갪갫갬갭갮갯갰갱갲갳갴갵갶갷 :44087

44088: 갸갹갺갻갼갽갾갿걀걁걂걃걄걅걆걇걈걉걊걋걌걍걎걏걐걑걒걓 :44115

44116: 걔걕걖걗걘걙걚걛걜걝걞걟걠걡걢걣걤걥걦걧걨걩걪걫걬걭걮걯 :44143

44144: 거걱걲걳건걵걶걷걸걹걺걻걼걽걾걿검겁겂것겄겅겆겇겈겉겊겋 :44171

44172: 게겍겎겏겐겑겒겓겔겕겖겗겘겙겚겛겜겝겞겟겠겡겢겣겤겥겦겧 :44199

44200: 겨격겪겫견겭겮겯결겱겲겳겴겵겶겷겸겹겺겻겼경겾겿곀곁곂곃 :44227

44228: 계곅곆곇곈곉곊곋곌곍곎곏곐곑곒곓곔곕곖곗곘곙곚곛곜곝곞곟 :44255

44256: 고곡곢곣곤곥곦곧골곩곪곫곬곭곮곯곰곱곲곳곴공곶곷곸곹곺곻 :44283

44284: 과곽곾곿관괁괂괃괄괅괆괇괈괉괊괋괌괍괎괏괐광괒괓괔괕괖괗 :44311

44312: 괘괙괚괛괜괝괞괟괠괡괢괣괤괥괦괧괨괩괪괫괬괭괮괯괰괱괲괳 :44339

44340: 괴괵괶괷괸괹괺괻괼괽괾괿굀굁굂굃굄굅굆굇굈굉굊굋굌굍굎굏 :44367

44368: 교굑굒굓굔굕굖굗굘굙굚굛굜굝굞굟굠굡굢굣굤굥굦굧굨굩굪굫 :44395

44396: 구국굮굯군굱굲굳굴굵굶굷굸굹굺굻굼굽굾굿궀궁궂궃궄궅궆궇 :44423

44424: 궈궉궊궋권궍궎궏궐궑궒궓궔궕궖궗궘궙궚궛궜궝궞궟궠궡궢궣 :44451

44452: 궤궥궦궧궨궩궪궫궬궭궮궯궰궱궲궳궴궵궶궷궸궹궺궻궼궽궾궿 :44479

44480: 귀귁귂귃귄귅귆귇귈귉귊귋귌귍귎귏귐귑귒귓귔귕귖귗귘귙귚귛 :44507

44508: 규귝귞귟균귡귢귣귤귥귦귧귨귩귪귫귬귭귮귯귰귱귲귳귴귵귶귷 :44535

44536: 그극귺귻근귽귾귿글긁긂긃긄긅긆긇금급긊긋긌긍긎긏긐긑긒긓 :44563

44564: 긔긕긖긗긘긙긚긛긜긝긞긟긠긡긢긣긤긥긦긧긨긩긪긫긬긭긮긯 :44591

44592: 기긱긲긳긴긵긶긷길긹긺긻긼긽긾긿김깁깂깃깄깅깆깇깈깉깊깋 :44619

44620: 까깍깎깏깐깑깒깓깔깕깖깗깘깙깚깛깜깝깞깟깠깡깢깣깤깥깦깧 :44647

44648: 깨깩깪깫깬깭깮깯깰깱깲깳깴깵깶깷깸깹깺깻깼깽깾깿꺀꺁꺂꺃 :44675

44676: 꺄꺅꺆꺇꺈꺉꺊꺋꺌꺍꺎꺏꺐꺑꺒꺓꺔꺕꺖꺗꺘꺙꺚꺛꺜꺝꺞꺟 :44703

44704: 꺠꺡꺢꺣꺤꺥꺦꺧꺨꺩꺪꺫꺬꺭꺮꺯꺰꺱꺲꺳꺴꺵꺶꺷꺸꺹꺺꺻 :44731

44732: 꺼꺽꺾꺿껀껁껂껃껄껅껆껇껈껉껊껋껌껍껎껏껐껑껒껓껔껕껖껗 :44759

44760: 께껙껚껛껜껝껞껟껠껡껢껣껤껥껦껧껨껩껪껫껬껭껮껯껰껱껲껳 :44787

44788: 껴껵껶껷껸껹껺껻껼껽껾껿꼀꼁꼂꼃꼄꼅꼆꼇꼈꼉꼊꼋꼌꼍꼎꼏 :44815

44816: 꼐꼑꼒꼓꼔꼕꼖꼗꼘꼙꼚꼛꼜꼝꼞꼟꼠꼡꼢꼣꼤꼥꼦꼧꼨꼩꼪꼫 :44843

44844: 꼬꼭꼮꼯꼰꼱꼲꼳꼴꼵꼶꼷꼸꼹꼺꼻꼼꼽꼾꼿꽀꽁꽂꽃꽄꽅꽆꽇 :44871

44872: 꽈꽉꽊꽋꽌꽍꽎꽏꽐꽑꽒꽓꽔꽕꽖꽗꽘꽙꽚꽛꽜꽝꽞꽟꽠꽡꽢꽣 :44899

44900: 꽤꽥꽦꽧꽨꽩꽪꽫꽬꽭꽮꽯꽰꽱꽲꽳꽴꽵꽶꽷꽸꽹꽺꽻꽼꽽꽾꽿 :44927

44928: 꾀꾁꾂꾃꾄꾅꾆꾇꾈꾉꾊꾋꾌꾍꾎꾏꾐꾑꾒꾓꾔꾕꾖꾗꾘꾙꾚꾛 :44955

44956: 꾜꾝꾞꾟꾠꾡꾢꾣꾤꾥꾦꾧꾨꾩꾪꾫꾬꾭꾮꾯꾰꾱꾲꾳꾴꾵꾶꾷 :44983

44984: 꾸꾹꾺꾻꾼꾽꾾꾿꿀꿁꿂꿃꿄꿅꿆꿇꿈꿉꿊꿋꿌꿍꿎꿏꿐꿑꿒꿓 :45011

45012: 꿔꿕꿖꿗꿘꿙꿚꿛꿜꿝꿞꿟꿠꿡꿢꿣꿤꿥꿦꿧꿨꿩꿪꿫꿬꿭꿮꿯 :45039

45040: 꿰꿱꿲꿳꿴꿵꿶꿷꿸꿹꿺꿻꿼꿽꿾꿿뀀뀁뀂뀃뀄뀅뀆뀇뀈뀉뀊뀋 :45067

45068: 뀌뀍뀎뀏뀐뀑뀒뀓뀔뀕뀖뀗뀘뀙뀚뀛뀜뀝뀞뀟뀠뀡뀢뀣뀤뀥뀦뀧 :45095

45096: 뀨뀩뀪뀫뀬뀭뀮뀯뀰뀱뀲뀳뀴뀵뀶뀷뀸뀹뀺뀻뀼뀽뀾뀿끀끁끂끃 :45123

45124: 끄끅끆끇끈끉끊끋끌끍끎끏끐끑끒끓끔끕끖끗끘끙끚끛끜끝끞끟 :45151

45152: 끠끡끢끣끤끥끦끧끨끩끪끫끬끭끮끯끰끱끲끳끴끵끶끷끸끹끺끻 :45179

45180: 끼끽끾끿낀낁낂낃낄낅낆낇낈낉낊낋낌낍낎낏낐낑낒낓낔낕낖낗 :45207

45208: 나낙낚낛난낝낞낟날낡낢낣낤낥낦낧남납낪낫났낭낮낯낰낱낲낳 :45235

45236: 내낵낶낷낸낹낺낻낼낽낾낿냀냁냂냃냄냅냆냇냈냉냊냋냌냍냎냏 :45263

45264: 냐냑냒냓냔냕냖냗냘냙냚냛냜냝냞냟냠냡냢냣냤냥냦냧냨냩냪냫 :45291

45292: 냬냭냮냯냰냱냲냳냴냵냶냷냸냹냺냻냼냽냾냿넀넁넂넃넄넅넆넇 :45319

45320: 너넉넊넋넌넍넎넏널넑넒넓넔넕넖넗넘넙넚넛넜넝넞넟넠넡넢넣 :45347

45348: 네넥넦넧넨넩넪넫넬넭넮넯넰넱넲넳넴넵넶넷넸넹넺넻넼넽넾넿 :45375

45376: 녀녁녂녃년녅녆녇녈녉녊녋녌녍녎녏념녑녒녓녔녕녖녗녘녙녚녛 :45403

45404: 녜녝녞녟녠녡녢녣녤녥녦녧녨녩녪녫녬녭녮녯녰녱녲녳녴녵녶녷 :45431

45432: 노녹녺녻논녽녾녿놀놁놂놃놄놅놆놇놈놉놊놋놌농놎놏놐놑높놓 :45459

45460: 놔놕놖놗놘놙놚놛놜놝놞놟놠놡놢놣놤놥놦놧놨놩놪놫놬놭놮놯 :45487

45488: 놰놱놲놳놴놵놶놷놸놹놺놻놼놽놾놿뇀뇁뇂뇃뇄뇅뇆뇇뇈뇉뇊뇋 :45515

45516: 뇌뇍뇎뇏뇐뇑뇒뇓뇔뇕뇖뇗뇘뇙뇚뇛뇜뇝뇞뇟뇠뇡뇢뇣뇤뇥뇦뇧 :45543

45544: 뇨뇩뇪뇫뇬뇭뇮뇯뇰뇱뇲뇳뇴뇵뇶뇷뇸뇹뇺뇻뇼뇽뇾뇿눀눁눂눃 :45571

45572: 누눅눆눇눈눉눊눋눌눍눎눏눐눑눒눓눔눕눖눗눘눙눚눛눜눝눞눟 :45599

45600: 눠눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻 :45627

45628: 눼눽눾눿뉀뉁뉂뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌뉍뉎뉏뉐뉑뉒뉓뉔뉕뉖뉗 :45655

45656: 뉘뉙뉚뉛뉜뉝뉞뉟뉠뉡뉢뉣뉤뉥뉦뉧뉨뉩뉪뉫뉬뉭뉮뉯뉰뉱뉲뉳 :45683

45684: 뉴뉵뉶뉷뉸뉹뉺뉻뉼뉽뉾뉿늀늁늂늃늄늅늆늇늈늉늊늋늌늍늎늏 :45711

45712: 느늑늒늓는늕늖늗늘늙늚늛늜늝늞늟늠늡늢늣늤능늦늧늨늩늪늫 :45739

45740: 늬늭늮늯늰늱늲늳늴늵늶늷늸늹늺늻늼늽늾늿닀닁닂닃닄닅닆닇 :45767

45768: 니닉닊닋닌닍닎닏닐닑닒닓닔닕닖닗님닙닚닛닜닝닞닟닠닡닢닣 :45795

45796: 다닥닦닧단닩닪닫달닭닮닯닰닱닲닳담답닶닷닸당닺닻닼닽닾닿 :45823

45824: 대댁댂댃댄댅댆댇댈댉댊댋댌댍댎댏댐댑댒댓댔댕댖댗댘댙댚댛 :45851

45852: 댜댝댞댟댠댡댢댣댤댥댦댧댨댩댪댫댬댭댮댯댰댱댲댳댴댵댶댷 :45879

45880: 댸댹댺댻댼댽댾댿덀덁덂덃덄덅덆덇덈덉덊덋덌덍덎덏덐덑덒덓 :45907

45908: 더덕덖덗던덙덚덛덜덝덞덟덠덡덢덣덤덥덦덧덨덩덪덫덬덭덮덯 :45935

45936: 데덱덲덳덴덵덶덷델덹덺덻덼덽덾덿뎀뎁뎂뎃뎄뎅뎆뎇뎈뎉뎊뎋 :45963

45964: 뎌뎍뎎뎏뎐뎑뎒뎓뎔뎕뎖뎗뎘뎙뎚뎛뎜뎝뎞뎟뎠뎡뎢뎣뎤뎥뎦뎧 :45991

45992: 뎨뎩뎪뎫뎬뎭뎮뎯뎰뎱뎲뎳뎴뎵뎶뎷뎸뎹뎺뎻뎼뎽뎾뎿돀돁돂돃 :46019

46020: 도독돆돇돈돉돊돋돌돍돎돏돐돑돒돓돔돕돖돗돘동돚돛돜돝돞돟 :46047

46048: 돠돡돢돣돤돥돦돧돨돩돪돫돬돭돮돯돰돱돲돳돴돵돶돷돸돹돺돻 :46075

46076: 돼돽돾돿됀됁됂됃됄됅됆됇됈됉됊됋됌됍됎됏됐됑됒됓됔됕됖됗 :46103

46104: 되됙됚됛된됝됞됟될됡됢됣됤됥됦됧됨됩됪됫됬됭됮됯됰됱됲됳 :46131

46132: 됴됵됶됷됸됹됺됻됼됽됾됿둀둁둂둃둄둅둆둇둈둉둊둋둌둍둎둏 :46159

46160: 두둑둒둓둔둕둖둗둘둙둚둛둜둝둞둟둠둡둢둣둤둥둦둧둨둩둪둫 :46187

46188: 둬둭둮둯둰둱둲둳둴둵둶둷둸둹둺둻둼둽둾둿뒀뒁뒂뒃뒄뒅뒆뒇 :46215

46216: 뒈뒉뒊뒋뒌뒍뒎뒏뒐뒑뒒뒓뒔뒕뒖뒗뒘뒙뒚뒛뒜뒝뒞뒟뒠뒡뒢뒣 :46243

46244: 뒤뒥뒦뒧뒨뒩뒪뒫뒬뒭뒮뒯뒰뒱뒲뒳뒴뒵뒶뒷뒸뒹뒺뒻뒼뒽뒾뒿 :46271

46272: 듀듁듂듃듄듅듆듇듈듉듊듋듌듍듎듏듐듑듒듓듔듕듖듗듘듙듚듛 :46299

46300: 드득듞듟든듡듢듣들듥듦듧듨듩듪듫듬듭듮듯듰등듲듳듴듵듶듷 :46327

46328: 듸듹듺듻듼듽듾듿딀딁딂딃딄딅딆딇딈딉딊딋딌딍딎딏딐딑딒딓 :46355

46356: 디딕딖딗딘딙딚딛딜딝딞딟딠딡딢딣딤딥딦딧딨딩딪딫딬딭딮딯 :46383

46384: 따딱딲딳딴딵딶딷딸딹딺딻딼딽딾딿땀땁땂땃땄땅땆땇땈땉땊땋 :46411

46412: 때땍땎땏땐땑땒땓땔땕땖땗땘땙땚땛땜땝땞땟땠땡땢땣땤땥땦땧 :46439

46440: 땨땩땪땫땬땭땮땯땰땱땲땳땴땵땶땷땸땹땺땻땼땽땾땿떀떁떂떃 :46467

46468: 떄떅떆떇떈떉떊떋떌떍떎떏떐떑떒떓떔떕떖떗떘떙떚떛떜떝떞떟 :46495

46496: 떠떡떢떣떤떥떦떧떨떩떪떫떬떭떮떯떰떱떲떳떴떵떶떷떸떹떺떻 :46523

46524: 떼떽떾떿뗀뗁뗂뗃뗄뗅뗆뗇뗈뗉뗊뗋뗌뗍뗎뗏뗐뗑뗒뗓뗔뗕뗖뗗 :46551

46552: 뗘뗙뗚뗛뗜뗝뗞뗟뗠뗡뗢뗣뗤뗥뗦뗧뗨뗩뗪뗫뗬뗭뗮뗯뗰뗱뗲뗳 :46579

46580: 뗴뗵뗶뗷뗸뗹뗺뗻뗼뗽뗾뗿똀똁똂똃똄똅똆똇똈똉똊똋똌똍똎똏 :46607

46608: 또똑똒똓똔똕똖똗똘똙똚똛똜똝똞똟똠똡똢똣똤똥똦똧똨똩똪똫 :46635

46636: 똬똭똮똯똰똱똲똳똴똵똶똷똸똹똺똻똼똽똾똿뙀뙁뙂뙃뙄뙅뙆뙇 :46663

46664: 뙈뙉뙊뙋뙌뙍뙎뙏뙐뙑뙒뙓뙔뙕뙖뙗뙘뙙뙚뙛뙜뙝뙞뙟뙠뙡뙢뙣 :46691

46692: 뙤뙥뙦뙧뙨뙩뙪뙫뙬뙭뙮뙯뙰뙱뙲뙳뙴뙵뙶뙷뙸뙹뙺뙻뙼뙽뙾뙿 :46719

46720: 뚀뚁뚂뚃뚄뚅뚆뚇뚈뚉뚊뚋뚌뚍뚎뚏뚐뚑뚒뚓뚔뚕뚖뚗뚘뚙뚚뚛 :46747

46748: 뚜뚝뚞뚟뚠뚡뚢뚣뚤뚥뚦뚧뚨뚩뚪뚫뚬뚭뚮뚯뚰뚱뚲뚳뚴뚵뚶뚷 :46775

46776: 뚸뚹뚺뚻뚼뚽뚾뚿뛀뛁뛂뛃뛄뛅뛆뛇뛈뛉뛊뛋뛌뛍뛎뛏뛐뛑뛒뛓 :46803

46804: 뛔뛕뛖뛗뛘뛙뛚뛛뛜뛝뛞뛟뛠뛡뛢뛣뛤뛥뛦뛧뛨뛩뛪뛫뛬뛭뛮뛯 :46831

46832: 뛰뛱뛲뛳뛴뛵뛶뛷뛸뛹뛺뛻뛼뛽뛾뛿뜀뜁뜂뜃뜄뜅뜆뜇뜈뜉뜊뜋 :46859

46860: 뜌뜍뜎뜏뜐뜑뜒뜓뜔뜕뜖뜗뜘뜙뜚뜛뜜뜝뜞뜟뜠뜡뜢뜣뜤뜥뜦뜧 :46887

46888: 뜨뜩뜪뜫뜬뜭뜮뜯뜰뜱뜲뜳뜴뜵뜶뜷뜸뜹뜺뜻뜼뜽뜾뜿띀띁띂띃 :46915

46916: 띄띅띆띇띈띉띊띋띌띍띎띏띐띑띒띓띔띕띖띗띘띙띚띛띜띝띞띟 :46943

46944: 띠띡띢띣띤띥띦띧띨띩띪띫띬띭띮띯띰띱띲띳띴띵띶띷띸띹띺띻 :46971

46972: 라락띾띿란랁랂랃랄랅랆랇랈랉랊랋람랍랎랏랐랑랒랓랔랕랖랗 :46999

47000: 래랙랚랛랜랝랞랟랠랡랢랣랤랥랦랧램랩랪랫랬랭랮랯랰랱랲랳 :47027

47028: 랴략랶랷랸랹랺랻랼랽랾랿럀럁럂럃럄럅럆럇럈량럊럋럌럍럎럏 :47055

47056: 럐럑럒럓럔럕럖럗럘럙럚럛럜럝럞럟럠럡럢럣럤럥럦럧럨럩럪럫 :47083

47084: 러럭럮럯런럱럲럳럴럵럶럷럸럹럺럻럼럽럾럿렀렁렂렃렄렅렆렇 :47111

47112: 레렉렊렋렌렍렎렏렐렑렒렓렔렕렖렗렘렙렚렛렜렝렞렟렠렡렢렣 :47139

47140: 려력렦렧련렩렪렫렬렭렮렯렰렱렲렳렴렵렶렷렸령렺렻렼렽렾렿 :47167

47168: 례롁롂롃롄롅롆롇롈롉롊롋롌롍롎롏롐롑롒롓롔롕롖롗롘롙롚롛 :47195

47196: 로록롞롟론롡롢롣롤롥롦롧롨롩롪롫롬롭롮롯롰롱롲롳롴롵롶롷 :47223

47224: 롸롹롺롻롼롽롾롿뢀뢁뢂뢃뢄뢅뢆뢇뢈뢉뢊뢋뢌뢍뢎뢏뢐뢑뢒뢓 :47251

47252: 뢔뢕뢖뢗뢘뢙뢚뢛뢜뢝뢞뢟뢠뢡뢢뢣뢤뢥뢦뢧뢨뢩뢪뢫뢬뢭뢮뢯 :47279

47280: 뢰뢱뢲뢳뢴뢵뢶뢷뢸뢹뢺뢻뢼뢽뢾뢿룀룁룂룃룄룅룆룇룈룉룊룋 :47307

47308: 료룍룎룏룐룑룒룓룔룕룖룗룘룙룚룛룜룝룞룟룠룡룢룣룤룥룦룧 :47335

47336: 루룩룪룫룬룭룮룯룰룱룲룳룴룵룶룷룸룹룺룻룼룽룾룿뤀뤁뤂뤃 :47363

47364: 뤄뤅뤆뤇뤈뤉뤊뤋뤌뤍뤎뤏뤐뤑뤒뤓뤔뤕뤖뤗뤘뤙뤚뤛뤜뤝뤞뤟 :47391

47392: 뤠뤡뤢뤣뤤뤥뤦뤧뤨뤩뤪뤫뤬뤭뤮뤯뤰뤱뤲뤳뤴뤵뤶뤷뤸뤹뤺뤻 :47419

47420: 뤼뤽뤾뤿륀륁륂륃륄륅륆륇륈륉륊륋륌륍륎륏륐륑륒륓륔륕륖륗 :47447

47448: 류륙륚륛륜륝륞륟률륡륢륣륤륥륦륧륨륩륪륫륬륭륮륯륰륱륲륳 :47475

47476: 르륵륶륷른륹륺륻를륽륾륿릀릁릂릃름릅릆릇릈릉릊릋릌릍릎릏 :47503

47504: 릐릑릒릓릔릕릖릗릘릙릚릛릜릝릞릟릠릡릢릣릤릥릦릧릨릩릪릫 :47531

47532: 리릭릮릯린릱릲릳릴릵릶릷릸릹릺릻림립릾릿맀링맂맃맄맅맆맇 :47559

47560: 마막맊맋만맍많맏말맑맒맓맔맕맖맗맘맙맚맛맜망맞맟맠맡맢맣 :47587

47588: 매맥맦맧맨맩맪맫맬맭맮맯맰맱맲맳맴맵맶맷맸맹맺맻맼맽맾맿 :47615

47616: 먀먁먂먃먄먅먆먇먈먉먊먋먌먍먎먏먐먑먒먓먔먕먖먗먘먙먚먛 :47643

47644: 먜먝먞먟먠먡먢먣먤먥먦먧먨먩먪먫먬먭먮먯먰먱먲먳먴먵먶먷 :47671

47672: 머먹먺먻먼먽먾먿멀멁멂멃멄멅멆멇멈멉멊멋멌멍멎멏멐멑멒멓 :47699

47700: 메멕멖멗멘멙멚멛멜멝멞멟멠멡멢멣멤멥멦멧멨멩멪멫멬멭멮멯 :47727

47728: 며멱멲멳면멵멶멷멸멹멺멻멼멽멾멿몀몁몂몃몄명몆몇몈몉몊몋 :47755

47756: 몌몍몎몏몐몑몒몓몔몕몖몗몘몙몚몛몜몝몞몟몠몡몢몣몤몥몦몧 :47783

47784: 모목몪몫몬몭몮몯몰몱몲몳몴몵몶몷몸몹몺못몼몽몾몿뫀뫁뫂뫃 :47811

47812: 뫄뫅뫆뫇뫈뫉뫊뫋뫌뫍뫎뫏뫐뫑뫒뫓뫔뫕뫖뫗뫘뫙뫚뫛뫜뫝뫞뫟 :47839

47840: 뫠뫡뫢뫣뫤뫥뫦뫧뫨뫩뫪뫫뫬뫭뫮뫯뫰뫱뫲뫳뫴뫵뫶뫷뫸뫹뫺뫻 :47867

47868: 뫼뫽뫾뫿묀묁묂묃묄묅묆묇묈묉묊묋묌묍묎묏묐묑묒묓묔묕묖묗 :47895

47896: 묘묙묚묛묜묝묞묟묠묡묢묣묤묥묦묧묨묩묪묫묬묭묮묯묰묱묲묳 :47923

47924: 무묵묶묷문묹묺묻물묽묾묿뭀뭁뭂뭃뭄뭅뭆뭇뭈뭉뭊뭋뭌뭍뭎뭏 :47951

47952: 뭐뭑뭒뭓뭔뭕뭖뭗뭘뭙뭚뭛뭜뭝뭞뭟뭠뭡뭢뭣뭤뭥뭦뭧뭨뭩뭪뭫 :47979

47980: 뭬뭭뭮뭯뭰뭱뭲뭳뭴뭵뭶뭷뭸뭹뭺뭻뭼뭽뭾뭿뮀뮁뮂뮃뮄뮅뮆뮇 :48007

48008: 뮈뮉뮊뮋뮌뮍뮎뮏뮐뮑뮒뮓뮔뮕뮖뮗뮘뮙뮚뮛뮜뮝뮞뮟뮠뮡뮢뮣 :48035

48036: 뮤뮥뮦뮧뮨뮩뮪뮫뮬뮭뮮뮯뮰뮱뮲뮳뮴뮵뮶뮷뮸뮹뮺뮻뮼뮽뮾뮿 :48063

48064: 므믁믂믃믄믅믆믇믈믉믊믋믌믍믎믏믐믑믒믓믔믕믖믗믘믙믚믛 :48091

48092: 믜믝믞믟믠믡믢믣믤믥믦믧믨믩믪믫믬믭믮믯믰믱믲믳믴믵믶믷 :48119

48120: 미믹믺믻민믽믾믿밀밁밂밃밄밅밆밇밈밉밊밋밌밍밎및밐밑밒밓 :48147

48148: 바박밖밗반밙밚받발밝밞밟밠밡밢밣밤밥밦밧밨방밪밫밬밭밮밯 :48175

48176: 배백밲밳밴밵밶밷밸밹밺밻밼밽밾밿뱀뱁뱂뱃뱄뱅뱆뱇뱈뱉뱊뱋 :48203

48204: 뱌뱍뱎뱏뱐뱑뱒뱓뱔뱕뱖뱗뱘뱙뱚뱛뱜뱝뱞뱟뱠뱡뱢뱣뱤뱥뱦뱧 :48231

48232: 뱨뱩뱪뱫뱬뱭뱮뱯뱰뱱뱲뱳뱴뱵뱶뱷뱸뱹뱺뱻뱼뱽뱾뱿벀벁벂벃 :48259

48260: 버벅벆벇번벉벊벋벌벍벎벏벐벑벒벓범법벖벗벘벙벚벛벜벝벞벟 :48287

48288: 베벡벢벣벤벥벦벧벨벩벪벫벬벭벮벯벰벱벲벳벴벵벶벷벸벹벺벻 :48315

48316: 벼벽벾벿변볁볂볃별볅볆볇볈볉볊볋볌볍볎볏볐병볒볓볔볕볖볗 :48343

48344: 볘볙볚볛볜볝볞볟볠볡볢볣볤볥볦볧볨볩볪볫볬볭볮볯볰볱볲볳 :48371

48372: 보복볶볷본볹볺볻볼볽볾볿봀봁봂봃봄봅봆봇봈봉봊봋봌봍봎봏 :48399

48400: 봐봑봒봓봔봕봖봗봘봙봚봛봜봝봞봟봠봡봢봣봤봥봦봧봨봩봪봫 :48427

48428: 봬봭봮봯봰봱봲봳봴봵봶봷봸봹봺봻봼봽봾봿뵀뵁뵂뵃뵄뵅뵆뵇 :48455

48456: 뵈뵉뵊뵋뵌뵍뵎뵏뵐뵑뵒뵓뵔뵕뵖뵗뵘뵙뵚뵛뵜뵝뵞뵟뵠뵡뵢뵣 :48483

48484: 뵤뵥뵦뵧뵨뵩뵪뵫뵬뵭뵮뵯뵰뵱뵲뵳뵴뵵뵶뵷뵸뵹뵺뵻뵼뵽뵾뵿 :48511

48512: 부북붂붃분붅붆붇불붉붊붋붌붍붎붏붐붑붒붓붔붕붖붗붘붙붚붛 :48539

48540: 붜붝붞붟붠붡붢붣붤붥붦붧붨붩붪붫붬붭붮붯붰붱붲붳붴붵붶붷 :48567

48568: 붸붹붺붻붼붽붾붿뷀뷁뷂뷃뷄뷅뷆뷇뷈뷉뷊뷋뷌뷍뷎뷏뷐뷑뷒뷓 :48595

48596: 뷔뷕뷖뷗뷘뷙뷚뷛뷜뷝뷞뷟뷠뷡뷢뷣뷤뷥뷦뷧뷨뷩뷪뷫뷬뷭뷮뷯 :48623

48624: 뷰뷱뷲뷳뷴뷵뷶뷷뷸뷹뷺뷻뷼뷽뷾뷿븀븁븂븃븄븅븆븇븈븉븊븋 :48651

48652: 브븍븎븏븐븑븒븓블븕븖븗븘븙븚븛븜븝븞븟븠븡븢븣븤븥븦븧 :48679

48680: 븨븩븪븫븬븭븮븯븰븱븲븳븴븵븶븷븸븹븺븻븼븽븾븿빀빁빂빃 :48707

48708: 비빅빆빇빈빉빊빋빌빍빎빏빐빑빒빓빔빕빖빗빘빙빚빛빜빝빞빟 :48735

48736: 빠빡빢빣빤빥빦빧빨빩빪빫빬빭빮빯빰빱빲빳빴빵빶빷빸빹빺빻 :48763

48764: 빼빽빾빿뺀뺁뺂뺃뺄뺅뺆뺇뺈뺉뺊뺋뺌뺍뺎뺏뺐뺑뺒뺓뺔뺕뺖뺗 :48791

48792: 뺘뺙뺚뺛뺜뺝뺞뺟뺠뺡뺢뺣뺤뺥뺦뺧뺨뺩뺪뺫뺬뺭뺮뺯뺰뺱뺲뺳 :48819

48820: 뺴뺵뺶뺷뺸뺹뺺뺻뺼뺽뺾뺿뻀뻁뻂뻃뻄뻅뻆뻇뻈뻉뻊뻋뻌뻍뻎뻏 :48847

48848: 뻐뻑뻒뻓뻔뻕뻖뻗뻘뻙뻚뻛뻜뻝뻞뻟뻠뻡뻢뻣뻤뻥뻦뻧뻨뻩뻪뻫 :48875

48876: 뻬뻭뻮뻯뻰뻱뻲뻳뻴뻵뻶뻷뻸뻹뻺뻻뻼뻽뻾뻿뼀뼁뼂뼃뼄뼅뼆뼇 :48903

48904: 뼈뼉뼊뼋뼌뼍뼎뼏뼐뼑뼒뼓뼔뼕뼖뼗뼘뼙뼚뼛뼜뼝뼞뼟뼠뼡뼢뼣 :48931

48932: 뼤뼥뼦뼧뼨뼩뼪뼫뼬뼭뼮뼯뼰뼱뼲뼳뼴뼵뼶뼷뼸뼹뼺뼻뼼뼽뼾뼿 :48959

48960: 뽀뽁뽂뽃뽄뽅뽆뽇뽈뽉뽊뽋뽌뽍뽎뽏뽐뽑뽒뽓뽔뽕뽖뽗뽘뽙뽚뽛 :48987

48988: 뽜뽝뽞뽟뽠뽡뽢뽣뽤뽥뽦뽧뽨뽩뽪뽫뽬뽭뽮뽯뽰뽱뽲뽳뽴뽵뽶뽷 :49015

49016: 뽸뽹뽺뽻뽼뽽뽾뽿뾀뾁뾂뾃뾄뾅뾆뾇뾈뾉뾊뾋뾌뾍뾎뾏뾐뾑뾒뾓 :49043

49044: 뾔뾕뾖뾗뾘뾙뾚뾛뾜뾝뾞뾟뾠뾡뾢뾣뾤뾥뾦뾧뾨뾩뾪뾫뾬뾭뾮뾯 :49071

49072: 뾰뾱뾲뾳뾴뾵뾶뾷뾸뾹뾺뾻뾼뾽뾾뾿뿀뿁뿂뿃뿄뿅뿆뿇뿈뿉뿊뿋 :49099

49100: 뿌뿍뿎뿏뿐뿑뿒뿓뿔뿕뿖뿗뿘뿙뿚뿛뿜뿝뿞뿟뿠뿡뿢뿣뿤뿥뿦뿧 :49127

49128: 뿨뿩뿪뿫뿬뿭뿮뿯뿰뿱뿲뿳뿴뿵뿶뿷뿸뿹뿺뿻뿼뿽뿾뿿쀀쀁쀂쀃 :49155

49156: 쀄쀅쀆쀇쀈쀉쀊쀋쀌쀍쀎쀏쀐쀑쀒쀓쀔쀕쀖쀗쀘쀙쀚쀛쀜쀝쀞쀟 :49183

49184: 쀠쀡쀢쀣쀤쀥쀦쀧쀨쀩쀪쀫쀬쀭쀮쀯쀰쀱쀲쀳쀴쀵쀶쀷쀸쀹쀺쀻 :49211

49212: 쀼쀽쀾쀿쁀쁁쁂쁃쁄쁅쁆쁇쁈쁉쁊쁋쁌쁍쁎쁏쁐쁑쁒쁓쁔쁕쁖쁗 :49239

49240: 쁘쁙쁚쁛쁜쁝쁞쁟쁠쁡쁢쁣쁤쁥쁦쁧쁨쁩쁪쁫쁬쁭쁮쁯쁰쁱쁲쁳 :49267

49268: 쁴쁵쁶쁷쁸쁹쁺쁻쁼쁽쁾쁿삀삁삂삃삄삅삆삇삈삉삊삋삌삍삎삏 :49295

49296: 삐삑삒삓삔삕삖삗삘삙삚삛삜삝삞삟삠삡삢삣삤삥삦삧삨삩삪삫 :49323

49324: 사삭삮삯산삱삲삳살삵삶삷삸삹삺삻삼삽삾삿샀상샂샃샄샅샆샇 :49351

49352: 새색샊샋샌샍샎샏샐샑샒샓샔샕샖샗샘샙샚샛샜생샞샟샠샡샢샣 :49379

49380: 샤샥샦샧샨샩샪샫샬샭샮샯샰샱샲샳샴샵샶샷샸샹샺샻샼샽샾샿 :49407

49408: 섀섁섂섃섄섅섆섇섈섉섊섋섌섍섎섏섐섑섒섓섔섕섖섗섘섙섚섛 :49435

49436: 서석섞섟선섡섢섣설섥섦섧섨섩섪섫섬섭섮섯섰성섲섳섴섵섶섷 :49463

49464: 세섹섺섻센섽섾섿셀셁셂셃셄셅셆셇셈셉셊셋셌셍셎셏셐셑셒셓 :49491

49492: 셔셕셖셗션셙셚셛셜셝셞셟셠셡셢셣셤셥셦셧셨셩셪셫셬셭셮셯 :49519

49520: 셰셱셲셳셴셵셶셷셸셹셺셻셼셽셾셿솀솁솂솃솄솅솆솇솈솉솊솋 :49547

49548: 소속솎솏손솑솒솓솔솕솖솗솘솙솚솛솜솝솞솟솠송솢솣솤솥솦솧 :49575

49576: 솨솩솪솫솬솭솮솯솰솱솲솳솴솵솶솷솸솹솺솻솼솽솾솿쇀쇁쇂쇃 :49603

49604: 쇄쇅쇆쇇쇈쇉쇊쇋쇌쇍쇎쇏쇐쇑쇒쇓쇔쇕쇖쇗쇘쇙쇚쇛쇜쇝쇞쇟 :49631

49632: 쇠쇡쇢쇣쇤쇥쇦쇧쇨쇩쇪쇫쇬쇭쇮쇯쇰쇱쇲쇳쇴쇵쇶쇷쇸쇹쇺쇻 :49659

49660: 쇼쇽쇾쇿숀숁숂숃숄숅숆숇숈숉숊숋숌숍숎숏숐숑숒숓숔숕숖숗 :49687

49688: 수숙숚숛순숝숞숟술숡숢숣숤숥숦숧숨숩숪숫숬숭숮숯숰숱숲숳 :49715

49716: 숴숵숶숷숸숹숺숻숼숽숾숿쉀쉁쉂쉃쉄쉅쉆쉇쉈쉉쉊쉋쉌쉍쉎쉏 :49743

49744: 쉐쉑쉒쉓쉔쉕쉖쉗쉘쉙쉚쉛쉜쉝쉞쉟쉠쉡쉢쉣쉤쉥쉦쉧쉨쉩쉪쉫 :49771

49772: 쉬쉭쉮쉯쉰쉱쉲쉳쉴쉵쉶쉷쉸쉹쉺쉻쉼쉽쉾쉿슀슁슂슃슄슅슆슇 :49799

49800: 슈슉슊슋슌슍슎슏슐슑슒슓슔슕슖슗슘슙슚슛슜슝슞슟슠슡슢슣 :49827

49828: 스슥슦슧슨슩슪슫슬슭슮슯슰슱슲슳슴습슶슷슸승슺슻슼슽슾슿 :49855

49856: 싀싁싂싃싄싅싆싇싈싉싊싋싌싍싎싏싐싑싒싓싔싕싖싗싘싙싚싛 :49883

49884: 시식싞싟신싡싢싣실싥싦싧싨싩싪싫심십싮싯싰싱싲싳싴싵싶싷 :49911

49912: 싸싹싺싻싼싽싾싿쌀쌁쌂쌃쌄쌅쌆쌇쌈쌉쌊쌋쌌쌍쌎쌏쌐쌑쌒쌓 :49939

49940: 쌔쌕쌖쌗쌘쌙쌚쌛쌜쌝쌞쌟쌠쌡쌢쌣쌤쌥쌦쌧쌨쌩쌪쌫쌬쌭쌮쌯 :49967

49968: 쌰쌱쌲쌳쌴쌵쌶쌷쌸쌹쌺쌻쌼쌽쌾쌿썀썁썂썃썄썅썆썇썈썉썊썋 :49995

49996: 썌썍썎썏썐썑썒썓썔썕썖썗썘썙썚썛썜썝썞썟썠썡썢썣썤썥썦썧 :50023

50024: 써썩썪썫썬썭썮썯썰썱썲썳썴썵썶썷썸썹썺썻썼썽썾썿쎀쎁쎂쎃 :50051

50052: 쎄쎅쎆쎇쎈쎉쎊쎋쎌쎍쎎쎏쎐쎑쎒쎓쎔쎕쎖쎗쎘쎙쎚쎛쎜쎝쎞쎟 :50079

50080: 쎠쎡쎢쎣쎤쎥쎦쎧쎨쎩쎪쎫쎬쎭쎮쎯쎰쎱쎲쎳쎴쎵쎶쎷쎸쎹쎺쎻 :50107

50108: 쎼쎽쎾쎿쏀쏁쏂쏃쏄쏅쏆쏇쏈쏉쏊쏋쏌쏍쏎쏏쏐쏑쏒쏓쏔쏕쏖쏗 :50135

50136: 쏘쏙쏚쏛쏜쏝쏞쏟쏠쏡쏢쏣쏤쏥쏦쏧쏨쏩쏪쏫쏬쏭쏮쏯쏰쏱쏲쏳 :50163

50164: 쏴쏵쏶쏷쏸쏹쏺쏻쏼쏽쏾쏿쐀쐁쐂쐃쐄쐅쐆쐇쐈쐉쐊쐋쐌쐍쐎쐏 :50191

50192: 쐐쐑쐒쐓쐔쐕쐖쐗쐘쐙쐚쐛쐜쐝쐞쐟쐠쐡쐢쐣쐤쐥쐦쐧쐨쐩쐪쐫 :50219

50220: 쐬쐭쐮쐯쐰쐱쐲쐳쐴쐵쐶쐷쐸쐹쐺쐻쐼쐽쐾쐿쑀쑁쑂쑃쑄쑅쑆쑇 :50247

50248: 쑈쑉쑊쑋쑌쑍쑎쑏쑐쑑쑒쑓쑔쑕쑖쑗쑘쑙쑚쑛쑜쑝쑞쑟쑠쑡쑢쑣 :50275

50276: 쑤쑥쑦쑧쑨쑩쑪쑫쑬쑭쑮쑯쑰쑱쑲쑳쑴쑵쑶쑷쑸쑹쑺쑻쑼쑽쑾쑿 :50303

50304: 쒀쒁쒂쒃쒄쒅쒆쒇쒈쒉쒊쒋쒌쒍쒎쒏쒐쒑쒒쒓쒔쒕쒖쒗쒘쒙쒚쒛 :50331

50332: 쒜쒝쒞쒟쒠쒡쒢쒣쒤쒥쒦쒧쒨쒩쒪쒫쒬쒭쒮쒯쒰쒱쒲쒳쒴쒵쒶쒷 :50359

50360: 쒸쒹쒺쒻쒼쒽쒾쒿쓀쓁쓂쓃쓄쓅쓆쓇쓈쓉쓊쓋쓌쓍쓎쓏쓐쓑쓒쓓 :50387

50388: 쓔쓕쓖쓗쓘쓙쓚쓛쓜쓝쓞쓟쓠쓡쓢쓣쓤쓥쓦쓧쓨쓩쓪쓫쓬쓭쓮쓯 :50415

50416: 쓰쓱쓲쓳쓴쓵쓶쓷쓸쓹쓺쓻쓼쓽쓾쓿씀씁씂씃씄씅씆씇씈씉씊씋 :50443

50444: 씌씍씎씏씐씑씒씓씔씕씖씗씘씙씚씛씜씝씞씟씠씡씢씣씤씥씦씧 :50471

50472: 씨씩씪씫씬씭씮씯씰씱씲씳씴씵씶씷씸씹씺씻씼씽씾씿앀앁앂앃 :50499

50500: 아악앆앇안앉않앋알앍앎앏앐앑앒앓암압앖앗았앙앚앛앜앝앞앟 :50527

50528: 애액앢앣앤앥앦앧앨앩앪앫앬앭앮앯앰앱앲앳앴앵앶앷앸앹앺앻 :50555

50556: 야약앾앿얀얁얂얃얄얅얆얇얈얉얊얋얌얍얎얏얐양얒얓얔얕얖얗 :50583

50584: 얘얙얚얛얜얝얞얟얠얡얢얣얤얥얦얧얨얩얪얫얬얭얮얯얰얱얲얳 :50611

50612: 어억얶얷언얹얺얻얼얽얾얿엀엁엂엃엄업없엇었엉엊엋엌엍엎엏 :50639

50640: 에엑엒엓엔엕엖엗엘엙엚엛엜엝엞엟엠엡엢엣엤엥엦엧엨엩엪엫 :50667

50668: 여역엮엯연엱엲엳열엵엶엷엸엹엺엻염엽엾엿였영옂옃옄옅옆옇 :50695

50696: 예옉옊옋옌옍옎옏옐옑옒옓옔옕옖옗옘옙옚옛옜옝옞옟옠옡옢옣 :50723

50724: 오옥옦옧온옩옪옫올옭옮옯옰옱옲옳옴옵옶옷옸옹옺옻옼옽옾옿 :50751

50752: 와왁왂왃완왅왆왇왈왉왊왋왌왍왎왏왐왑왒왓왔왕왖왗왘왙왚왛 :50779

50780: 왜왝왞왟왠왡왢왣왤왥왦왧왨왩왪왫왬왭왮왯왰왱왲왳왴왵왶왷 :50807

50808: 외왹왺왻왼왽왾왿욀욁욂욃욄욅욆욇욈욉욊욋욌욍욎욏욐욑욒욓 :50835

50836: 요욕욖욗욘욙욚욛욜욝욞욟욠욡욢욣욤욥욦욧욨용욪욫욬욭욮욯 :50863

50864: 우욱욲욳운욵욶욷울욹욺욻욼욽욾욿움웁웂웃웄웅웆웇웈웉웊웋 :50891

50892: 워웍웎웏원웑웒웓월웕웖웗웘웙웚웛웜웝웞웟웠웡웢웣웤웥웦웧 :50919

50920: 웨웩웪웫웬웭웮웯웰웱웲웳웴웵웶웷웸웹웺웻웼웽웾웿윀윁윂윃 :50947

50948: 위윅윆윇윈윉윊윋윌윍윎윏윐윑윒윓윔윕윖윗윘윙윚윛윜윝윞윟 :50975

50976: 유육윢윣윤윥윦윧율윩윪윫윬윭윮윯윰윱윲윳윴융윶윷윸윹윺윻 :51003

51004: 으윽윾윿은읁읂읃을읅읆읇읈읉읊읋음읍읎읏읐응읒읓읔읕읖읗 :51031

51032: 의읙읚읛읜읝읞읟읠읡읢읣읤읥읦읧읨읩읪읫읬읭읮읯읰읱읲읳 :51059

51060: 이익읶읷인읹읺읻일읽읾읿잀잁잂잃임입잆잇있잉잊잋잌잍잎잏 :51087

51088: 자작잒잓잔잕잖잗잘잙잚잛잜잝잞잟잠잡잢잣잤장잦잧잨잩잪잫 :51115

51116: 재잭잮잯잰잱잲잳잴잵잶잷잸잹잺잻잼잽잾잿쟀쟁쟂쟃쟄쟅쟆쟇 :51143

51144: 쟈쟉쟊쟋쟌쟍쟎쟏쟐쟑쟒쟓쟔쟕쟖쟗쟘쟙쟚쟛쟜쟝쟞쟟쟠쟡쟢쟣 :51171

51172: 쟤쟥쟦쟧쟨쟩쟪쟫쟬쟭쟮쟯쟰쟱쟲쟳쟴쟵쟶쟷쟸쟹쟺쟻쟼쟽쟾쟿 :51199

51200: 저적젂젃전젅젆젇절젉젊젋젌젍젎젏점접젒젓젔정젖젗젘젙젚젛 :51227

51228: 제젝젞젟젠젡젢젣젤젥젦젧젨젩젪젫젬젭젮젯젰젱젲젳젴젵젶젷 :51255

51256: 져젹젺젻젼젽젾젿졀졁졂졃졄졅졆졇졈졉졊졋졌졍졎졏졐졑졒졓 :51283

51284: 졔졕졖졗졘졙졚졛졜졝졞졟졠졡졢졣졤졥졦졧졨졩졪졫졬졭졮졯 :51311

51312: 조족졲졳존졵졶졷졸졹졺졻졼졽졾졿좀좁좂좃좄종좆좇좈좉좊좋 :51339

51340: 좌좍좎좏좐좑좒좓좔좕좖좗좘좙좚좛좜좝좞좟좠좡좢좣좤좥좦좧 :51367

51368: 좨좩좪좫좬좭좮좯좰좱좲좳좴좵좶좷좸좹좺좻좼좽좾좿죀죁죂죃 :51395

51396: 죄죅죆죇죈죉죊죋죌죍죎죏죐죑죒죓죔죕죖죗죘죙죚죛죜죝죞죟 :51423

51424: 죠죡죢죣죤죥죦죧죨죩죪죫죬죭죮죯죰죱죲죳죴죵죶죷죸죹죺죻 :51451

51452: 주죽죾죿준줁줂줃줄줅줆줇줈줉줊줋줌줍줎줏줐중줒줓줔줕줖줗 :51479

51480: 줘줙줚줛줜줝줞줟줠줡줢줣줤줥줦줧줨줩줪줫줬줭줮줯줰줱줲줳 :51507

51508: 줴줵줶줷줸줹줺줻줼줽줾줿쥀쥁쥂쥃쥄쥅쥆쥇쥈쥉쥊쥋쥌쥍쥎쥏 :51535

51536: 쥐쥑쥒쥓쥔쥕쥖쥗쥘쥙쥚쥛쥜쥝쥞쥟쥠쥡쥢쥣쥤쥥쥦쥧쥨쥩쥪쥫 :51563

51564: 쥬쥭쥮쥯쥰쥱쥲쥳쥴쥵쥶쥷쥸쥹쥺쥻쥼쥽쥾쥿즀즁즂즃즄즅즆즇 :51591

51592: 즈즉즊즋즌즍즎즏즐즑즒즓즔즕즖즗즘즙즚즛즜증즞즟즠즡즢즣 :51619

51620: 즤즥즦즧즨즩즪즫즬즭즮즯즰즱즲즳즴즵즶즷즸즹즺즻즼즽즾즿 :51647

51648: 지직짂짃진짅짆짇질짉짊짋짌짍짎짏짐집짒짓짔징짖짗짘짙짚짛 :51675

51676: 짜짝짞짟짠짡짢짣짤짥짦짧짨짩짪짫짬짭짮짯짰짱짲짳짴짵짶짷 :51703

51704: 째짹짺짻짼짽짾짿쨀쨁쨂쨃쨄쨅쨆쨇쨈쨉쨊쨋쨌쨍쨎쨏쨐쨑쨒쨓 :51731

51732: 쨔쨕쨖쨗쨘쨙쨚쨛쨜쨝쨞쨟쨠쨡쨢쨣쨤쨥쨦쨧쨨쨩쨪쨫쨬쨭쨮쨯 :51759

51760: 쨰쨱쨲쨳쨴쨵쨶쨷쨸쨹쨺쨻쨼쨽쨾쨿쩀쩁쩂쩃쩄쩅쩆쩇쩈쩉쩊쩋 :51787

51788: 쩌쩍쩎쩏쩐쩑쩒쩓쩔쩕쩖쩗쩘쩙쩚쩛쩜쩝쩞쩟쩠쩡쩢쩣쩤쩥쩦쩧 :51815

51816: 쩨쩩쩪쩫쩬쩭쩮쩯쩰쩱쩲쩳쩴쩵쩶쩷쩸쩹쩺쩻쩼쩽쩾쩿쪀쪁쪂쪃 :51843

51844: 쪄쪅쪆쪇쪈쪉쪊쪋쪌쪍쪎쪏쪐쪑쪒쪓쪔쪕쪖쪗쪘쪙쪚쪛쪜쪝쪞쪟 :51871

51872: 쪠쪡쪢쪣쪤쪥쪦쪧쪨쪩쪪쪫쪬쪭쪮쪯쪰쪱쪲쪳쪴쪵쪶쪷쪸쪹쪺쪻 :51899

51900: 쪼쪽쪾쪿쫀쫁쫂쫃쫄쫅쫆쫇쫈쫉쫊쫋쫌쫍쫎쫏쫐쫑쫒쫓쫔쫕쫖쫗 :51927

51928: 쫘쫙쫚쫛쫜쫝쫞쫟쫠쫡쫢쫣쫤쫥쫦쫧쫨쫩쫪쫫쫬쫭쫮쫯쫰쫱쫲쫳 :51955

51956: 쫴쫵쫶쫷쫸쫹쫺쫻쫼쫽쫾쫿쬀쬁쬂쬃쬄쬅쬆쬇쬈쬉쬊쬋쬌쬍쬎쬏 :51983

51984: 쬐쬑쬒쬓쬔쬕쬖쬗쬘쬙쬚쬛쬜쬝쬞쬟쬠쬡쬢쬣쬤쬥쬦쬧쬨쬩쬪쬫 :52011

52012: 쬬쬭쬮쬯쬰쬱쬲쬳쬴쬵쬶쬷쬸쬹쬺쬻쬼쬽쬾쬿쭀쭁쭂쭃쭄쭅쭆쭇 :52039

52040: 쭈쭉쭊쭋쭌쭍쭎쭏쭐쭑쭒쭓쭔쭕쭖쭗쭘쭙쭚쭛쭜쭝쭞쭟쭠쭡쭢쭣 :52067

52068: 쭤쭥쭦쭧쭨쭩쭪쭫쭬쭭쭮쭯쭰쭱쭲쭳쭴쭵쭶쭷쭸쭹쭺쭻쭼쭽쭾쭿 :52095

52096: 쮀쮁쮂쮃쮄쮅쮆쮇쮈쮉쮊쮋쮌쮍쮎쮏쮐쮑쮒쮓쮔쮕쮖쮗쮘쮙쮚쮛 :52123

52124: 쮜쮝쮞쮟쮠쮡쮢쮣쮤쮥쮦쮧쮨쮩쮪쮫쮬쮭쮮쮯쮰쮱쮲쮳쮴쮵쮶쮷 :52151

52152: 쮸쮹쮺쮻쮼쮽쮾쮿쯀쯁쯂쯃쯄쯅쯆쯇쯈쯉쯊쯋쯌쯍쯎쯏쯐쯑쯒쯓 :52179

52180: 쯔쯕쯖쯗쯘쯙쯚쯛쯜쯝쯞쯟쯠쯡쯢쯣쯤쯥쯦쯧쯨쯩쯪쯫쯬쯭쯮쯯 :52207

52208: 쯰쯱쯲쯳쯴쯵쯶쯷쯸쯹쯺쯻쯼쯽쯾쯿찀찁찂찃찄찅찆찇찈찉찊찋 :52235

52236: 찌찍찎찏찐찑찒찓찔찕찖찗찘찙찚찛찜찝찞찟찠찡찢찣찤찥찦찧 :52263

52264: 차착찪찫찬찭찮찯찰찱찲찳찴찵찶찷참찹찺찻찼창찾찿챀챁챂챃 :52291

52292: 채책챆챇챈챉챊챋챌챍챎챏챐챑챒챓챔챕챖챗챘챙챚챛챜챝챞챟 :52319

52320: 챠챡챢챣챤챥챦챧챨챩챪챫챬챭챮챯챰챱챲챳챴챵챶챷챸챹챺챻 :52347

52348: 챼챽챾챿첀첁첂첃첄첅첆첇첈첉첊첋첌첍첎첏첐첑첒첓첔첕첖첗 :52375

52376: 처척첚첛천첝첞첟철첡첢첣첤첥첦첧첨첩첪첫첬청첮첯첰첱첲첳 :52403

52404: 체첵첶첷첸첹첺첻첼첽첾첿쳀쳁쳂쳃쳄쳅쳆쳇쳈쳉쳊쳋쳌쳍쳎쳏 :52431

52432: 쳐쳑쳒쳓쳔쳕쳖쳗쳘쳙쳚쳛쳜쳝쳞쳟쳠쳡쳢쳣쳤쳥쳦쳧쳨쳩쳪쳫 :52459

52460: 쳬쳭쳮쳯쳰쳱쳲쳳쳴쳵쳶쳷쳸쳹쳺쳻쳼쳽쳾쳿촀촁촂촃촄촅촆촇 :52487

52488: 초촉촊촋촌촍촎촏촐촑촒촓촔촕촖촗촘촙촚촛촜총촞촟촠촡촢촣 :52515

52516: 촤촥촦촧촨촩촪촫촬촭촮촯촰촱촲촳촴촵촶촷촸촹촺촻촼촽촾촿 :52543

52544: 쵀쵁쵂쵃쵄쵅쵆쵇쵈쵉쵊쵋쵌쵍쵎쵏쵐쵑쵒쵓쵔쵕쵖쵗쵘쵙쵚쵛 :52571

52572: 최쵝쵞쵟쵠쵡쵢쵣쵤쵥쵦쵧쵨쵩쵪쵫쵬쵭쵮쵯쵰쵱쵲쵳쵴쵵쵶쵷 :52599

52600: 쵸쵹쵺쵻쵼쵽쵾쵿춀춁춂춃춄춅춆춇춈춉춊춋춌춍춎춏춐춑춒춓 :52627

52628: 추축춖춗춘춙춚춛출춝춞춟춠춡춢춣춤춥춦춧춨충춪춫춬춭춮춯 :52655

52656: 춰춱춲춳춴춵춶춷춸춹춺춻춼춽춾춿췀췁췂췃췄췅췆췇췈췉췊췋 :52683

52684: 췌췍췎췏췐췑췒췓췔췕췖췗췘췙췚췛췜췝췞췟췠췡췢췣췤췥췦췧 :52711

52712: 취췩췪췫췬췭췮췯췰췱췲췳췴췵췶췷췸췹췺췻췼췽췾췿츀츁츂츃 :52739

52740: 츄츅츆츇츈츉츊츋츌츍츎츏츐츑츒츓츔츕츖츗츘츙츚츛츜츝츞츟 :52767

52768: 츠측츢츣츤츥츦츧츨츩츪츫츬츭츮츯츰츱츲츳츴층츶츷츸츹츺츻 :52795

52796: 츼츽츾츿칀칁칂칃칄칅칆칇칈칉칊칋칌칍칎칏칐칑칒칓칔칕칖칗 :52823

52824: 치칙칚칛친칝칞칟칠칡칢칣칤칥칦칧침칩칪칫칬칭칮칯칰칱칲칳 :52851

52852: 카칵칶칷칸칹칺칻칼칽칾칿캀캁캂캃캄캅캆캇캈캉캊캋캌캍캎캏 :52879

52880: 캐캑캒캓캔캕캖캗캘캙캚캛캜캝캞캟캠캡캢캣캤캥캦캧캨캩캪캫 :52907

52908: 캬캭캮캯캰캱캲캳캴캵캶캷캸캹캺캻캼캽캾캿컀컁컂컃컄컅컆컇 :52935

52936: 컈컉컊컋컌컍컎컏컐컑컒컓컔컕컖컗컘컙컚컛컜컝컞컟컠컡컢컣 :52963

52964: 커컥컦컧컨컩컪컫컬컭컮컯컰컱컲컳컴컵컶컷컸컹컺컻컼컽컾컿 :52991

52992: 케켁켂켃켄켅켆켇켈켉켊켋켌켍켎켏켐켑켒켓켔켕켖켗켘켙켚켛 :53019

53020: 켜켝켞켟켠켡켢켣켤켥켦켧켨켩켪켫켬켭켮켯켰켱켲켳켴켵켶켷 :53047

53048: 켸켹켺켻켼켽켾켿콀콁콂콃콄콅콆콇콈콉콊콋콌콍콎콏콐콑콒콓 :53075

53076: 코콕콖콗콘콙콚콛콜콝콞콟콠콡콢콣콤콥콦콧콨콩콪콫콬콭콮콯 :53103

53104: 콰콱콲콳콴콵콶콷콸콹콺콻콼콽콾콿쾀쾁쾂쾃쾄쾅쾆쾇쾈쾉쾊쾋 :53131

53132: 쾌쾍쾎쾏쾐쾑쾒쾓쾔쾕쾖쾗쾘쾙쾚쾛쾜쾝쾞쾟쾠쾡쾢쾣쾤쾥쾦쾧 :53159

53160: 쾨쾩쾪쾫쾬쾭쾮쾯쾰쾱쾲쾳쾴쾵쾶쾷쾸쾹쾺쾻쾼쾽쾾쾿쿀쿁쿂쿃 :53187

53188: 쿄쿅쿆쿇쿈쿉쿊쿋쿌쿍쿎쿏쿐쿑쿒쿓쿔쿕쿖쿗쿘쿙쿚쿛쿜쿝쿞쿟 :53215

53216: 쿠쿡쿢쿣쿤쿥쿦쿧쿨쿩쿪쿫쿬쿭쿮쿯쿰쿱쿲쿳쿴쿵쿶쿷쿸쿹쿺쿻 :53243

53244: 쿼쿽쿾쿿퀀퀁퀂퀃퀄퀅퀆퀇퀈퀉퀊퀋퀌퀍퀎퀏퀐퀑퀒퀓퀔퀕퀖퀗 :53271

53272: 퀘퀙퀚퀛퀜퀝퀞퀟퀠퀡퀢퀣퀤퀥퀦퀧퀨퀩퀪퀫퀬퀭퀮퀯퀰퀱퀲퀳 :53299

53300: 퀴퀵퀶퀷퀸퀹퀺퀻퀼퀽퀾퀿큀큁큂큃큄큅큆큇큈큉큊큋큌큍큎큏 :53327

53328: 큐큑큒큓큔큕큖큗큘큙큚큛큜큝큞큟큠큡큢큣큤큥큦큧큨큩큪큫 :53355

53356: 크큭큮큯큰큱큲큳클큵큶큷큸큹큺큻큼큽큾큿킀킁킂킃킄킅킆킇 :53383

53384: 킈킉킊킋킌킍킎킏킐킑킒킓킔킕킖킗킘킙킚킛킜킝킞킟킠킡킢킣 :53411

53412: 키킥킦킧킨킩킪킫킬킭킮킯킰킱킲킳킴킵킶킷킸킹킺킻킼킽킾킿 :53439

53440: 타탁탂탃탄탅탆탇탈탉탊탋탌탍탎탏탐탑탒탓탔탕탖탗탘탙탚탛 :53467

53468: 태택탞탟탠탡탢탣탤탥탦탧탨탩탪탫탬탭탮탯탰탱탲탳탴탵탶탷 :53495

53496: 탸탹탺탻탼탽탾탿턀턁턂턃턄턅턆턇턈턉턊턋턌턍턎턏턐턑턒턓 :53523

53524: 턔턕턖턗턘턙턚턛턜턝턞턟턠턡턢턣턤턥턦턧턨턩턪턫턬턭턮턯 :53551

53552: 터턱턲턳턴턵턶턷털턹턺턻턼턽턾턿텀텁텂텃텄텅텆텇텈텉텊텋 :53579

53580: 테텍텎텏텐텑텒텓텔텕텖텗텘텙텚텛템텝텞텟텠텡텢텣텤텥텦텧 :53607

53608: 텨텩텪텫텬텭텮텯텰텱텲텳텴텵텶텷텸텹텺텻텼텽텾텿톀톁톂톃 :53635

53636: 톄톅톆톇톈톉톊톋톌톍톎톏톐톑톒톓톔톕톖톗톘톙톚톛톜톝톞톟 :53663

53664: 토톡톢톣톤톥톦톧톨톩톪톫톬톭톮톯톰톱톲톳톴통톶톷톸톹톺톻 :53691

53692: 톼톽톾톿퇀퇁퇂퇃퇄퇅퇆퇇퇈퇉퇊퇋퇌퇍퇎퇏퇐퇑퇒퇓퇔퇕퇖퇗 :53719

53720: 퇘퇙퇚퇛퇜퇝퇞퇟퇠퇡퇢퇣퇤퇥퇦퇧퇨퇩퇪퇫퇬퇭퇮퇯퇰퇱퇲퇳 :53747

53748: 퇴퇵퇶퇷퇸퇹퇺퇻퇼퇽퇾퇿툀툁툂툃툄툅툆툇툈툉툊툋툌툍툎툏 :53775

53776: 툐툑툒툓툔툕툖툗툘툙툚툛툜툝툞툟툠툡툢툣툤툥툦툧툨툩툪툫 :53803

53804: 투툭툮툯툰툱툲툳툴툵툶툷툸툹툺툻툼툽툾툿퉀퉁퉂퉃퉄퉅퉆퉇 :53831

53832: 퉈퉉퉊퉋퉌퉍퉎퉏퉐퉑퉒퉓퉔퉕퉖퉗퉘퉙퉚퉛퉜퉝퉞퉟퉠퉡퉢퉣 :53859

53860: 퉤퉥퉦퉧퉨퉩퉪퉫퉬퉭퉮퉯퉰퉱퉲퉳퉴퉵퉶퉷퉸퉹퉺퉻퉼퉽퉾퉿 :53887

53888: 튀튁튂튃튄튅튆튇튈튉튊튋튌튍튎튏튐튑튒튓튔튕튖튗튘튙튚튛 :53915

53916: 튜튝튞튟튠튡튢튣튤튥튦튧튨튩튪튫튬튭튮튯튰튱튲튳튴튵튶튷 :53943

53944: 트특튺튻튼튽튾튿틀틁틂틃틄틅틆틇틈틉틊틋틌틍틎틏틐틑틒틓 :53971

53972: 틔틕틖틗틘틙틚틛틜틝틞틟틠틡틢틣틤틥틦틧틨틩틪틫틬틭틮틯 :53999

54000: 티틱틲틳틴틵틶틷틸틹틺틻틼틽틾틿팀팁팂팃팄팅팆팇팈팉팊팋 :54027

54028: 파팍팎팏판팑팒팓팔팕팖팗팘팙팚팛팜팝팞팟팠팡팢팣팤팥팦팧 :54055

54056: 패팩팪팫팬팭팮팯팰팱팲팳팴팵팶팷팸팹팺팻팼팽팾팿퍀퍁퍂퍃 :54083

54084: 퍄퍅퍆퍇퍈퍉퍊퍋퍌퍍퍎퍏퍐퍑퍒퍓퍔퍕퍖퍗퍘퍙퍚퍛퍜퍝퍞퍟 :54111

54112: 퍠퍡퍢퍣퍤퍥퍦퍧퍨퍩퍪퍫퍬퍭퍮퍯퍰퍱퍲퍳퍴퍵퍶퍷퍸퍹퍺퍻 :54139

54140: 퍼퍽퍾퍿펀펁펂펃펄펅펆펇펈펉펊펋펌펍펎펏펐펑펒펓펔펕펖펗 :54167

54168: 페펙펚펛펜펝펞펟펠펡펢펣펤펥펦펧펨펩펪펫펬펭펮펯펰펱펲펳 :54195

54196: 펴펵펶펷편펹펺펻펼펽펾펿폀폁폂폃폄폅폆폇폈평폊폋폌폍폎폏 :54223

54224: 폐폑폒폓폔폕폖폗폘폙폚폛폜폝폞폟폠폡폢폣폤폥폦폧폨폩폪폫 :54251

54252: 포폭폮폯폰폱폲폳폴폵폶폷폸폹폺폻폼폽폾폿퐀퐁퐂퐃퐄퐅퐆퐇 :54279

54280: 퐈퐉퐊퐋퐌퐍퐎퐏퐐퐑퐒퐓퐔퐕퐖퐗퐘퐙퐚퐛퐜퐝퐞퐟퐠퐡퐢퐣 :54307

54308: 퐤퐥퐦퐧퐨퐩퐪퐫퐬퐭퐮퐯퐰퐱퐲퐳퐴퐵퐶퐷퐸퐹퐺퐻퐼퐽퐾퐿 :54335

54336: 푀푁푂푃푄푅푆푇푈푉푊푋푌푍푎푏푐푑푒푓푔푕푖푗푘푙푚푛 :54363

54364: 표푝푞푟푠푡푢푣푤푥푦푧푨푩푪푫푬푭푮푯푰푱푲푳푴푵푶푷 :54391

54392: 푸푹푺푻푼푽푾푿풀풁풂풃풄풅풆풇품풉풊풋풌풍풎풏풐풑풒풓 :54419

54420: 풔풕풖풗풘풙풚풛풜풝풞풟풠풡풢풣풤풥풦풧풨풩풪풫풬풭풮풯 :54447

54448: 풰풱풲풳풴풵풶풷풸풹풺풻풼풽풾풿퓀퓁퓂퓃퓄퓅퓆퓇퓈퓉퓊퓋 :54475

54476: 퓌퓍퓎퓏퓐퓑퓒퓓퓔퓕퓖퓗퓘퓙퓚퓛퓜퓝퓞퓟퓠퓡퓢퓣퓤퓥퓦퓧 :54503

54504: 퓨퓩퓪퓫퓬퓭퓮퓯퓰퓱퓲퓳퓴퓵퓶퓷퓸퓹퓺퓻퓼퓽퓾퓿픀픁픂픃 :54531

54532: 프픅픆픇픈픉픊픋플픍픎픏픐픑픒픓픔픕픖픗픘픙픚픛픜픝픞픟 :54559

54560: 픠픡픢픣픤픥픦픧픨픩픪픫픬픭픮픯픰픱픲픳픴픵픶픷픸픹픺픻 :54587

54588: 피픽픾픿핀핁핂핃필핅핆핇핈핉핊핋핌핍핎핏핐핑핒핓핔핕핖핗 :54615

54616: 하학핚핛한핝핞핟할핡핢핣핤핥핦핧함합핪핫핬항핮핯핰핱핲핳 :54643

54644: 해핵핶핷핸핹핺핻핼핽핾핿햀햁햂햃햄햅햆햇했행햊햋햌햍햎햏 :54671

54672: 햐햑햒햓햔햕햖햗햘햙햚햛햜햝햞햟햠햡햢햣햤향햦햧햨햩햪햫 :54699

54700: 햬햭햮햯햰햱햲햳햴햵햶햷햸햹햺햻햼햽햾햿헀헁헂헃헄헅헆헇 :54727

54728: 허헉헊헋헌헍헎헏헐헑헒헓헔헕헖헗험헙헚헛헜헝헞헟헠헡헢헣 :54755

54756: 헤헥헦헧헨헩헪헫헬헭헮헯헰헱헲헳헴헵헶헷헸헹헺헻헼헽헾헿 :54783

54784: 혀혁혂혃현혅혆혇혈혉혊혋혌혍혎혏혐협혒혓혔형혖혗혘혙혚혛 :54811

54812: 혜혝혞혟혠혡혢혣혤혥혦혧혨혩혪혫혬혭혮혯혰혱혲혳혴혵혶혷 :54839

54840: 호혹혺혻혼혽혾혿홀홁홂홃홄홅홆홇홈홉홊홋홌홍홎홏홐홑홒홓 :54867

54868: 화확홖홗환홙홚홛활홝홞홟홠홡홢홣홤홥홦홧홨황홪홫홬홭홮홯 :54895

54896: 홰홱홲홳홴홵홶홷홸홹홺홻홼홽홾홿횀횁횂횃횄횅횆횇횈횉횊횋 :54923

54924: 회획횎횏횐횑횒횓횔횕횖횗횘횙횚횛횜횝횞횟횠횡횢횣횤횥횦횧 :54951

54952: 효횩횪횫횬횭횮횯횰횱횲횳횴횵횶횷횸횹횺횻횼횽횾횿훀훁훂훃 :54979

54980: 후훅훆훇훈훉훊훋훌훍훎훏훐훑훒훓훔훕훖훗훘훙훚훛훜훝훞훟 :55007

55008: 훠훡훢훣훤훥훦훧훨훩훪훫훬훭훮훯훰훱훲훳훴훵훶훷훸훹훺훻 :55035

55036: 훼훽훾훿휀휁휂휃휄휅휆휇휈휉휊휋휌휍휎휏휐휑휒휓휔휕휖휗 :55063

55064: 휘휙휚휛휜휝휞휟휠휡휢휣휤휥휦휧휨휩휪휫휬휭휮휯휰휱휲휳 :55091

55092: 휴휵휶휷휸휹휺휻휼휽휾휿흀흁흂흃흄흅흆흇흈흉흊흋흌흍흎흏 :55119

55120: 흐흑흒흓흔흕흖흗흘흙흚흛흜흝흞흟흠흡흢흣흤흥흦흧흨흩흪흫 :55147

55148: 희흭흮흯흰흱흲흳흴흵흶흷흸흹흺흻흼흽흾흿힀힁힂힃힄힅힆힇 :55175

55176: 히힉힊힋힌힍힎힏힐힑힒힓힔힕힖힗힘힙힚힛힜힝힞힟힠힡힢힣 :55203 

top

Trackback Address :: http://www.ssial.com/trackback/278 관련글 쓰기

Write a comment


확장된 단위 테스트 도구

(Cactus & JUnitEE)

 

자바스터디 조대협 (http://bcho.tistory.com)

 지금까지 기본적인 단위 테스트 도구에 대해서 알아보았다.

좀더 상세화된 단위 테스트의 단계를 나눠 보면 다음과 같이 나눠볼 수 있다. 

l        Type 1.코드 단위 테스트

코드상의 로직에 대해서만 테스트를 수행한다.앞 장에서 살펴본 테스트 방법이 일반적인 코드 단위 테스트 방법이다.

그러나 EJB,Servlet과 같은 J2EE 컴포넌트에 대해서 로직이 Dependency를 가지는 경우에는 EJB,Servlet 객체를 직접 연동하는 경우 container (WAS)에 배포하고, 기타 환경 설정이 필요하기 때문에, 로직 테스트를 위해서는 container 환경을 구성하기 전에 동일한 인터페이스를 가지면서 실제 로직은 없고 간단한 return값만 주는 가상의 객체 (Mock Object)이용해서 테스트 한다. Mock Test의 경우는 Spring IOC (Inversion of control)의 기능을 이용하면 좀더 쉽게 테스트할 수 있다. 

l        Type 2. In-Container 테스트

In container테스트는 , 컴포넌트들의 상호 작용과 컴포넌트와 컨테이너간의 상호작용을 테스트 하는 케이스 인데, POJO기반의 객체의 경우 문제가 없지만 J2EE와 같이 Container 위에서 동작하는 컴포넌트인 경우 Container에 배포하고, Container내에서 테스트 케이스를 수행해야 한다.

예를 들어 Servlet 클래스는 WAS환경 위에서만 작동하기 때문에, 테스트 하기 위해서는 JUnit이용하여 Pure Java환경에서는 테스트할 수 없으며 반드시 WAS 환경위에서 테스트가 필요하다. 이렇게 Container 환경내에서 테스트하는 것을 in-container 테스트라 하며,이를 지워하는 테스트 프레임웍으로는 JUnitEE Apache Cactus 테스트이 있다.  

l        Type 3. Acceptance 테스트

사용자 요구 사항에 대한 기능 테스트 이다. 좀더 쉽게 설명하면 사용자의 Use Case에 대한 시나리오에 따라 테스트를 하는것인데, 예를 들어 사이트에 LogIn해서 Form에 어떤 값을 넣고 submit 버튼을 누르면 화면상에 어떤 데이터가 출력되어야 한다는 등의 기능적 시나리오에 대한 테스트 이다. HTML 기반의 기능 테스트를 지원하는 테스트 프레임웍으로는 HttpUnit과 이를 확장한 JWebUnit등이 있다. 

l        Type 4.성능 단위 테스트

Type 1~3는 기능에 대한 테스트를 수행하였다면, Type 4는 비기능적인 이슈중에서 성능에 대한 테스트 방법으로, 각 단위 컴포넌트의 수행 시간에 대한 테스트를 수행하는 것이다. 성능 단위 테스트를 지원하는 프레임웍으로는 JPerfUnit등이 있다. 

지금까지 확장된 단위 테스트 방법에 대해서 살펴보았다. 지금부터는 이런 확장된 단위테스트를 수행할 수 있는 구체적인 테스트 프레임웍에 대해서 소개하고자 한다. 

(1)  In-container test - Catcus & JUnitEE

Type 2에서 언급한 in-container 테스트를 위한 프레임웍으로는 Apache Catcus JUnitEE이 있다.

두 프레임웍 모두 In-container 테스트를 가능하게 해주지만, JUnitEE의 경우 Catcus에 비해서 상대적으로 사용하기가 쉽다.JUnit TestCase를 구현해주고 JUnitEE의 서블릿을 설치하면, WAS에 리모트로 테스트를 수행할 수 있으며, 웹브라우져로도 테스트가 가능하다.

 Catcus의 경우 사용법은 JUnitEE에 비해서는 비교적 어렵지만, Servlet,JSP,TagLib에 대한 좀더 전문화된 테스트 프레임웍을 제공한다. 

 

EJB의 경우 Remote Interface가 있는 경우에는 In-container가 아니더라도 WAS외부에서 JUnit이용해서 테스트가 가능하지만, Local Interface만 구현한 경우에는 In-container 테스트만 가능하다. Servlet,JSP를 제외한 EJB,JMS,JNDI등의 J2EE컴포넌트들은 In-container test가 아니더라도, JUnit이용하여 리모트로 테스트 하는 것이 가능하다.

그러나 J2EE 솔루션 패키지(EAI,EP,ESB) 의 경우 J2EE의 호출 환경이 container 내에서만 호출되도록 디자인된 경우도 있기 때문에 필수적으로 In-container 테스트를 진행해야 할경우도 있다.

 In-container test의 경우 별도의 테스트 프레임웍 도입에 대한 비용(시간과 교육)이 필요한 만큼 필요성에 대해 고민한 다음 도입 여부를 결정하는 것이 필요하다.

 

 1)    JUnitEE

JUnitEE는 단순하게, TestCase를 컨테이너 안에서 수행할 수 있게 해주는데 그 목적을 둔다. 앞에서 작성한 JUnit 테스트 클래스인 HelloWorldTest EmpDAOTest Tomcat 컨테이너 안에서 수행해보자

 먼저 JUnit,DBUnit,JUnitEE 라이브러리를 다운로드 받은 후 dbunit-2.2.jar,junit.jar junitee.jar webapp WEB-INF/lib 디렉토리에 복사한다.

WEB-INF/web.xml JUnitEETestServlet을 아래와 같이 설정한다.

 

  <servlet>

    <servlet-name>JUnitEETestServlet</servlet-name>

    <description>JUnitEE test framework</description>

    <servlet-class>org.junitee.servlet.JUnitEEServlet</servlet-class>

  </servlet>

 

  <servlet-mapping>

    <servlet-name>JUnitEETestServlet</servlet-name>

    <url-pattern>/TestServlet/*</url-pattern>

  </servlet-mapping>

< 예제. JUnitEE 테스트 서블릿 설치 >

WEB-INF/testCase.txt라는 파일을 아래와 같이 작성한다. 이 파일에는 TestCase 클래명을 정의한다. 이 클래스명으로 JUnitEE가 단위테스트를 실행할 수 있게 된다.

# test Case Def

bcho.test.HelloWorldTest

bcho.simpledb.test.EmpDAOTest

<예제. JUnitEE에서 테스트 클래스를 지정하는 방법 >

위의 방법은 testCase.txt이용하여 TestCase를 지정하는 방법인데, 그 외에도 JUnitEEWar Ant task이용하여 자동으로 testCase.txt를 생성해낼 수 도 있으며 또는 web.xml servlet init parameter testCase가 들어있는 jar파일을 지정하는 방법등을 이용하여 테스트 케이스 클래스를 지정할 수 있다.

다음으로 Tomcat을 기동한후 http://ip:port/webapp_contextname/TestServlet을 수행한다.

사용자 삽입 이미지

<그림. JUnitEE의 테스트 선택화면 >

웹 화면상에서 등록한 테스트 케이스를 확인할 수 있으며 선택해서 테스트를 진행하게 되면, 다음과 같이 결과를 웹상으로 확인할 수 있다.

사용자 삽입 이미지
< 그림 JUnitEE의 테스트 결과 >

간단하게 JUnitEE이용해서 In-container 테스트를 수행하는 방법에 대해서 알아보았다.EJB JMS같은 J2EE컴포넌트들 역시 같은 방법으로 테스트가 가능하다.

또한 예제의 테스트 방법은 웹브라우져를 통해서 테스트를 수행하는 방법이었는데, 이 외에도 ant task이용해서 ant이용하여 테스트 자동화에 포함하는것도 가능하다. 

2)     Cactus

( http://jakarta.apache.org/cactus/)

또다른 in-container 단위 테스트 프레임웍인 Cactus  에 대해서 알아보자, Cactus Apache 자카르타 프로젝트의 일부로,JUnitEE JUnit 클래스를 서버에서 단순호출 해주는데 비해서 Cactus J2EE컴포넌트에 대한 좀더 정교한 테스트를 지원한다.

Cactus에서 지원하는 J2EE 컴포넌트로는 Servlet/JSP, Filter, TagLib,EJB 테스트들이 있으며, 이러한 테스트를 위한 클래스들을 지원한다.

 예를 들어 Servlet을 테스트하고자 할 때 JUnitEE를 사용하면 단순한 Java 객체로밖에 Servlet을 테스트할 수 밖에 없다. 그러나Servlet을 제대로 테스트하기 위해서는 HttpRequest 내용을 채워넣고, Servlet을 수행한후 Cookie Session 값들을 체크하고, HttpResponse의 내용을 체크해야 한다. 이런 일련의 작업들을 개발자가 일일이 Pure Java Code이용해서 테스트 코드를 만들어내기란 여간 까다로운 작업이 아니다.

 Cactus에서는 이러한 HttpRequest,Cookie,HttpReponse,Session,ServletConfig Servlet을 테스트하기 위해서 필요한 클래스들을 내장 클래스 형식으로 지원하여 쉽게 Servlet을 테스트할 수 있도록 해준다.

 JSP ServletFilter들도 마찬가지 원리로 지원해주게 된다. 

이해를 돕기 위해서 간단한 서블릿을 테스트하는 환경을 만들어보자.

서블릿은 Http Request를 통해서 id passwd를 입력받고, 이 값을 비교해서 맞으면 session id값을 저장하고 Login이 성공하였음을 response로 보내는 간단한 서블릿이다.

public class LogInServlet extends HttpServlet {

 

  public void doGet(HttpServletRequest req, HttpServletResponse res)

      throws ServletException, IOException {

 

    PrintWriter out = res.getWriter();

    HttpSession session = req.getSession();

 

    String id = req.getParameter("id");

    String passwd = req.getParameter("passwd");

 

    if(checkLogin(id,passwd)){

        // 로그인이 성공하였으면 세션에 ID 저장한다.

        session.setAttribute("id", id);

          out.print(id+" Login failed");

    }

   

    out.print(id+" Login success");

}// doGet

:

 

< 테스트할 서블릿 > 

이 서블릿을 테스트하기 위해서 Cactus로 테스트 케이스를 만든후, id passwd HttpRequest로 보낸후, session id가 저장되는지 확인한후, HttpResponse“success” 라는 문자열을 리턴하는지 확인하면,  서블릿이 정상적으로 동작하는 것을 확인할 수 있다.

Cactus로 만드는 테스트 클래스는 org.apache.cactus.ServletTestCase를 상속 받아서 구현되어야 하며, beginXXX testXXX,endXXX 메서드를 구현해야 한다.

beginXXX메서드는 test를 수행하기 전에 WebRequest 객체를 받아서 HttpRequest를 만드는 역할을 하고, testXXX에서는 실제로 테스트를 수행하며, endXXX에서는 Servlet의 수행이 끝난후에 WebResponse 객체를 받아서 HttpResponse 내용으로 테스트 결과를 검증하는 과정을 거친다. 

아래 샘플코드를 살펴보자

package bcho.servlet.test.catcus;

 import org.apache.cactus.Cookie;

import org.apache.cactus.ServletTestCase;

import org.apache.cactus.WebRequest;

import org.apache.cactus.WebResponse;

 import bcho.servlet.LogInServlet;

 public class TestLogInServlet extends ServletTestCase{

       public void beginLogin(WebRequest theRequest)

       {

             theRequest.addParameter("id", "bcho");

             theRequest.addParameter("passwd","passwd");

       }

       public void testLogin()

       {

           LogInServlet servlet = new LogInServlet();

             try{

                 servlet.init(config);

                

                 // Call method to test

                 servlet.doGet(request, response);

           }catch(Exception e){

               e.printStackTrace();

               assertTrue(false); // Exception 발생하였을 경우 실패로 간주한다.

               return;

           }

           // Perform some server side asserts

      

           assertEquals("bcho", session.getAttribute("id"));

       }

 

       public void endLogin(WebResponse theResponse)

       {

           // Asserts the returned HTTP response

             // 리턴되는 HTML 로그인 성공 메세지가 있는지 확인한다.

             String responseTxt = theResponse.getText();

             assertTrue(responseTxt.indexOf("success") > 0);

            

       }

 

}

< Cactus 서블릿 테스트 케이스 >

 beginLogin에서 request객체에 addParameter id passwd를 저장한다.testLogin에서 servlet.doGet을 수행하고, session“id”“bcho”라는 문자열이 저장되었는지 확인한다.endLogin에서는 servlet에서 나온 HttpResponse“success”라는 문자열이 있는지를 확인한다.

서블릿과 서블릿 테스트 클래스가 완성되었으면 이 환경을 서버에 배포해서 테스트를 수행해보자.

먼저 WEB-INF/lib 디렉토리에 http://jakarta.apache.org/cactus 에서 다운 받은 cactus 관련 라이브러리를 복사한다.

( cactus.jar , common-httpclient.jar,common-logging.jar, junit.jar , aspectjrt.jar ) 다음으로 위에서 만든 서블릿 클래스들을 WEB-INF/classes의 패키지 디렉토리에 알맞게 복사한다. 

다음으로 cactus 서블릿들인 ServletRedirector ServletTestRunner 클래스를 WEB-INF/web.xml에 다음과 같이 설정한다.

<?xml version="1.0" encoding="UTF-8"?>

<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

  <display-name> Einstein Unit Tester Web Application </display-name>

       <servlet>

         <servlet-name>ServletRedirector</servlet-name>

         <servlet-class>org.apache.cactus.server.ServletTestRedirector</servlet-class>

         <init-param>

           <param-name>param1</param-name>

           <param-value>value1 used for testing</param-value>

         </init-param>

       </servlet>

     

       <servlet>

         <servlet-name>ServletTestRunner</servlet-name>

         <servlet-class>org.apache.cactus.server.runner.ServletTestRunner</servlet-class>

       </servlet>

      

       <servlet-mapping>

           <servlet-name>ServletRedirector</servlet-name>

           <url-pattern>/ServletRedirector</url-pattern>

       </servlet-mapping>

      

       <servlet-mapping>

           <servlet-name>ServletTestRunner</servlet-name>

           <url-pattern>/ServletTestRunner</url-pattern>

       </servlet-mapping>

 

      

</web-app>

 

 서버를 기동하고, 웹브라우져에서 다음과 같은 URL로 접근하면 테스트 결과를 얻을 수 있다.

http://{ip}:{port}/{context-root}/ServletTestRunner?suite={서블릿테스트클래스명}

 필자는 localhost 8080포트에서 context-root Catcus로 배포하였고, 테스트 서블릿은 위에서 작성한데로, bcho.servlet.test.catcus.TestLogInServlet으로 구성하였기 때문에, 테스트 URL은 아래와 같이 구성 된다.

 http://localhost:8080/Catcus/ServletTestRunner?suite=bcho.servlet.test.catcus.TestLogInServlet

 브라우져에서 테스트해서 테스트가 성공하면 다음과 같은 결과를 얻을 수 있다. 

사용자 삽입 이미지

XML 형식이 아니라 좀더 정재되고 보기 편한 형태로 테스트 리포트를 보고 싶을 경우에는 Apache Jakarta 프로젝트에서 제공하는 XSL을 적용하면 되는데, XSLhttp://jakarta.apache.org/cactus/misc/cactus-report.xsl 에서 다운 받을 수 있다.

다운 받은 cactus-report.xsl WebApplication context-root 디렉토리에 저장한후에 QueryString으로 xsl=cactus-report.xsl xsl 파일을 다음과 같이 지정할 수 있다. http://localhost:8080/Catcus/ServletTestRunner?suite=bcho.servlet.test.catcus.TestLogInServlet&xsl=cactus-report.xsl

 XSL을 저장하여 리포트를 생성한 결과는 다음과 같다. 

사용자 삽입 이미지

지금까지 Catcus이용해서In-container 테스트를 하는 방법을 살펴보았다.

테스트를 브라우져에서 진행하였는데, 단위테스트는 위에서도 설명하였듯이 빌드 작업의 일부로 진행이 되고, 빌드는 통상적으로 자동화가 되기 때문에 Catcus이용한 단위테스트 역시 빌드과정에 자동화되어 포함되어야할 필요가 있다.

지금부터는 대표적인 자바 빌드 도구인 ANT를 통해서 Catcus 테스트를 자동화하는 방법에 대해서 알아보도록 하자.

 먼저 디렉토리 구조를 살펴보자, 필자의 경우 다음과 같은 디렉토리 구조를 구성하였다.

사용자 삽입 이미지
*.java 파일은 src 디렉토리에 위치하였으며, 개발에 필요한 각종 jar 파일은 lib 디렉토리에 저장하였다. web.xml과 같은 config 파일은 conf 파일에 저장하였다.

 ANT로 빌드를 진행하면, *.class 파일은 ./build 디렉토리에 저장되고, ./build디렉토리에 저장된 클래스와 ./lib 디렉토리의 라이브러리 그리고 ./conf/web.xml 파일은 pack 디렉토리에서 Cactus.war라는 파일로 패키징되서 저장된다.

 다음은 작성한 ANT 스크립트이다. BUILD에서부터 WAR 파일 생성까지를 진행하고, “cactus-test” 라는 task에서 cactus 테스트를 수행하도록 설정하였다. (진하게 블록으로 표시한 부분) 빌드와 패키징 부분에 대한 설명은 생략하고 cactus 이용 ANT TASK 부분을 살펴보자. 

<project name="CactusTest" default="cactus-test" >

         <!--

       ============================================================================================

        기본 환경 정보 설정

        =============================================================================================

        -->    

        <!-- 기본 디렉토리 정보 -->

        <property name="project.name" value="Cactus"/>

        <property name="lib" value="./lib"/>

        <property name="src" value="./src"/>

        <property name="build" value="./build"/>

        <property name="packaging" value="./pack"/>

        <property name="conf" value="./conf"/>

 

        <property name="cactus.home" value="D:\dev\lib\jakarta-cactus-13-1.7.2"/>

        <property name="cactus.testlog" value="c:\temp\test-report"/>

        <property name="tomcat.home" value="D:\dev\apache-tomcat-5.5.23\"/>

 

        <!-- jar 파일 경로 -->

        <property name="cactus.jar" value="${cactus.home}/lib/cactus-1.7.2.jar"/>

        <property name="cactus-ant.jar" value="${cactus.home}/lib/cactus-ant-1.7.2.jar"/>

        <property name="commons-httpclient.jar" value="${cactus.home}/lib/commons-httpclient-2.0.2.jar"/>

        <property name="commons-logging.jar" value="${cactus.home}/lib/commons-logging-1.0.4.jar"/>

        <property name="aspectjrt.jar" value="${cactus.home}/lib/aspectjrt-1.2.1.jar"/>

        <property name="cargo.jar" value="${cactus.home}/lib/cargo-0.5.jar"/>

        <property name="servletapi.jar" value="${lib}/servletapi-2.3.jar"/>

        <property name="junit.jar" value="${lib}/junit-3.8.1.jar"/>

 

        <!-- class path 설정 -->

        <path id="cactus.classpath">

            <pathelement location="${cactus.jar}"/>

            <pathelement location="${cactus-ant.jar}"/>

            <pathelement location="${commons-httpclient.jar}"/>

               <pathelement location="${commons-logging.jar}"/>

               <pathelement location="${aspectjrt.jar}"/>

               <pathelement location="${cargo.jar}"/>

               <pathelement location="${junit.jar}"/>

        </path>

        <!--

        =============================================================================================

        Task 정의

        =============================================================================================

        -->    

        <!-- Cactus task 정의 -->

        <taskdef resource="cactus.tasks" classpathref="cactus.classpath"/>

       

        <!-- Clean -->

        <target name="clean">

               <delete dir="${build}"/>

               <mkdir dir="${build}" />

               <mkdir dir="${packaging}" />

        </target>

        <!--

        =============================================================================================

        컴파일

        =============================================================================================

        -->    

        <!-- 소스 컴파일 -->

        <target name="compile"  >

               <javac srcdir="${src}" destdir="${build}">

                       <classpath>

                              <path refid="cactus.classpath"/>

                              <pathelement location="${servletapi.jar}"/>

                       </classpath>

               </javac>

        </target>

        <!--

        =============================================================================================

        패키징

        ${build} 생성된 클래스들과, ${conf} 있는 web.xml 설정 정보를 모아서 하나의 WAR파일로 패키징 한다.

        =============================================================================================

        -->    

        <!-- WAR 패키징 -->

        <target name="packaging" depends="compile" >

               <war warfile="${packaging}/${project.name}.war"      webxml="${conf}/web.xml" >

                       <classes dir="${build}" />

                       <lib dir="${lib}"/>

               </war>

        </target>

       

        <!--

        =============================================================================================

        배포

        LOCAL Machine으로 배포하는 스크립트로 tomcat /webapps 디렉토리에 war파일을 복사한다.

        =============================================================================================

        -->    

        <!-- 배포 -->

        <target name="deploy" depends="packaging" >

               <copy file="${packaging}/${project.name}.war" todir="${tomcat.home}/webapps"/>

        </target>

       

        <!--

        =============================================================================================

        테스트

        =============================================================================================

        -->           

        <!-- Cactus 테스트 -->

        <target name="cactus-test">

                <!-- Run the tests -->

                 <cactus  warfile="${packaging}/${project.name}.war" fork="yes"

                       printsummary="yes"

                     failureproperty="tests.failed"> 

                       <classpath>

                              <path refid="cactus.classpath"/>

                              <pathelement location="${servletapi.jar}"/>

                              <pathelement location="${build}"/>

                       </classpath>

                   <containerset>

                       <!--

                       <tomcat5x dir="D:\dev\apache-tomcat-5.5.23" port="8080"      />

                               -->

                       <generic name="local tomcat" port="8080">

                            <startup target="dummy"/>

                            <shutdown target="dummy"/>

                       </generic>

                      

                   </containerset>

 

                   <formatter type="brief" usefile="false"/>

                   <formatter type="xml"/>

                   <batchtest todir="${cactus.testlog}">

                     <fileset dir="./src">

                       <include name="**/Test*.java"/>

                     </fileset>

                   </batchtest>

                 </cactus>

                 <!-- JUnit(cactus) 테스트 리포트 생성 -->

                 <junitreport todir="${cactus.testlog}">

                       <fileset dir="${cactus.testlog}" includes="TEST*.xml"/>

                       <report todir="${cactus.testlog}" format="frames"/>

                 </junitreport>

                      

                 <fail if="tests.failed"> cactus Test failed</fail>

        </target>

        <target name="dummy">

        </target>

       

       

</project>

                      

<Cactus 실행용 ANT 스크립트 >

 l        cactus 용 테스크 정의하기

먼저 ant cactus 테스크를 추가하자.

CLASSPATH cactus에 필요한 클래스들을 추가한후 taskdef이용하여 cactus.tasks resource로 정의하면 cactus 관련된 task들을 정의할 수 있다. (cactus.tasks는 실제로 cactus에 관련된 jar파일 내에 존재한다.)

        <!-- class path 설정 -->

        <path id="cactus.classpath">

            <pathelement location="${cactus.jar}"/>

            <pathelement location="${cactus-ant.jar}"/>

            <pathelement location="${commons-httpclient.jar}"/>

               <pathelement location="${commons-logging.jar}"/>

               <pathelement location="${aspectjrt.jar}"/>

               <pathelement location="${cargo.jar}"/>

               <pathelement location="${junit.jar}"/>

        </path>

      

        <taskdef resource="cactus.tasks" classpathref="cactus.classpath"/>

    

이제는 cactus 관련 테스크들을 사용할 수 있다.

 l        cactus 테스크 사용

실제로 cactus 테스크를 사용해야 하는데, 테스크의 내용을 나눠보면 크게, 테스트할 WAR파일 설정, CLASSPATH 설정,테스트용 서버 정보 설정, 테스트할 유닛들 지정 및 로그 정리 절차로 나눠볼 수 있다.

 

<cactus  warfile="${packaging}/${project.name}.war" fork="yes"

printsummary="yes" failureproperty="tests.failed">

 

warfile 속성에 테스트할 (서버에 배포한) war 파일을 지정하고, failureproperty에 실패값을 지정한다. 그리고 해당 target 마지막 부분에 이 failureproperty 값으로 아래와 같이 실패 처리를 한다..

<fail if="tests.failed"> cactus Test failed</fail>

만약 이 처리를 하지 않으면 cactus 단위 테스트가 실제로 실패하더라도 ant target에 대한 결과는 Successful로 처리된다.

 다음으로 cactus 테스트에서 사용할 클래스 패스를 지정하는데, cactus 관련 클래스들 이외에, 테스트에 사용된 클래스들을 사용하기 위한 lib들이 포함되어야 하며, 반드시 빌드된 테스트 클래스들을 CLASSPATH에 포함 시켜야 한다. (아래 굵게 표시한 부분)

 

<classpath>

<path refid="cactus.classpath"/>

<pathelement location="${servletapi.jar}"/>

<pathelement location="${build}"/>

</classpath>

 

cactus in-container 테스트이기 때문에 테스트할 컨테이너를 꼭 지정해야 하는데,catcus에서는 테스트할 서버를 직접 기동해서 WAR파일을 배포하는 것 까지 자동화 할 수 있다.Tomcat Orion,Resin,WebLogic까지 지원하는데 자세한 내용은 cactus의 문서를 참고하도록 하고 필자는 generic이란 element이용하여, 단순하게, 테스트할 서버의 URL PORT만 지정하였다. (테스트할 서버는 서블릿과 테스트 서블릿이 배포된 서버를 의미한다.)

<containerset>

<generic name="local tomcat" port="8080">

      <startup target="dummy"/>

      <shutdown target="dummy"/>

</generic>

</containerset>

:

<target name="dummy">

</target>

 

그리고 두개의 element 를 지정해야 하는데, startupshutdown 엘리먼트이다.

이 엘리먼트는 서버가 기동되어 있지 않은 경우 실행되어 서버를 기동시키고 테스트가 끝난후에 서버를 자동으로 내리는 역할을 하는 “target”을 지정해야 한다. (필수 element 이다.)

필자는 서버의 기동과 정지를 ANT 내에서 처리 하지 않고 직접 처리하였기 때문에, “dummy”라는 아무 작업도 하지 않는 target을 정의하여 지정하였다.

다음으로 Unit 테스트에 대한 로그를 저장하는 설정을 진행한다. <formatter> 라는 element를 사용하는데 Cactus JUnit의 확장이기 때문에 JUnit formatter element를 지원한다.

여기서 사용한 formatter type 속성은 두가지로 brief xml 을 사용하였다.

Brief는 테스트의 실패이유를 대략적으로 출력해주는 속성이고 xml은 테스테에 대한 각종 정보(통계,프로퍼티등등)을 상세하게 저장해주는 속성이다.

brief 값은 usefile=”false” 로 설정하여, stdout(화면)으로 출력하도록 하였고, xml usefile을 설정하지 않아서 파일로 저장되도록 하였다. 파일 경로는 다음 설명할 <batchtest> element에서 지정되는 todir 디렉토리에 저장되게 된다.

 

<formatter type="brief" usefile="false"/>

<formatter type="xml"/>

 

이제 실제로 테스트할 테스트 클래스들을 선택해야 하는데 한꺼번에 지정하기 위해서 fileset element이용하여, Test*.java 로 된 모든 테스트 클래스들을 수행하도록 하였고, 로그 파일은 todir로 정의된 ${cactus.testlog} 디렉토리에 저장되도록 설정하였다.

 

<batchtest todir="${cactus.testlog}">

      <fileset dir="./src">

        <include name="**/Test*.java"/>

      </fileset>

</batchtest>

 

cactus 테스트를 수행하기 위한 준비가 완료되었고 여기까지 설정하면 실제로 테스트를 수행할 수 있다.

 

여기에 하나 덧붙여서 설명하면, 테스트 결과는 formatter에서 type=”xml”로 설정한 부분이 XML파일로 저장된다. 그러나 이 파일을 읽기가 쉽지 않기 때문에, xml 파일을 이용하여 보기 쉬운 HTML로 테스트 결과를 생성해주는데 junitreport element를 사용할 수 있다.

<junitreport todir="${cactus.testlog}">

       <fileset dir="${cactus.testlog}" includes="TEST*.xml"/>

       <report todir="${cactus.testlog}" format="frames"/>

 </junitreport>

아래는 <junitreport> 이용하여 테스트 결과를 HTML로 생성한 결과이다.

사용자 삽입 이미지
<그림 junitreport이용하여 Cactus 테스트 결과를 HTML로 생성한 결과 >

자아, 이제 cactus이용한 테스트와 리포트 생성까지 모두 살펴보았다.

다소 복잡하기는 하지만 JUnitEE보다 훨씬 더 정교한 J2EE 컴포넌트 테스트를 지원하고, ANT 스크립트 역시 개발 빌드 과정에서 필요한 부분에 약간만 더 해진것이기 때문에 정교한J2EE 컴포넌트 테스트를 진행하기 위해서는 Cactus를 사용하는 것을 권장한다.

top

Trackback Address :: http://www.ssial.com/trackback/269 관련글 쓰기

Write a comment


단위 테스트 (Unit Test)

 

2007-08-27

자바스터디 조대협(bcho.tistory.com)

 

현재 BEA Systems Korea Senior Consultant로 근무하고 있다.

SOA/SCA,EP,EAI등에 대한 기업 솔루션에 대한 아키텍쳐 컨설팅을 주로 하고 있으며, WAS 기반의 아키텍쳐 튜닝, 장애 대응에 대한 많은 경험을 가지고 있다.

 

1. 단위 테스트의 기초

2. 확장된 단위 테스트 도구

3. Test Coverage 분석

 

오래간만에 실제 프로젝트에 코더로써 참가하였다.

엔지니어 시절부터 장애나 버그, 성능에 대한 문제를 어떻게 방지할 수 있을까에 대해서 고민하고, 문제의 추적이나 장애 대처 방안, 회피 아키텍쳐들을 고민해왔지만, 애플리케이션상에서 발생하는 문제는 발견은 할 수 있지만, 발견에 드는 비용이 크고 무엇보다 문제를 미리 예방하는 것보다는 어떤 방식이라도 비효율적일 수 밖에 없다.

 

이런 고민을 하던중 자바기반의 프로젝트 관리에서 IDE이용한 개발과, 소스 버전관리 자동 빌드, 테스트와 이슈 트랙킹등의 자동화된 프로젝트 관리론에 대해 관심을 돌리게 되었고, 간단하게나마 Unit 테스트에 대해서 정리해보고자 한다.

 

1. 테스트

소프트웨어 개발에서 테스트란, ‘요구사항에 의해 개발된 산출물이 요구사항과 부합하는지 여부를 검증하기 위한 작업’이다. 소프트웨어 컴포넌트가 기능적으로 잘 작동하는지 뿐만 아니라 넓은 의미에서 고객의 요구사항이 제대로 이해되어 반영이 되었는지 등의 모든 검증 작업을 테스트로 생각할 수 있다.

많은 소프트웨어 개발자나 엔지니어들이 “테스트”의 필요성과 중요성에 대해서 인정한다. 그러나 이 “테스트”에 시간을 투자하기 보다는 비즈니스 로직에 시간을 투자하는 것 구현체에 시간을 투자하는 것을 더 중요한 일로 생각한다. (실제 프로젝트 종료 시간이 임박해 오니까는) 또는 “테스트”를 소프트웨어 개발이 완료된 후에 하는 것으로 생각한다.

테스트되지 않고 마감일에 맞추어진 시스템은 버그에 의해서 그보다 더 많은 시간을 버그 수정에 쏟아 붓게 되고 그러다 보면 코드는 어느새 스파게티 처럼 엉켜 버릴것이다.

 

전통적인 소프트웨어 개발 프로세스를 보면 아래 그림과 같이 요구사항의 분석,설계,구현,테스트의 과정을 거치게 된다. 각 과정별로 검증을 할 수 있는 테스트 방법과 도구가 존재하고 이를 통해서 문제를 단계별로 미리 해결함으로써 나중에 문제 해결과 발생에 소요되는 시간과 비용을 혁신적으로 줄일 수 있다.

사용자 삽입 이미지

< 그림 1. 전통적인 소프트웨어 개발 프로세스 >

 

(1)  요구 사항 분석 단계의 검증

요구 사항 분석 단계의 검증은 고객의 요구 사항을 분석가가 제대로 이해하였는지 검증에서 시작한다. 소프트웨어 개발 프로젝트의 대부분의 실패 원인은 잘못된 요구 사항 분석이 많은 비중을 차지한다.

 

이 과정에서 필요한 것이 바로 소프트웨어 명세서이다.

이 명세서에는 기능적인 요구 사항과, 비기능적 요구 사항(성능,장애에 대한 대처 방법)등이 기술되어야 하며, 우선적으로 반영되어야할 내용에 대한 우선순위가 설정되어야 한다.

이 명세서를 고객에게 CONFIRM받는 것으로써, 요구사항이 적절히 수집되었는지 여부를 확인할 수 있다.

그러나, 명세서를 아무리 잘 작성한다고 해도, 실제 돌아가는 애플리케이션이 아니기 때문에 의사소통에 있어서 문제가 발생할 수 있다.

고객은 “A”라고 이야기 했지만, 분석가는 “a”로 이해한 경우라고나 할까?

이를 좀더 명확하게 하기 위해서 우리는 몇가지 도구를 이용할 수 있는데, 가장 효과적이라고 생각하는 것은 바로 PILOT 프로젝트 또는 시제품이다.

 실제 기능은 구현되지 않은 UI기반의 시스템을 작성하는 것이다. Visual Basic과 같은 4GL이나 Flex와 같은 RIA(Rich Internet Application)등을 이용하면 손쉽게 작성할 수 있고, 또는 HTML기반으로 구현될 웹사이트를 만들 수 도 있다. 이는 실제로 고객의 요구 사항을 정확하게 수집하여 나중에 발생될 수 있는 문제를 미연에 방지함으로써 중복 개발이나, 잘못 분석된 요구사항으로 인해서 시스템을 뒤집어 엎는 일을 막아줌으로써, PILOT 제품을 만드는데 소요된 비용을 보상한다.

 위의 PILOT 제품 이외에, 프로세스(업무 절차)가 복잡한 경우에는 BPA(Business Process Analyze) 솔루션을 이용하여 프로세스를 순서도로 도식화하고 흐름에 대한 검증을 함으로써 복잡한 프로세스에 대한 문제도 검증할 수 있다.

 

 단 몇가지 주의할점은 고객은 대부분 기술적이지 못하기 때문에 (적어도 개발자보다는 기술적이지 못할것이다.) 파일럿으로 만들어진 제품을 보고 고객은 “폰트를 바꿔 주세요.” “표 간격이 멀어요..” 등의 기능이 아닌 화면 디자인을 가지고 이슈를 삼을지도 모른다. 물론 디자인에 대한 요구사항 수집 역시 중요한 일이지만, 기능적인 요구 사항 이외의 디자인적 이슈를 다루기 위한 파일럿이 아니라는 것을 고객에게 인식 시키고 기능적 요구사항에만 집중할 필요가 있다. 디자인팀이 고객으로부터 디자인에 대한 요구 사항의 수집을 원한다면, 기능적인 이슈에 대한 수집이 끝난후에, 파일럿 제품에 대한 디자인을 차차 변경해나가도 될 것이다.

 

※ 이글은 단위 테스트에 대해 소개하는 글이지만, 요구 사항 분석에 대한 중요성이 높기 때문에 다소 길게 요구사항 분석에 대한 검증 기법에 대해서 설명하였다

.

(2)  시스템 디자인 단계의 검증

분석된 요구 사항을 UML이나 각종 방법론을 이용해서 소프트웨어 디자인을 진행할것이다. 여기에서 특정한 테스팅 방법론을 적용하기는 매우 어렵다. 데이터 베이스의 정규화나, 또는 디자인 패턴을 이용한 설계 방법론 정도라고나 할까?

다행히 복잡한 비즈니스 프로세스에 대해서는 BPA솔루션에서 시뮬레이션을 해볼 수 는 있지만, 테스팅 방법론으로 접근하는 것보다는 소프트웨어 설계 방법론으로 접근하는 것이 좋을것이다. 자세한 내용은 Software Design에 관련된 문서들을 참고하기 바란다.

 

(3)  구현 단계의 검증

본 문서에서 다루고자 하는 부분이 구현에 대한 검증이다.

실제로 구현 (Implementation)이 들어갔을 때, 어떻게 테스트를 할것인가에 대한 검증작업인데, 소프트웨어의 구현 작업은 크게 나누면 2가지 단계로 나눠볼 수 있다. 각각의 컴포넌트를 작성하는 것과 이 작성된 컴포넌트를 통합(Integration) 하는 단계이다.

 

컴포넌트를 구현하는 단계에서는 각 컴포넌트가 디자인된 의도데로 작동하는지를 검토하는 “단위 테스트”(Unit Test)를 적용할 수 있고, 통합이 된 후에는 “통합 테스트” (Integration Test)를 수행한다.

 

개발 과정에서 이 두가지 테스트는 고객의 요구사항과 무관하게 진행되어야 한다. “구현된 소프트웨어 자체가 디자인된 데로 작동하는가” 여부를 판단하는 테스트이다.

소프트웨어가 고객의 요구사항데로 디자인되고 그대로 작동하는가 여부는 테스트 단계에서 인수 테스트 (Acceptance test) 과정에서 검증된다.

 

이 글에서는 구현 단계 검증에서 특히 컴포넌트 테스트 “단위 테스트”에 대해서 설명하고자 한다.

 

(4)  테스트 단계의 검증

통합 테스트까지 완료되었으면 우리 손에는 완성된 제품이 있는 것이다.

이 제품을 고객에게 넘기기 위해서 고객의 요구 사항을 충족하고 있는지에 대한 테스트가 필요하다. 이 테스트는 앞에서 설명한 인수 테스트(Acceptance test)이다.

먼저 고객의 요구사항에 기술된 기능이 제대로 구현되었는지에 대한 기능적 테스트가 필요하고, 장애나 성능에 대한 비기능 테스트가 필요하다.

 

기능 테스트는 정해진 시나리오에 따라 입력된 값에 대해서 적절한 출력값이 나오는가를 검증해야 한다. 이런 테스트는 일반적으로 자동화된 도구 보다는 고객의 Heuristic (사람이 테스트 하는) 하게 테스트 하는 경우가 많다.

 

비 기능적 테스트는 기본적으로 성능 테스트와 장애 테스트가 많이 이루어진다.

이런 테스트를 위해서는 실환경과 유사한 환경을 재현해주는 것이 필요한데, 재현을 위해 사용되는 도구가 부하 발생기이다. 대표적인 상용 도구로는 Load Runner가 있고, 무료로 사용할 수 있는 도구로는 Apache JMeter, MicrosoftMS Stress등이 있다.

 

이 테스트 단계에서 각종 튜닝과 장애 원인 분석, 배포 아키텍쳐에 대한 재 분석, 용량산정 등을 수행할 수 있는데, 이 강좌의 내용과는 논외니까는 여기까지만 설명하도록 한다.

 

2. 단위 테스트 도구

 

단위 테스트의 정의를 살펴보자, Pragmatic Unit Testing에서 단위 테스트를 다음과 같이 정의하고 있다.

단위 테스트는 테스트 대상이 되는 코드 기능의 아주 작은 특정 영역을 실행해 보는, 개발자가 작성한 코드 조각이다. 대개 단위 테스트는 특정 상황에서 특정 메서드를 시험해 본다. 예를 들어 어떤 정렬된 리스트에 큰 값을 넣고 이 값이 리스트 끝에 들어가 있는지 확인해볼 수 있다.

단위 테스트는 위에서 설명한 말 그대로 개발자가 작성한 컴포넌트가 입력값에 대해서 적절한 출력값을 리턴하는 지를 체크하는 것이다.

 

이런 테스트 코드는 JDK 1.4 이상의 assertion등을 사용할 수 도 있겠지만, 일일이 이런 테스트를 만들고, 자동으로 이러한 테스트를 실행하기 위한 코드를 작성하기 위해서는 많은 작업이 필요하다.

 

이런 작업들을 덜어주기 위해서 xUnit 테스트 프레임웍들이 존재한다.

몇가지 단위테스트 도구에 대해서 알아보도록 하자.

 

(1)   JUnit

JUnit은 자바 애플리케이션의 단위 테스트 자동화를 위한 프레임웍이다.

상당히 사용하기 쉽고, Eclipse와 같은 IDE,ANT와 같은 빌드 스크립트에도 쉽게 통합이 되기 때문에 가장 널리 사용되고 있다.

 

현재 사용되는 버전은 3.8버전과 4.X 버전이 있는데, 4.X 버전의 경우 “@ Annotation을 사용하면서 문법이 변경되었기 때문에 주의를 필요로한다. (본 문서에서는 3. 버전을 기준으로 설명한다.)

JUnit 테스트에 대해서 간략하게 예를 들어보면 다음과 같다.

테스트를 위한 Test 클래스를 생성한다.

테스트 클래스는 junit.framework.TestCase를 상속 받아서 구현하며, 테스트 메서드는 testXXX 메서드로 구현한다.

testXXX메서드에서 테스트는 assertXXX 메서드를 이용하여, 테스트의 성공 여부를 체크한다.

다음과 같은 클래스가 있다. getCurrentVersion() 메서드는 “version 1.0”이라는 문자열을 항상 리턴해야 한다. 이 메서드의 유효성을 체크하기 위해서 테스트 클래스를 작성하면

package bcho;

 

public class HelloWorld {

       public String getCurrentVersion(){

             return "version 1.0";

       }

}

 

다음과 같이 간단한 테스트 클래스를 만들 수 있다.

package bcho.test;

 

import junit.framework.TestCase;

import bcho.HelloWorld;

public class HelloWorldTest extends TestCase {

 

        public void testGetCurrentVersion() {

               HelloWorld hw = new HelloWorld();

               assertEquals(hw.getCurrentVersion(), "version 1.0");

        }

 

}

testGetCurrentVersion에서 getCurrentVersion이 리턴 문자열이 “version 1.0”인지 테스트를 해주는 클래스이다.

이클립스에서 이 테스트 유닛을 수행하면, 각 테스트의 통과 여부를 보여준다.

사용자 삽입 이미지

 

기본적으로 JUnit은 테스트 클래스에 포함된 모든 testXXX 메서드들을 테스트로 수행하는데, 상황에 따라서 모든 테스트 메서드가 아닌 일부 메서드만 수행하고 싶은 경우가 있다. 이럴 경우 public static Test suite(); 라는 메서드를 오버로딩함으로써 구현을 할 수 있다.

 

아래 예제는 suite 메서드를 이용한 테스트를 조합한 예이다.

이 테스트 클래스를 이용하면, 두개의 test 메서드중에서 testGetCurrentVersion2

테스트만 수행되게 된다.

package bcho.test;

 

import junit.framework.Test;

import junit.framework.TestCase;

import junit.framework.TestSuite;

import bcho.HelloWorld;

public class HelloWorldTest extends TestCase {

 

       public void testGetCurrentVersion() {

             HelloWorld hw = new HelloWorld();

             this.assertEquals(hw.getCurrentVersion(), "version 1.0");

       }

       public void testGetCurrentVersion2() {

             HelloWorld hw = new HelloWorld();

             this.assertEquals(hw.getCurrentVersion(), "version 2.0");

       }

       public HelloWorldTest(String method){

             super(method);

       }

       public static Test suite(){

             TestSuite suite = new TestSuite();

      

             suite.addTest(new HelloWorldTest("testGetCurrentVersion2"));

             return suite;

       }

}

< 그림. suite 메서드를 이용한 테스트의 조합 >

 


사용자 삽입 이미지
 

< 그림.  suite 메서드를 이용한 테스트의 조합 테스트를 수행한 결과 >

 

testXXX메서드를 구현할 때, testXXX에 대해서 공통적으로 구현되어야할 부분이 존재할 수 있다. 예를 들어서 test마다 데이터베이스 Connection Close 작업이 필요하거나, 공통적으로 test메서드에서 File Descriptor(FD)를 사용하기 때문에 매번 File Open Close가 일어날 경우등이 있다.

이러한 메서드들을 매번 testXXX 메서드에 구현할 수도 있겠지만, 모든 testXXX메서드들이 메서드 전후에 수행할 수 있는 메서드들이 있다.

l         protected void setUp();

l         protected void tearDown();

이 둘이 그 메서드들이다. 다음 예제를 보자, 다음 예제는 testXXX메서드를 수행하기 이전, 이후에 매번 데이터 베이스 연결을 열고 닫도록 구현한 코드이다.

       private Connection conn;

       protected void setUp(){

             try{

                    conn = getConnection("10.136.21.1","oracle","oracle");

             }catch(Exception e){

                    e.printStackTrace();

                    this.fail("Fail to open DB Connection");

             }     

       }

       protected void tearDown(){

             if(conn != null){

                    try{

                           conn.close();

                    }catch(Exception e){

                           e.printStackTrace();

                           this.fail("Fail to close DB Connection");

                    }

             }

       }

< 예제. setUp() tearDown()이용한 데이터베이스 연결 관리 예제 >

 

메서드 전후에 수행할 수 있는 메서드 이외에도, JUnit에서는 Test의 묶음 (위에서 설명한 Suite)단위의 setUp teardown 메서드를 제공한다. 여기서 적용된 클래스는 suite수행은 전후에 단한번씩만 수행된다. 구현 방법은 suite Object를 생성한후에, TestSetup이라는 클래스로 Wrapping하여 suite 메서드 내에서 return하면 된다.

       public static Test suite(){

             TestSuite suite = new TestSuite();

            

             suite.addTest(new HelloWorldTest("testGetCurrentVersion2"));

             TestSetup wrapper = new TestSetup(suite){

                    protected void setUp(){

                           // doSomething for initial phase

                    }

                    protected void tearDown(){

                           // doSomething after end phase

                    }

             };

             return wrapper;

       }

< 예제. setUp() tearDown suite에 구현한 예제 >

 

지금까지 간단하게나마 JUnit에 대한 사용법에 대해서 살펴보았다. 이외에도 Exception처리 등 몇가지 필요한 사항이 있지만, 본 강의는 JUnit의 사용법이 아니라 단위 테스트에 대한 전반적인 개념을 소개하기 위한 글이 기 때문에, JUnit에 대한 사용법은 http://www.junit.org/index.htm 의 문서나 또는 [실용주의 프로그래머를 위한 단위 테스트 with JUnit – 인사이트 ] 등의 서적을 참고하기 바란다.

 

지금까지 JUnit을 통한 기본적인 Java Application의 테스트 방법에 대해서 알아보았다. 단위 테스트를 Pure Java JUnit이용해서 작성해도 좋겠지만, 데이터베이스 테스트나 Servlet/JSP, EJB,JMS Java 세계에는 여러가지 특성을 가진 컴포넌트들이 존재하며, 이를 Pure Java Code로만 만들기에는 다소 많은 작업이 필요하다.

 

지금부터 일반적인 단위 테스트 프레임웍에 대해서 소개한다.

 

(2)   DBUnit

인터넷 서비스 애플리케이션이나 엔터프라이즈 애플리케이션에서 많은 비중을 차지 하는 것이 데이터베이스 관련 작업일 것이다.

이런 데이터 베이스 단위테스트를 지원하는 프레임웍으로는  DBUnit이라는 것이 있다.

테스트 시나리오를 요약해보면 다음과 같다.

1)     테스트 데이터베이스를 초기화 한다.

데이터베이스의 초기화는 XML파일에서 데이터 로딩등의 통해서 DB를 초기화할 수 있다.

2)     테스트할 객체를 수행한다.

3)     데이터베이스에서 2)에 의해 수행된 결과를 쿼리한다.

4)     XML 파일등으로부터 기대 결과를 로딩한다.

5)     3) 4) assert 메서드를 이용하여 비교한다.

6)     데이터 베이스를 테스트 전 상태로 원상 복구한다.

 

아래 예제코드를 살펴보자.

DB 단위 테스트이기 때문에 DBMS에 대한 Connection관리가 필요한데, 베이스클래스 인

DBTestCase getConnection 메서드와 closeConnection 메서드에 의해서 이루어진다.

 

직접 getConnection closeConnection 메서드를 구현할 수 도 있지만, System Property에 필요한 URL,ID,PASSWORD등을 지정하면, 자동으로 DBUnit에서 Connection 관리에 대한 메서드를 제공한다.

public class EmpDAOTest extends DBTestCase

{

       final static String JDBC_DRIVER="org.gjt.mm.mysql.Driver";

       final static String JDBC_USERID="user";

       final static String JDBC_USERPASSWD="password";

       final static String JDBC_URL

="jdbc:mysql://localhost:20001/dbms";

 

       public EmpDAOTest(String name){

             super(name);

           System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS, JDBC_DRIVER );

           System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL, JDBC_URL );

           System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME, JDBC_USERID );

           System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD, JDBC_USERPASSWD );

 

       }

 <예제, DBUnit에서 Connection을 관리하는 부분 >

 

위의 예제에서는 Connection에 관련된 Property값을 System.setProperty이용해서 지정하였지만, ANT나 시스템환경 변수를 이용해서 지정하는것도 가능하며, 위의 예제는 JDBC Driver를 통해서 직접 연결하는 예제이지만, 설정값에 따라서 DataSource,JNDI이용하는것도 가능하다.

 

데이터 베이스 연결을 위한 메서드가 구현되었으면, 테스트전의 데이터 베이스 초기화를 위한 작업이 선행 되어야 한다. 이 작업은 getSetUpOperation  에서 이루어진다. 이 메서드는 테스트가 진행되기전에 수행되어 리턴값에 따라서 데이터베이스를 초기화 하는데, CLEAN_INSERT는 데이터베이스를 모두 지우고, getDataSet에 의해서 리턴되는 Record들을 Insert하여 데이터베이스를 초기화 한다. 아래 예제에서는 초기화 Record 값들을 FlatXmlDataSet이용하여, dataset.xml에서 읽어서 그 데이터로 초기화를  수행하도록 구현하였다.

 getSetUpOperation에서 사용할 수 있는 리턴값으로는 , 모든 데이터를 지우는 DELETE_ALL 이나 INSERT,UPDATE등의 작업을 수행할 수 있다.

 

모든 테스트가 종료된 다음에는 데이터베이스를 원상복구하기 위해서 getTearDownOperation  메서드에서 getSetUpOperation  과 같은 방법으로 정리 작업을 수행한다.

 

protected DatabaseOperation getSetUpOperation()

throws Exception

{

        return DatabaseOperation.CLEAN_INSERT;

}

protected DatabaseOperation getTearDownOperation()

throws Exception

{

        return DatabaseOperation.NONE;

}

protected IDataSet getDataSet() throws Exception

{

             return new

FlatXmlDataSet(new FileInputStream("c:\\temp\\dataset.xml"));

}

 

< 예제 . 데이터 초기화 방법 >

 

<?xml version='1.0' encoding='euc-kr'?>

 

<dataset>

        <BCHO_EMP name='bcho'

                 address='Seoul Korea'/>

</dataset>

< 예제. 데이터 초기화에 사용된 dataset.xml >

 

자 여기까지 구현하였으면, 데이터베이스 테스트를 하기 위한 준비가 완료되었다. 이제부터 실제로 테스트 메서드를 작성해보자. 테스트 메서드는 위에서 설명한데로, 테스트할 메서드를 호출하고, 반영된 결과를 DBMS로부터 읽어서 기대되는 결과와 비교하면 된다.

public void testInsertEmployee(){

IDatabaseConnection conn = null;

try{

       // 1. 테스트할 객체를 호출하여 데이터베이스를 업데이트 한다.

       EmpDAO empDao = new EmpDAOImpl();

       empDao.insertEmployee("kim", "YoungIn Suji");

                   

       // 2. 업데이트 내용이 반영된후, 반영된 내용을 DBMS로부터 읽어온다.

       conn = getConnection();

       IDataSet databaseDataSet = conn.createDataSet();

       ITable actualTable

= databaseDataSet.getTable("BCHO_EMP");

                   

       // 3. 비교할 내용을 XML파일에서 읽어온다.

       IDataSet expectedDataSet

= new FlatXmlDataSet(new File("c:\\temp\\dataset.xml"));

       ITable expectedTable = expectedDataSet.getTable("BCHO_EMP");

 

       // 4. 위의 2,3 내용이 일치하는지 비교한다.

       Assertion.assertEquals(expectedTable,actualTable);

}catch(Exception e ){

             e.printStackTrace();

             fail("Test failed during testEmpTable");

}finally{

             try{

                    closeConnection(conn);

             }catch(Exception e){

                    fail("Fail to close connection");

             }//try-catch

}//finally

            

}

예제에서와 같이 EmpDAO이용하여, BCHO_EMP” 테이블을 업데이트 한후에, 업데이트 된 내용을  2항에서 actualTable이라는 객체로 받아내고, dataset.xml에서 비교할 데이터를 expectedTable형태로 받아온다.

이 둘을 비교하여, 테스트를 수행한다.

 

이 예제에서는 간단하게 두개의 테이블을 비교했지만, SELECT를 해서 테이블의 SUBSET으로만 비교하거나 또는 특정 컬럼들만 비교하는 것이 가능하다.

테스트 데이터를 예제에서는 XML파일에서 읽도록 하였지만, CSV파일에서 읽어 드리는 것도 가능하며, XML  테스트 데이터도 DBMS에서 Generate하거나 DTD역시 DBMS의 쿼리 결과를 이용해서 자동으로 생성할 수 있다.

 ( 참고 : http://dbunit.sourceforge.net/faq.html#extract )

 

간단하게나마 DBUnit의 사용법에 대해서 살펴보았다. 특히 Enterprise Application의 경우 데이터에 대한 작업이 매우 중요하기 때문에, 데이터 검증에 대한 단위테스트는 매우 중요하다. DBUnit의 경우 RDBMS에 대한 테스트도 지원하지만, FlatXmlDataSet이용해서 XML데이타 검증등에도 손쉽게 사용할 수 있다.

 

DBUnit이용하여 데이터베이스 관련 테스트를 진행할 경우에는 DBMS를 공유하여 협업을 할 경우에는 데이터에 대한 간섭이 일어날 수 있기 때문에, 가능하다면 독립된 테스트 데이터 베이스 (LOCAL PC도 좋다)를 유지하는 것을 권장한다.

 

이번 회에서는 테스트의 개념과 단위 테스트의 개념에 대해서 알아보았고, 이를 구현하기 위한 가장 기본적인 JUnit과 데이터베이스 테스트를 위한 DBUnit에 대해 알아보았다.

다음 회에서는 좀더 확장된 단위 테스트 도구를 이용한 단위 테스트 방법에 대해서 소개하도록 한다.

top

Trackback Address :: http://www.ssial.com/trackback/268 관련글 쓰기

Write a comment


JVM GC와 메모리 튜닝




자바스터디 네트워크 [www.javastudy.co.kr]

조대협 [bcho_N_O_SPAM@j2eestudy.co.kr]




모든 Java Application은 JVM(Java Virtual Machine)위에서 동작한다.
이 JVM이 동작하는데 있어서, 메모리의 구조와 특히 GC는 Application의 응답시간과 성능에 밀접한 관계를 미친다. 이번 강좌에서는 JVM 의 메모리 구조와 GC 알고리즘 (JDK 1.4.X에 포함된 새로운 알고리즘 포함) 그리고, JVM의 메모리 튜닝을 통한 Application의 성능향상방법에 대해서 알아보도록 하자.


1.GC란 무엇인가?


GC는 Garbage Collection의 약자로 Java 언어의 중요한 특징중의 하나이다.
GC는 Java Application에서 사용하지 않는 메모리를 자동으로 수거하는 기능을 말한다.
예전의 전통적인 언어 C등의 경우 malloc, free등을 이용해서 메모리를 할당하고, 일일이 그 메모리를 수거해줘야했다. 그러나 Java 언어에서는 GC 기술을 사용함에 따라서 개발자로 하여금 메모리 관리에서 부터 좀더 자유롭게 해주었다.


2.GC의 동작 방법은 어떻게 되는가?


1) JVM 메모리 영역

GC의 동작 방법을 이해하기 위해서는 Java의 메모리 구조를 먼저 이해할 필요가 있다.
일반적으로 Application에서 사용되는 객체는 오래 유지 되는 객체보다, 생성되고 얼마안있어서 사용되지 않는 경우가 많다. <그림 1 참조>


<그림 1. 메모리 foot print>


그래서 Java에서는 크게 두가지 영역으로 메모리를 나누는데 Young 영역과 Old 영역이 그것이다.
Young 영역은 생긴지 얼마 안된 객체들을 저장하는 장소이고, Old영역은 생성된지 오래된 객체를 저장하는 장소이다. 각 영역의 성격이 다른 만큼 GC의 방법도 다르다.
먼저 Java의 메모리 구조를 살펴보자.


<그림 2. Java 메모리 구조>


Java의 메모리 영역은 앞에서 이야기한 두 영역 (Young 영역,Old 영역)과 Perm 영역 이렇게 3가지로 영역으로 구성된다.


<표 1. Java 메모리 영역>



2) GC 알고리즘

그러면 이 메모리 영역을 JVM이 어떻게 관리하는지에 대해서 알아보자.
JVM은 New/Young 영역과, Old영역 이 두영역에 대해서만 GC를 수행한다. Perm영역은 앞에서 설명했듯이 Code가 올라가는 부분이기 때문에, GC가 일어날 필요가 없다. Perm영역은 Code가 모두 Load되고 나면 거의 일정한 수치를 유지한다.


○ Minor GC
먼저 New/Young영역의 GC방법을 살펴보자 New/Young 영역의 GC를 Minor GC라고 부르는데, New/Young영역은 Eden과 Survivor라는 두가지 영역으로 또 나뉘어 진다. Eden영역은 Java 객체가 생성되자 마자 저장이 되는곳이다. 이렇게 생성된 객체는 Minor GC가 발생할때 Survivor 영역으로 이동된다.

Survivor 영역은 Survivor 1과 Suvivor2 영역 두 영역으로 나뉘어 지는데, Minor GC가 발생하면 Eden과 Survivor1에 Alive되어 있는 객체를 Suvivor2로 복사한다. 그리고 Alive되어 있지 않는 객체는 자연히 Suvivor1에 남아있게 되고, Survivor1과 Eden영역을 Clear한다. (결과적으로 Alive된 객체만 Survivor2로 이동한것이다.)
다음번 Minor GC가 발생하면 같은 원리로 Eden과 Survivor2영역에서 Alive되어 있는 객체를 Survivor1에 복사한다. 계속 이런 방법을 반복적으로 수행하면서 Minor GC를 수행한다.

이렇게 Minor GC를 수행하다가, Survivor영역에서 오래된 객체는 Old영역으로 옮기게 된다.

이런 방식의 GC 알고리즘을 Copy & Scavenge라고 한다. 이 방법은 매우 속도가 빠르며 작은 크기의 메모리를 Collecting하는데 매우 효과적이다. Minor GC의 경우에는 자주 일어나기 때문에, GC에 소요되는 시간이 짧은 알고리즘이 적합하다.

이 내용을 그림을 보면서 살펴보도록 하자.


<그림 3-1. 1st Minor GC>


Eden에서 Alive된 객체를 Suvivor1으로 이동한다. Eden 영역을 Clear한다.


<그림 3-2. 2nd Minor GC>


Eden영역에 Alive된 객체와 Suvivor1영역에 Alive된 객체를 Survivor 2에 copy한다.
Eden영역과 Suvivor2영역을 clear한다.


<그림 3-3. 3rd Minor GC>


객체가 생성된 시간이 오래지나면 Eden과 Suvivor영역에 있는 오래된 객체들을 Old 영역으로 이동한다.


○ Full GC

Old 영역의 Garbage Collection을 Full GC라고 부르며, Full GC에 사용되는 알고리즘은 Mark & Compact라는 알고리즘을 이용한다. Mark & Compact 알고리즘은 전체 객체들의 reference를 쭉 따라가다면서 reference가 연결되지 않는 객체를 Mark한다. 이 작업이 끝나면 사용되지 않는 객체를 모두 Mark가 되고, 이 mark된 객체를 삭제한다.<그림 4 참고> (실제로는 compact라고 해서, mark된 객체로 생기는 부분을 unmark된 즉 사용하는 객체로 메꾸어 버리는 방법이다.)

Full GC는 매우 속도가 느리며, Full GC가 일어나는 도중에는 순간적으로 Java Application이 멈춰 버리기 때문에, Full GC가 일어나는 정도와 Full GC에 소요되는 시간은 Application의 성능과 안정성에 아주 큰 영향을 준다.


<그림 4. Full GC>




3. GC가 왜 중요한가?


Garbage Collection중에서 Minor GC의 경우 보통 0.5초 이내에 끝나기 때문에 큰문제가 되지 않는다. 그러나 Full GC의 경우 보통 수초가 소요가 되고, Full GC동안에는 Java Application이 멈춰버리기 때문에 문제가 될 수 있다.
예를 들어 게임 서버와 같은 Real Time Server를 구현을 했을때, Full GC가 일어나서 5초동안 시스템이 멈춘다고 생각해보자.
또 일반 WAS에서도 5~10초동안 멈추면, 멈추는동안의 사용자의 Request가 Queue에 저장되었다가 Full GC가 끝난후에 그 요청이 한꺼번에 들어오게 되면 과부하에 의한 여러 장애를 만들 수 있다..
그래서 원할한 서비스를 위해서는 GC를 어떻게 일어나게 하느냐가 시스템의 안정성과 성능에 큰 변수로 작용할 수 있다.


4. 다양한 GC 알고리즘


앞에서 설명한 기본적인 GC방법 (Scavenge 와 Mark and compact)이외에 JVM에서는 좀더 다양한 GC 방법을 제공하고 그 동작방법이나 사용방법도 틀리다. 이번에는 다양한 GC 알고리즘에 대해서 알아보자. 현재 (JDK 1.4)까지 나와 있는 JVM의 GC방법은 크게 아래 4가지를 지원하고 있다.

- Default Collector
- Parallel GC for young generation (from JDK 1.4 )
- Concurrent GC for old generation (from JDK 1.4)
- Incremental GC (Train GC)

1) Default Collector
이 GC 방법은 앞에서 설명한 전통적인 GC방법으로 Minor GC에 Scavenge를, Full GC에 Mark & compact 알고리즘을 사용하는 방법이다. 이 알고리즘에는 이미 앞에서 설명했기 때문에 별도의 설명을 하지는 않는다.

JDK 1.4에서부터 새로 적용되는 GC방법은 Parallel GC와 Concurrent GC 두가지 방법이 있다. Parallel GC는 Minor GC를 좀더 빨리하게 하는 방법이고 (Throughput 위주) Concurrent GC는 Full GC시에 시스템의 멈춤(Pause)현상을 최소화하는 GC방법이다.

2) Parallel GC
JDK1.3까지 GC는 하나의 Thread에서 이루어진다. Java가 Multi Thread환경을 지원함에도 불구하고, 1 CPU에서는 동시에 하나의 Thread만을 수행할 수 밖에 없기때문에, 예전에는 하나의 CPU에서만 GC를 수행했지만, 근래에 들어서 하나의 CPU에서 동시에 여러개의 Thread를 실행할 수 있는 Hyper Threading기술이나, 여러개의 CPU를 동시에 장착한 HW의 보급으로 하나의 HW Box에서 동시에 여러개의 Thread를 수행할 수 있게 되었다.

JDK 1.4부터 지원되는 Parallel GC는 Minor GC를 동시에 여러개의 Thread를 이용해서 GC를 수행하는 방법으로 하나의 Thread를 이용하는것보다 훨씬 빨리 GC를 수행할 수 있다.


<그림 7. Parallel GC 개념도>


<그림 7> 을 보자 왼쪽의 Default GC방법은 GC가 일어날때 Thread들이 작업을 멈추고, GC를 수행하는 thread만 gc를 수행한다. (그림에서 파란영역), Parallel GC에서는 여러 thread들이 gc를 수행이 가능하기 때문에, gc에 소요되는 시간이 낮아진다.

Parallel GC가 언제나 유익한것은 아니다. 앞에서도 말했듯이 1CPU에서는 동시에 여러개의 thread를 실행할 수 없기 때문에 오히혀 Parallel GC가 Default GC에 비해서 느리다. 2 CPU에서도 Multi thread에 대한 지원이나 계산등을 위해서 CPU Power가 사용되기 때문에, 최소한 4CPU의 256M 정도의 메모리를 가지고 있는 HW에서 Parallel GC가 유용하게 사용된다.

Parallel GC는 크게 두가지 종류의 옵션을 가지고 있는데,Low-pause 방식과 Throughput 방식의 GC방식이 있다.

Solaris 기준에서 Low-pause Parallel GC는 ?XX:+UseParNewGC 옵션을 사용한다. 이 모델은 Old 영역을 GC할때 다음에 설명할 Concurrent GC방법과 함께 사용할 수 있다. 이 방법은 GC가 일어날때 빨리 GC하는것이 아니라 GC가 발생할때 Application이 멈춰지는 현상(pause)를 최소화하는데 역점을 뒀다.

Throughput 방식의 Parallel GC는 ?XX:+UseParallelGC (Solaris 기준) 옵션을 이용하며 Old 영역을 GC할때는 Default GC (Mark and compact)방법만을 사용하도록 되어 있다.Minor GC가 발생했을때, 되도록이면 빨리 수행하도록 throughput에 역점을 두었다.

그외에도 ParallelGC를 수행할때 동시에 몇개의 Thread를 이용하여 Minor영역을 Parallel GC할지를 결정할 수 있는데, -XX:ParallelGCThreads= 옵션을 이용하여 Parallel GC에 사용되는 Thread의 수를 지정할 수 있다.

3) Concurrent GC

앞에서도 설명했듯이, Full GC즉 Old 영역을 GC하는 경우에는 그 시간이 길고 Application이 순간적으로 멈춰버리기 때문에, 시스템 운용에 문제가 된다.

그래서 JDK1.4부터 제공하는 Concurrent GC는 기존의 이런 Full GC의 단점을 보완하기 위해서 Full GC에 의해서 Application이 멈추어 지는 현상을 최소화 하기 위한 GC방법이다.
Full GC에 소요되는 작업을 Application을 멈추고 진행하는것이 아니라, 일부는 Application이 돌아가는 단계에서 수행하고, 최소한의 작업만을 Application이 멈췄을때 수행하는 방법으로 Application이 멈추는 시간을 최소화한다.


<그림 8. Concurrent GC 개념도>


그림 8에서와 같이 Application이 수행중일때(붉은 라인) Full GC를 위한 작업을 수행한다. (Sweep,mark) Application을 멈추고 수행하는 작업은 일부분 (initial-mark, remark 작업)만을 수행하기 때문에, 기존 Default GC의 Mark & Sweep Collector에 비해서 Application이 멈추는 시간이 현저하게 줄어든다.

Solaris JVM에서는 -XX:+UseConcMarkSweepGC Parameter를 이용해 세팅한다.

4) Incremental GC (Train GC)

Incremental GC또는 Train GC라고도 불리는 GC방법은 JDK 1.3에서부터 지원된 GC방법이다. 앞에서 설명한 Concurrent GC와 비슷하게, 의도 자체는 Full GC에 의해서 Application이 멈추는 시간을 줄이고자 하는데 있다.

Incremental GC의 작동방법은 간단하다. Minor GC가 일어날때 마다 Old영역을 조금씩 GC를 해서 Full GC가 발생하는 횟수나 시간을 줄이는 방법이다.


<그림 9. Incremental GC 개념도>


그림 9에서 보듯이. 왼쪽의 Default GC는 FullGC가 일어난후에나 Old 영역이 Clear된다. 그러나, 오른쪽의 Incremental GC를 보면 Minor GC가 일어난후에, Old 영역이 일부 Collect된것을 볼 수 있다.

Incremental GC를 사용하는 방법은 JVM 옵션에 ?Xinc 옵션을 사용하면 된다.
Incremental GC는 많은 자원을 소모하고, Minor GC를 자주일으키고, 그리고 Incremental GC를 사용한다고 Full GC가 없어지거나 그 횟수가 획기적으로 줄어드는 것은 아니다. 오히려 느려지는 경우가 많다. 필히 테스트 후에 사용하도록 하자.

※ Default GC이외의 알고리즘은 Application의 형태나 HW Spec(CPU수, Hyper threading 지원 여부), 그리고 JVM 버전(JDK 1.4.1이냐 1.4.2냐)에 따라서 차이가 매우 크다. 이론상으로는 실제로 성능이 좋아보일 수 있으나, 운영환경에서는 여러 요인으로 인해서 기대했던것만큼의 성능이 안나올 수 있기 때문에, 실환경에서 미리 충분한 테스트를 거쳐서 검증한후에 사용해야 한다.


5. GC 로그는 어떻게 수집과 분석


JVM에서는 GC 상황에 대한 로그를 남기기 위해서 옵션을 제공하고 있다.
Java 옵션에 ?verbosegc 라는 옵션을 주면되고 HP Unix의 경우 ?verbosegc ?Xverbosegc 옵션을 주면 좀더 자세한 GC정보를 얻을 수 있다. GC 정보는 stdout으로 출력이 되기 때문에 “>” redirection등을 이용해서 file에 저장해놓고 분석할 수 있다.

Example ) java ?verbosegc MyApplication

그럼 실제로 나온 GC로그를 어떻게 보는지를 알아보자.


<그림 5. 일반적인 GC 로그, Windows, Solaris>


<그림 5>는 GC로그 결과를 모아논 내용이다. (실제로는 Application의 stdout으로 출력되는 내용과 섞여서 출력된다.)
Minor GC는 ”[GC “로 표기되고, Full GC는 “[Full GC”로 표기된다.
그 다음값은 Heap size before GC인데,GC 전에 Heap 사용량 ( New/Young 영역 + Old 영역 + Perm 영역)의 크기를 나타낸다.

Heap size after GC는 GC가 발생한후에 Heap의 사용량이다. Minor GC가 발생했을때는 Eden과 Survivor 영역으 GC가 됨으로 Heap size after GC는 Old영역의 용량과 유사하다.(Minor GC에서 GC되지 않은 하나의 Survivor영역내의 Object들의 크기도 포함해야한다.)

Total Heap Size는 현재 JVM이 사용하는 Heap Memory양이다. 이 크기는 Java에서 ?ms와 ?mx 옵션으로 조정이 가능한데. 예를 들어 ?ms512m ?mx1024m로 해놓으면 Java Heap은 메모리 사용량에 따라서 512~1024m사이의 크기에서 적절하게 늘었다 줄었다한다. (이 늘어나는 기준과 줄어드는 기준은 (-XX:MaxHeapFreeRatio와 ?XX:MinHeapFreeRation를 이용해서 조정할 수 있으나 JVM vendor에 따라서 차이가 나기때문에 각 vendor별 JVM 메뉴얼을 참고하기 바란다.) Parameter에 대한 이야기는 추후에 좀더 자세히하도록 하자.

그 다음값은 GC에 소요된 시간이다.

<그림 5>의 GC로그를 보면 Minor GC가 일어날때마다 약 20,000K 정도의 Collection이 일어난다. Minor GC는 Eden과 Suvivor영역 하나를 GC하는 것이기 때문에 New/Young 영역을 20,000Kbyte 정도로 생각할 수 있다.

Full GC때를 보면 약44,000Kbyte에서 1,749Kbyte로 GC가 되었음을 볼 수 있다. Old영역에 큰 데이타가 많지 않은 경우이다. Data를 많이 사용하는 Application의 경우 전체 Heap이 512이라고 가정할때, Full GC후에도 480M정도로 유지되는 경우가 있다. 이런 경우에는 실제로 Application에서 Memory를 많이 사용하고 있다고 판단할 수 있기 때문에 전체 Heap Size를 늘려줄 필요가 있다.

이렇게 수집된 GC로그는 다소 보기가 어렵기 때문에, 좀더 쉽게 분석할 수 있게 하기 위해서 GC로그를 awk 스크립트를 이용해서 정제하면 분석이 용이하다.


<표 2. gc.awk 스크립트>


이 스크립트를 작성한후에 Unix의 awk 명령을 이용해서

% awk ?f gc.awk GC로그파일명

을 쳐주면 아래<표 3>와 같이 정리된 형태로 GC 로그만 추출하여 보여준다.


<표 3. gc.awk 스크립트에 의해서 정재된 로그>


Minor와 Major는 각각 Minor GC와 Full GC가 일어날때 소요된 시간을 나타내며, Alive는 GC후에 남아있는 메모리양, 그리고 Freed는 GC에 의해서 collect된 메모리 양이다.

이 로그파일은 excel등을 이용하여 그래프등으로 변환해서 보면 좀더 다각적인 분석이 가능해진다.

※ JDK 1.4에서부터는 ?XX:+PrintGCDetails 옵션이 추가되어서 좀더 자세한 GC정보를 수집할 수 있다.


※ HP JVM의 GC Log 수집

HP JVM은 전체 heap 뿐 아니라 ?Xverbosegc 옵션을 통해서 Perm,Eden,Old등의 모든 영역에 대한 GC정보를 좀더 정확하게 수집할 수 있다.

Example ) java ?verbosegc ?Xverbosegc MyApplication ß (HP JVM Only)

HP JVM의 GC정보는 18개의 필드를 제공하는데 그 내용을 정리해보면 <표 4.>와 같다.

<GC : %1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14 %15 %16 %17 %18>


<표 4. HP JVM GC 로그 필드별 의미>


이 로그를 직접 보면서 분석하기는 쉽지가 않다. 그래서, HP에서는 좀더 Visual한 환경에서 분석이 가능하도록 HPJtune이라는 툴을 제공한다. 다음 URL에서 다운로드 받을 수 있다.

http://www.hp.com/products1/unix/java/java2/hpjtune/index.html


<그림 6. HP Jtune을 이용해서 GC후 Old영역의 변화 추이를 모니터링하는 화면>




6. GC 관련 Parameter


GC관련 설정값을 보기전에 앞서서 ?X와 ?XX 옵션에 대해서 먼저 언급하자. 이 옵션들은 표준 옵션이 아니라, 벤더별 JVM에서 따로 제공하는 옵션이기 때문에, 예고 없이 변경되거나 없어질 수 있기 때문에, 사용전에 미리 JVM 벤더 홈페이지를 통해서 검증한다음에 사용해야한다.

1) 전체 Heap Size 조정 옵션

전체 Heap size는 ?ms와 ?mx로 Heap 사이즈의 영역을 조정할 수 있다. 예를 들어 ?ms512m ?mx 1024m로 설정하면 JVM은 전체 Heap size를 application의 상황에 따라서 512m~1024m byte 사이에서 사용하게 된다. 그림2의 Total heap size

메모리가 모자를때는 heap을 늘리고, 남을때는 heap을 줄이는 heap growing과 shirinking 작업을 수행하는데, 메모리 변화량이 큰 애플리케이션이 아니라면 이 min heap size와 max heap size는 동일하게 설정하는 것이 좋다. 일반적으로 1GB까지의 Heap을 설정하는데에는 문제가 없으나, 1GB가 넘는 대용량 메모리를 설정하고자 할 경우에는 별도의 JVM 옵션이 필요한 경우가 있기때문에 미리 자료를 참고할 필요가 있다.

※ IBM AIX JVM의 경우
%export LDR_CNTRL=MAXDATA=0x10000000
%java -Xms1500m -Xmx1500m MyApplication

2) Perm size 조정 옵션

Perm Size는 앞에서도 설명했듯이, Java Application 자체(Java class etc..)가 로딩되는 영역이다. J2EE application의 경우에는 application 자체의 크기가 큰 편에 속하기 때문에, Default로 설정된 Perm Size로는 application class가 loading되기에 모자른 경우가 대부분이기 때문에, WAS start초기나, 가동 초기에 Out Of Memory 에러를 유발하는 경우가 많다.

PermSize는 -XX:MaxPermSize=128m 식으로 지정할 수 있다.
일반적으로 WAS에서 PermSize는 64~256m 사이가 적절하다.

3) New 영역과 Old 영역의 조정New 영역은 ?XX:NewRatio=2 에 의해서 조정이 된다.
NewRatio Old/New Size의 값이다. 전체 Heap Size가 768일때, NewRatio=2이면 New영역이 256m, Old 영역이 512m 로 설정이 된다.
JVM 1.4.X에서는 ?XX:NewSize=128m 옵션을 이용해서 직접 New 영역의 크기를 지정하는 것이 가능하다.

4) Survivor 영역 조정 옵션
-XX:SurvivorRatio=64 (eden/survivor 의 비율) :64이면 eden 이 128m일때, survivor영역은 2m가 된다.

5) -server와 ?client 옵션
JVM에는 일반적으로 server와 client 두가지 옵션을 제공한다.
결론만 말하면 server 옵션은 WAS와 같은 Server환경에 최적화된 옵션이고, client옵션은 워드프로세서와 같은 client application에 최적화된 옵션이다. 그냥 언뜻 보기에는 단순한 옵션 하나로보일 수 있지만, 내부에서 돌아가는 hotspot compiler에 대한 최적화 방법과 메모리 구조자체가 아예 틀리다.

○ -server 옵션

server용 application에 최적화된 옵션이다. Server application은 boot up 시간 보다는 user에 대한 response time이 중요하고, 많은 사용자가 동시에 사용하기 때문에 session등의 user data를 다루는게 일반적이다. 그래서 server 옵션으로 제공되는 hotspot compiler는 java application을 최적화 해서 빠른 response time을 내는데 집중되어 있다.

또한 메모리 모델 역시, 서버의 경우에는 특정 사용자가 서버 운영시간동안 계속 서버를 사용하는게 아니기 때문에 (Login하고, 사용한 후에는 Logout되기 때문에..) 사용자에 관련된 객체들이 오래 지속되는 경우가 드물다. 그래서 상대적으로 Old영역이 작고 New 영역이 크게 배정된다. <그림 7. 참조 >

○ -client 옵션

client application은 워드프로세서 처럼 혼자 사용하는 application이다. 그래서 client application은 response time보다는 빨리 기동되는데에 최적화가 되어 있다. 또한대부분의 client application을 구성하는 object는GUI Component와 같이 application이 종료될때까지 남아있는 object의 비중이 높기 때문에 상대적으로 Old 영역의 비율이 높다.


<그림 7. ?server와 ?client 옵션에 따른 JVM Old와 New영역>


이 두옵션은 가장 간단한 옵션이지만, JVM의 최적화에 아주 큰부분을 차지하고 있는 옵션이기 때문에, 반드시 Application의 성격에 맞춰서 적용하기 바란다.
(※ 참고로, SUN JVM은 default가 client, HPJVM는 default가 server로 세팅되어 있다.)

○ GC 방식에 대한 옵션

GC 방식에 대한 옵션은 앞에서도 설명했지만, 일반적인 GC방식이외에, Concurrent GC,Parallel GC,Inceremental GC와 같이 추가적인 GC Algorithm이 존재한다. 옵션과 내용은 앞장에서 설명한 “다양한 GC알고리즘” 을 참고하기 바란다.


7.JVM GC 튜닝


그러면 이제부터 지금까지 설명한 내용을 기반으로 실제로 JVM 튜닝을 어떻게 하는지 알아보도록 하자.

STEP 1. Application의 종류와 튜닝목표값을 결정한다.

JVM 튜닝을 하기위해서 가장 중요한것은 JVM 튜닝의 목표를 설정하는것이다. 메모리를 적게 쓰는것이 목표인지, GC 횟수를 줄이는것이 목표인지, GC에 소요되는시간이 목표인지, Application의 성능(Throughput or response time) 향상인지를 먼저 정의한후에. 그 목표치에 근접하도록 JVM Parameter를 조정하는것이 필요하다.

STEP 2. Heap size와 Perm size를 설정한다.

-ms와 ?mx 옵션을 이용해서 Heap Size를 정한다. 일반적으로 server application인 경우에는 ms와 mx 사이즈를 같게 하는것이 Memory의 growing과 shrinking에 의한 불필요한 로드를 막을 수 있어서 권장할만하다.

ms와mx사이즈를 다르게 하는 경우는 Application의 시간대별 memory 사용량이 급격하게 변화가 있는 Application에 효과적이다.
PermSize는 JVM vendor에 따라 다소 차이가 있으나 일반적으로 16m정도이다. Client application의 경우에는 문제가 없을 수 있지만, J2EE Server Application의 경우 64~128m 사이로 사용이 된다.

Heap Size와 Perm Size는 아래 과정을 통해서 적정 수치를 얻어가야한다.

STEP 3. 테스트 & 로그 분석.

JVM Option에 GC 로그를 수집하기 위한 ?verbosegc 옵션을 적용한다. (HP의 경우 ?Xverbosegc 옵션을 적용한다.)

LoadRunner나 MS Strest(무료로 MS社의 홈페이지에서 다운로드 받을 수 있다.)와 같은 Strest Test툴을 통해서 Application에 Strest를 줘서. 그 log를 수집한다. 튜닝에서 있어서 가장 중요한것은 목표산정이지만, 그만큼이나 중요한것은 실제 Tuning한 Parameter가 Application에 어떤 영향을 주는지를 테스트하는 방법이 매우 중요하다. 그런 의미에서 적절한 Strest Tool의 선정과, Strest Test 시나리오는 정확한 Tuning을 위해서 매우 중요한 요인이다.

○ Perm size 조정
아래 그림8.은 HP JVM에서 ?Xverbosegc 옵션으로 수집한 GC log를 HP Jtune을 통해서 graph로 나타낸 그래프이다. 그림을 보면 Application이 startup되었을때 Perm 영역이 40m에서. 시간이 지난후에도 50m 이하로 유지되는것을 볼 수 있다. 특별하게 동적 classloading등이 수십m byte가 일어나지 않는등의 큰 변화요인이 없을때, 이 application의 적정 Perm 영역은 64m로 판단할 수 있다.


<그림 8. GC 결과중 Perm 영역 그래프>


○ GC Time 수행 시간 분석

다음은 GC에 걸린 시간을 분석해보자. 앞에 강좌 내용에서도 설명햇듯이. GC Tuning에서 중요한 부분중 하나가 GC에 소요되는 시간 특히 Full GC 시간이다.

지금부터 볼 Log는 모社의 물류 시스템의 WAS 시스템 GC Log이다. HP JVM을 사용하며, -server ?ms512m ?mx512m 옵션으로 기동되는 시스템이다.

그림 9를 보면 Peak 시간 (첫번째 동그라미) 14시간동안에 Full GC(동그란점)가 7번일어난것을 볼 수 있다. 각각에 걸린 시간은2.5~6sec 사이이다.
여기서 STEP 1.에서 설정한 AP Tuning의 목표치를 참고해야하는데.

Full GC가 길게 일어나서 Full GC에 수행되는 시간을 줄이고자 한다면 Old 영역을 줄이면 Full GC가 일어나는 횟수는 늘어나고, 반대로 Full GC가 일어나는 시간을 줄어들것이다.

반대로 Full GC가 일어나는 횟수가 많다면, Old 영역을 늘려주면 Full GC가 일어나는 횟수는 상대적으로 줄어들것이고 반대로 Full GC 수행시간이 늘어날 것이다.

특히 Server Application의 경우Full GC가 일어날때는 JVM자체가 멈춰버리기 때문에, 그림 9의 instance는 14시간동안 총 7번 시스템이 멈추고, 그때마다 2.5~6sec가량 시스템이 response를 못하는 상태가 된것이다. 그래서 멈춘 시간이 고객이 납득할만한 시간인지를 판단해야 하고, 거기에 적절한 Tuning을 해야한다.

Server Application에서 Full GC를 적게일어나게하고, Full GC 시간을 양쪽다 줄이기 위해서는 Old영역을 적게한후에, 여러개의 Instance를 동시에 뛰어서 Load Balancing을 해주면, Load가 분산되기 때문에 Full GC가 일어나는 횟수가 줄어들테고, Old 영역을 줄였기 때문에, Full GC에 드는 시간도 줄어들것이다. 또한 각각의 FullGC가 일어나는동안 하나의 서버 instance가 멈춰져 있어도, Load Balancing이 되는 다른 서버가 response를 하고 있기때문에, Full GC로 인한 Application이 멈추는것에 의한 영향을 최소화할 수 있다.


<그림 9. GC 소요시간>


데이타에 따라서 GC Tuning을 진행한후에는 다시 Strest Test를 진행해서 응답시간과 TPS(Throughput Per Second)를 체크해서 어떤 변화를 주었는지를 반드시 체크해봐야한다.


<그림 10. GC후의 Old 영역>


그림 10은 GC후에 Old 영역의 메모리 변화량을 나타낸다.

금요일 업무시간에 메모리 사용량이 올라가다가. 주말에가서 완만한 곡선을 그리는것을 볼 수 있다. 월요일 근무시간에 메모리 사용량이 매우 많고, 화요일에도 어느정도 메모리 사용량이 있는것을 볼 수 있다. 월요일에 메모리 사용량이 많은것을 볼때, 이 시스템의 사용자들이 월요일에 시스템 사용량이 많을 수 있다고 생각할 수 있고, 또는 다른 주의 로그를 분석해봤을때 이 주만 월요일 사용량이 많았다면, 특별한 요인이나 Application 변경등이 있었는지를 고려해봐야할것이다.

이 그래프만을 봤을때 Full GC가 일어난후에도 월요일 근무시간을 보면 Old 영역이 180M를 유지하고 있는것을 볼 수 있다. 이 시스템의 Full GC후의 Old영역은 80M~180M를 유지하는것을 볼 수 있다. 그래서 이 시스템은 최소 180M이상의 Old 영역을 필요로하는것으로 판단할 수 있다.

STEP 4. Parameter 변경.
STEP 3에서 구한 각 영역의 허용 범위를 기준으로 Old영역과 New 영역을 적절하게 조절한다.
PermSize와 New영역의 배분 (Eden,Survivor)영역등을 조정한다.
PermSize는 대부분 Log에서 명확하게 나타나기 때문에, 크게 조정이 필요가 없고 New영역내의 Eden과 Survivor는 거의 조정하지 않는다. 가장 중요한것은 Old영역과 New 영역의 비율을 어떻게 조정하는가가 관건이다.

이 비율을 결정하면서, STEP1에서 세운 튜닝 목표에 따라서 JVM의 GC Algorithm을 적용한다. GC Algorithm을 결정하는 기본적인 판단 내용은 아래와 같다.



이렇게 Parameter를 변경하면서 테스트를 진행하고, 다시 변경하고 테스트를 진행하는 과정을 거쳐서 최적의 Parameter와 GC Algorithm을 찾아내는것이 JVM의 메모리 튜닝의 이상적인 절차이다.


지금까지 JVM의 메모리 구조와 GC 모델 그리고 GC 튜닝에 대해서 알아보았다.

정리하자면 GC 튜닝은 Application의 구조나 성격 그리고, 사용자의 이용 Pattern에 따라서 크게 좌우 되기때문에, 얼마만큼의 Parameter를 많이 아느냐 보다는 얼마만큼의 테스트와 로그를 통해서 목표 값에 접근하느냐가 가장 중요하다.
top

Trackback Address :: http://www.ssial.com/trackback/267 관련글 쓰기

Write a comment