· KLDP.org · KLDP.net · KLDP Wiki · KLDP BBS ·
Humble Little Ruby Book/Chap6


1. 6 라이브러리입니다


보셨다시피 루비에는 접근할 수 있는 내장 클래스들이 많습니다. 간단한 텍스트 입출력에서부터 네트워킹과 멀티미디어에 이르기까지 정말 많은 라이브러리들이 있습니다. Win32와 네트워크 등 몇 가지 라이브러리를 선택하여 살펴보았지만, 잡다한 몇 가지 클래스들을 빨리 살펴보는데 시간을 투자하고 싶습니다.

1.1. 문자열 다루기


루비의 문자열 지원은 펄이나 다른 언어들과 견줄 수 있는 수준입니다. 사실상 래리 월이 집에 가면 루비 프로그래머로 탈바꿈한다는 말도 들은 적이 있습니다. 제가 이런 것을 알려드렸다는 것을 다른 사람에게 알리지 마세요. 문자열을 다루는데는 두 가지 방법이 있습니다. 스트링 객체의 인스턴스 메소드와 정규식입니다.

1.1.1. 인스턴스 메소드

루비 스트링은 물론 객체이고, 다양한 방법으로 그것을 다룰 수 있는 메소드를 제공합니다. 먼저, Splice(접합)라고 하는 가장 단순하게 다룰 수 있는 것을 알아볼 것입니다. 첫 번째로 접합하는 방법은 접합 연산자를 사용하는 방법입니다. 배열 연산자 쓰는 것처럼 쓰시면 됩니다. 객체의 형태로 특정 원소에 접근할 수 있습니다. 예를 들어 다음과 같습니다.

the_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
puts the_alphabet[0..2]
→   ABC
puts the_alphabet["HIJ"]
→   HIJ
puts the_alphabet[1,2]
→   BC
the_alphabet[1] = "!"
puts the_alphabet
→   A!CDEFGHIJKLMNOPQRSTUVWXYZ

다른 방법으로는 splice 메소드를 사용해서 비슷한 방법으로 접합할 수 있습니다.

puts the_alphabet.slice(0, 3)
→   ABC
puts the_alphabet.slice(0..5)
→   ABCDEF

문자열을 원하는대로 다양한 방법으로 붙이고 자르고 할 수 있습니다. 대소문자를 바꿀 수 있는 메소드도 있으며, 특정 내용을 편집하거나, 문자열을 변경할 수 있습니다. 여기 예제가 있습니다.

a_str = "I likE To MesS with STRINGS!"
a_str.downcase
→   i like to mess with strings!
a_str.upcase
→   I LIKE TO MESS WITH STRINGS!
a_str.swapcase
→   i LIKe tO mESs WITH strings!
a_str.capitalize
→   I like to mess with strings!
a_str.capitalize!
puts a_str
→   I like to mess with strings!
a_str.squeeze
→   I like to mes with strings!

Squeeze 메소드는 같은 문자가 연달아 있는 경우에 이것을 하나의 문자만 나오도록 바꾸어주는데, 이것을 제외하고 다른 메소드들은 이름만 보아도 사용법을 분명히 알 수 있습니다. 각 메소드는 capitalize!에서 볼 수 있듯이, 그 자리에서 문자열을 변경시켜주는 메소드도 제공하고 있습니다.

insert 메소드를 이용하면 특정 위치에 문자열을 삽입할 수 있습니다. 이 메소드는 문자열을 그 자리에서 변경시키니 주의하세요! (왜 이 메소드 뒤에는 느낌표가 붙지 않는지 알 수 없지만 실제로 안 붙어 있습니다.)

into = "12345"
into.insert(2, "LETTERS!")
puts into
→   12LETTERS!345

한편으로 특정 범위의 문자들을 문자열에서 지우려면 delete 메소드를 이용합니다.

gone = "got gone fool!"
gone.delete("o", "r-v")
puts gone
→   g gne fl!

하이픈을 두 글자 사이에 넣으면 루비는 이 문자들을 포함하여 문자들 사이에 있는 범위를 생성합니다. 여러 메소드에서 이 기술을 쓸 수 있지만 삭제할 때 특히 유용합니다.

마지막 문자를 지우는 것은 매우 짜증스럽고 어떤 언어에서는 길게 써야 합니다. (바로 여러분의 PHP를 보고 있습니다.) 다행히도, 문자열 처리가 강력한 언어답게 chomp와 chop을 제공합니다.

eat_me = "A lot of letters\n"
eat_me.chomp
→   A lot of letters
eat_me.chop
→   A lot of letter
eat_me.chomp("ter")
→   A lot of let

chomp 메소드는 기록 분리자를 문자열의 마지막에서부터 지워줍니다. 이것은 전역 객체인 $/에 저장되어 있습니다. $/를 기본값으로 두었다면 \n, \r\n, \r과 같은 줄바꿈 문자들을 지울 것입니다. 마지막 예제에서처럼 chomp에 어떤 문자를 지울 것인지를 매개변수를 넘겨서 정할 수 있습니다. chop 메소드는 간단히 마지막 글자를 무엇이든 상관없이 지워버립니다. (주: \r\n은 한 문자로 인식합니다.)

간단히 시작과 끝에 있는 공백 문자들을 지워야 한다면, strip 메소드를 이용하십시오. lstrip, rstrip이나 그냥 strip을 쓰시면 됩니다. 예를 들어 보겠습니다.

stripper = "    La di da!    \t"
puts "[" + stripper.lstrip + "]"
→  [La di da!         ]
puts "[" + stripper.rstrip + "]"
→  [     La di da!]
puts "[" + stripper.strip + "]"
→  [La di da!]

예제가 명확하지 않다면 lstrip과 rstrip 메소드는 어떤 종류의 빈칸(스페이스, 탭, 줄바꿈 등)을 각각 왼쪽이나 오른쪽 편에서 지웁니다. strip 메소드는 양쪽에서 모두 지웁니다.

split 메소드는 문자열 메소드 중에서 가장 많이 사용하는 메소드 중에 하나일 것입니다. 정해진 구분자로 문자열을 부분 문자열로 나누어 줍니다. 한 입에 쏙 들어갈 정도이니까 예제 보세요.

breakable = "break,it,down,fool!"
breakable.split(",")
→  ["break", "it", "down", "fool!"]

split 메소드는 여러 조각으로 문자열을 나눕니다. 이 조각들은 스트링에 있는 구분자 위치를 기준으로 구분자를 제거하면서 잘라서 배열에 넣은 것입니다. 배열에 들어가게 되면, 집합체를 따라서 반복하여 자료에 다양한 연산을 하는 것이 가능해집니다.

그렇지만 문자열에 허용되지 않는 연산을 하는 경우에는 어떻게 해야 할까요? 예를 들어 문자열을 텍스트 파일에서 읽어들여 숫자와 함께 산술 연산을 해야 하는데, 파일에서 읽으면 항상 문자열이 됩니다. 루비에 있는 to_f와 to_i 메소드를 이용하면 이것을 할 수 있게 됩니다.

numerical_string = "1234"
numerical_string + 6
→  ! TypeError
numerical_string.to_f + 6.7
→  1240.7

소켓이나 파일, 혹은 사용자로부터 입력받은 자료들은 모두 문자열이라는 것을 기억하는 것은 매우 중요합니다. 이런 자료에 어떤 종류의 수학 연산을 하려고 하면 이런 메소드를 먼저 실행해야 합니다.

1.1.2. 정규식


이전에 정규식을 넘겨주는 방법에 대해서 언급한 적이 있으므로 다른 곳에서 루비 예제를 본다고 하더라도 구문의 생김새가 헷갈리지 않을 것입니다. 이제 정규식이 어떤 것이고 이것이 얼마나 놀라울 정도로 유용하게 쓰일 수 있는지에 대해서 이야기하고자 합니다.

먼저, 저는 여러분에게 정규식에 대한 완벽한 내용을 가르칠 생각도 없고, 그 셋째 사촌뻘인 Samuel del Fuega 4세의 전기로 여러분을 지루하게 하고 싶은 생각도 없습니다. 단지 저는 아주 기본적인 예제로 메소드의 사용법과 아무 대가도 없이 특별가로 부록에 정규식의 어두운 세계로 빠져들어가고 싶은 분이 참고할 만한 URL을 첨부해 두었습니다. 부록 A에 있습니다. 흥분되시지 않나요? 이제 진행해 봅시다.

정규식은 때때로 regexp 혹은 regex라고도 부르는 문자열인데, 구문 법칙들에 따라서 이것에 어울리는 문자열들의 집합을 기술한 것입니다. 간단한 검색에서부터 시작하여, 검색을 포함한 문자열 치환, 대체, 자료 이동을 할 수 있습니다. 문자열을 다루기 위한 유용한 도구로써 여러 프로그래밍 언어에 추가되어 있고, 후식에 얹어놓은 맛있는 토핑입니다.

정규식은 여러가지를 할 수 있도록 풍부한 구문을 두고 있습니다. 그러나 간결함과 스코프의 온전함으로 인하여 아주 간단하게 정규식을 이용할 수 있습니다. 정규식을 완전히 이해하려면 정규식 입문서 꺼내들어야겠지만, 입문서 없이도 진행 상황을 이해하실 수 있을 것입니다.

정규식을 가장 간단히 사용하는 맞춤부터 시작해 봅시다. 정규식으로 쉽게 문자열을 찾을 수 있습니다. 문자열이 다른 문자열의 부분문자열인지 보고 싶다고 한다면 다음과 같이 하면 됩니다.

matchmaker = "I'm a cowboy, baby!"
matchmaker =~ /cow/
→  6
matchmaker.match("cow")
→  #<MatchData:0x61d3120>

=~ 연산자를 이용하면 패턴이 처음 맞아들어가는 곳의 위치를 돌려줍니다. 그러나 문자열의 match 메소드를 이용하면, MatchData 객체를 얻을 수 있고 이 맞춤에 접근할 수 있는 여러가지 옵션을 줍니다. 예를 들면 다음과 같습니다.

my_message = "Hello there!"
my_message.match(/(..)(..)/).captures
→  ["He", "ll"]

captures 메소드를 이용하면 각 정규식에 맞는 결과를 반환합니다. to_a 메소드도 비슷하게 결과를 내지만, 완전히 맞춰진 문자열도 반환합니다. 좀 바보같은 예제였지만 더 그럴듯한 예제를 봅시다. 두 구분자 사이에 있는 텍스트를 얻고 싶다고 합시다. 여기 원하는 것이 있습니다.

html_element = "<html>"
my_element_text = html_element.match("(<)(.*)(>)").captures
puts my_element_text[1]
→  html

호기심이 많으신 편이라면 루비 API 문서에 보셔서 MatchData 클래스에 대한 정보를 보실 수도 있습니다.

중간에 있는 것들은 버리고 맞춰진 결과의 배열만을 간단히 얻고자 한다면 scan 메소드를 쓰실 수 있습니다.

my_message = "Hello there!"
my_message.scan(/(..)(..)/)
→  [["He", "ll"], ["o ", "th"], ["er", "e!"]]

돌려주는 값이 다른 것 외에도 match와 scan은 "욕심"의 정도가 다릅니다. "욕심 많은" 정규식이나 메소드는 하나가 아니라 발생되는 모든 무늬를 맞추려고 노력합니다. scan 메소드는 욕심이 많습니다. 이것은 문자열에 나타나는 모든 무늬를 다 맞추려고 합니다. (주: match를 이용하더라도 특정 정규식을 이용하면 욕심많게 할 수 있습니다.)

정규식은 문자열을 대체하는 용도로도 사용할 수 있습니다. 제대로 유력한 정규식을 이용하면 정규식을 이용하여 문자열을 대체하는 것은 아주 유연합니다. 다시 말씀드려서 고급 정규식에 대하여 논의하지 않기 때문에 이 기법의 진정한 유용함은 나타나지 않을 것입니다. 그러나 정규식을 진지하게 살펴보시고 이점을 얻을 수 있으셨으면 좋겠습니다. 정규식을 이용하여 문자열을 대체하려면 문자열 인스턴스의 sub이나 gsub 메소드를 이용해야 합니다.

go_away = "My name is Freak Nasty!"
go_away.sub("y", "o")
→  Mo name is Freak Nasty!
go_away.gsub("y", "o")
→  Mo name is Freak Nasto!
go_away.sub("Freak Nasty", "Fred Jones")
→  My name is Fred Jones!

sub 메소드는 첫번째로 무늬와 맞는 것만 바꾸지만 gsub는 욕심이 많아서 모든 맞춤을 바꾸려고 할 것입니다.. (g를 포기하지 않았으니까요) 각 메소드의 욕심의 정도는 특정 정규식을 사용하는지에 달려 있습니다. 더 강력한 정규식을 배우면 더 많이 쓸 수 있습니다. 부록 A를 보셔서 더 많은 정보를 얻으세요.

1.2. 날짜/시간

델리에서 햄과 맛있는 고기를 찾으면서 달력을 버뮤다에 두고 온 것은 깨달으신 적이 있습니까? 지난 천년에 제게 그런 일이 일어났고 얘기를 해 드리자면, 전 유혹에 넘어가기 직전이었습니다. 달력이 없다는 것은 낮이 없다는 것이고, 낮이 없다는 것은 밤이 없다는 뜻으로 제가 죽을 수도 있다는 것입니다. 다행히도 루비가 꽤나 쓸만한 날짜와 시간 라이브러리를 제공해서 제 반쪽 처남의 애완동물 원숭이의 조련사의 개를 시켜서 어제 우편으로 제 달력을 보내도록 하였습니다.

루비 라이브러리에는 세 개의 날짜 시간 클래스가 있습니다. Date, Time 가 1.4에서 만들어졌고, 9 개월 뒤에 DateTime 이 생겼다는 소문을 들었는데, 이 소문을 얘기해준 사람은 이런 얘기도 해주었습니다. 레일스가 루비 프로그래머의 신사 클럽이라고 말이죠.

1.2.1. Date 클래스

제일 먼저 알아볼 것은 Date입니다. 이 클래스는 루비 응용프로그램에서 저장하고, 다루고, 비교할 날짜를 간단히 노출합니다.

                                         
mydate = Date.new(1999, 6, 4)             → 1999-06-04
mydatejd = Date.jd(2451334)               → 1999-06-04
mydateord = Date.ordinal(1999, 155)       → 1999-06-04
mydatecom = Date.commercial(1999, 22, 5)  → 1999-06-04
Date.jd_to_civil(2451334)                 → [1999,6,4]
Date.jd_to_civil(2451334)                 → [1999,6,4]
mydatep = Date.parse("1999-06-04")        → 1999-06-04

보시다시피, 날짜 인스턴스는 문자로 표현된 방식이 오히려 더 간단합니다. 간단히 날짜를 매개변수로 넘겨서 new 혹은 civil 메소드를 호출하면 됩니다.이 메소드는 익숙한 날짜 형태를 쓰지만 다른 날짜 형태도 지원합니다. jd 메소드는 율리우스력의 날수를 기반으로 하고 있습니다. ordinal 메소드는 Ordinal을 넘겨줄 수도 있고, 연도와 날 수를 넘겨주어 Date 객체를 만들 수 있습니다. commercial 메소드는 Commercial 객체 혹은 연도, 주 수, 날 수를 넘겨주어 Date 객체를 생성할 수 있습니다. commercial_to_jd, jd_to_civil등, 날짜 형태를 바꾸는 메소드가 제공됩니다.

이것들만 있더라도 잘 동작하겠지만, parse 메소드를 이용하는 마지막 예제에 주목하세요. 문자열을 Date 객체로 분석할 수 있습니다. 이것이 new 메소드 다음으로 직관적으로 Date 객체를 만드는 방법입니다.

다양한 클래스 메소드로 입력을 테스트해 볼 수 있고, Date 객체를 얻고나면 모든 종류의 정보를 몇몇 인스턴스 메소드에서 얻을 수 있습니다.

Date.valid_jd?(3829)             → 3829
Date.valid_civil?(1999, 13, 43)  → nil
mydate.mon                       → 6
mydate.yday                      → 155
mydate.day                       → 4

보시다시피 특정 형태의 타당성을 검사할 수 있고, 인스턴스 메소드를 이용하여 다른 형태로 변환할 수 있습니다. 해의 날 수(yday), 월(mon)등 여러 구성 요소들을 얻을 수 있습니다. 기본 연산자를 이용하여 날짜를 비교하고 다룰 수도 있습니다.

date1 = Date.new(1985, 3, 18)
date2 = Date.new(1985, 5, 5)
date1 < date2                  → true
date1 == date2                 → false
date3 = date1
date1 == date3                 → true
date1 << 3                     → 1984-12-18
date2 >> 5                     → 1985-10-05

보시다시피 날짜를 비교하는 것은 표준 숫자 값을 비교하거나 비슷한 것들을 비교하는 것과 같습니다. 날짜끼리 부등호로 대소 비교를 할 수 있는데, 어떤 날보다 더 앞에 오는 날은 "보다 작은" 날이고 어떤 날 보다 더 뒤에 있는 날은 "보다 큰" 날입니다. >> 와 << 연산자를 써서 달을 더하거나 뺄 수 있습니다. 마지막 두 예제를 보세요. 이제 Date 클래스에 대해 익숙해졌으니 Time 클래스로 넘어갑시다.

1.2.2. Time 클래스


타임 클래스는 Date 클래스와 기능이 매우 유사한데, 그냥 날짜가 아니라 시간을 찍는데 더 중점을 두고 있습니다. Date 클래스와 비슷하게 다양한 생성자를 사용할 수 있습니다.

rightnow = Time.new
       →   Sun Sep 10 21:36:15 Eastern Daylight Time 2006
Time.at(934934932)
       →   Tue Aug 17 20:08:52 Eastern Daylight Time 1999
Time.local(2000,"jan",1,20,15,1)
       →   Sat Jan 01 20:15:01 Eastern Standard Time 2000
Time.utc(2006, 05, 21, 5, 13)
       →   Sun May 21 05:13:00 UTC 2006
Time.parse("Tue Jun 13 14:15:01 Eastern Standard Time 2005")
       →   Tue Jun 13 14:15:01 Eastern Daylight Time 2006

보시다시피, 간단히 new 메소드를 호출하거나 now를 호출하면 새 Time 객체를 현재 시간과 시간 영역으로 생성할 수 있습니다. 특정 시간이 필요하면 at을 쓸 수 있는데 이것은 1970년 1월 1일로부터 지나온 초로 운영됩니다. utc와 gm 메소드를 써서 이들 시간 영역과 제공되는 매개변수에 기반한 시간을 생성할 수 있습니다. Date에서 했던 것처럼 parse 메소드르르 써서 Time 객체를 만들 수 있습니다.

Time 클래스는 객체의 값을 얻을 수 있고, 값을 변환할 수 있고, 값을 다른 형태로 출력할 수 있는 몇몇 인스턴스 메소드도 제공합니다.

rightnow = Time.new
       →   Sun Sep 10 21:42:30 Eastern Daylight Time 2006
rightnow.hour
       →   21
rightnow.mon
       →   9
rightnow.day
       →   10
rightnow.to_i
       →   1158543750
rightnow.to_s
       →   Sun Sep 17 21:42:30 Eastern Daylight Time 2006

보시다시피, Time의 메소드들은 Date의 메소드와 매우 비슷합니다. 시간 객체를 Fixnum과 같은 다른 클래스로 바꾸는 것도 가능합니다.

한 인스턴스 메소드를 잠깐동안 집중해서 봅시다. strftime 메소드는 아주 유용한 메소드로 선택한 형태로 시간을 출력할 수 있게 해 줍니다. 이 인터페이스는 C++의 printf와 아주 유사합니다. %f와 같은 구분자를 써서 출력되는 문자열의 값의 위치를 나타낼 수 있습니다. 몇 가지 예제를 보시겠습니다.

rightnow = Time.now
rightnow.strftime("%m/%d/%Y")
       → 09/10/2006
rightnow.strftime("%I:%M%p")
       → 09:13PM
rightnow.strftime("The %dth of %B in '%y")
       → The 17th of September in '06
rightnow.strftime("%x")
       → 09/17/06

strftime 메소드는 Time 모듈에서 가장 복잡한 메소드 중에 하나입니다. 이것으로 더 할수 있는 것에 대한 정보를 더 얻고 싶으시다면 Time 클래스의 문서를 http://www.ruby-doc.org/core/classes/Time.html 에서 찾아보십시오.

1.2.3. Date와 Time

DateTime 클래스는 이전의 두 클래스를 하나의 편리하지만 약간 덜 효율적인 클래스로 만든 것입니다. DateTime 클래스는 Date에 단지 몇 가지 시간 기능이 잘 들어간 서브 클래스입니다. 뭐 확실히 해 두는 것은 좋은 시도입니다만 내게 물어보신다면 쓸데 없는데 시간낭비 하지 말라고 대답할겁니다. 그렇다고 해도 몇 가지 재미있는 기능들이 있습니다.

rightnow = DateTime.now
       → 2006-09-10T21:56:45-0400
maytime = DateTime.civil(2006, 5, 6)
       → 2003-05-06T00:00:00Z
parsed = DateTime.parse("2006-07-03T11:53:02-0400")
       → 2006-07-03T11:53:02-0400
parsed.hour                                  → 11
parsed.day                                   →3
parsed.year                                  → 2006

Date 클래스와 매우 비슷하게 동작한다는 것을 알 수 있습니다. 다양한 날짜 형태를 이용하여 생성하거나 날짜/시간 문자열을 분석할 수 있습니다. Date와 Time 클래스처럼 객체 내부의 다양한 값들을 알아낼 수 있습니다.

이제 아마 머리를 긁적거리시면서 왜 이것을 언제 써야 하는지 궁금히 여기실 것입니다. 개인적으로 DateTime은 쓰지 않고 Time이나 Date를 이용합니다. 때로는 DateTime을 쓰지 않을 수 없는 경우가 있습니다. 그러나 그냥 Date와 Time을 쓰면 DateTime을 쓰는 것보다 800%의 성능 향상이 있을 것이라는 점을 명심하세요. 어떤 경우에는 폴란드 크기 만큼의 성능 차이는 문제가 된답니다.

1.3. 해시와 암호화

가끔 여러분은 사람들이 데이터를 못 보게 하고 싶을 것입니다. 아마 불쾌한 일이 생겨서 다른 사람들이 몰랐으면 할 때도 있을 것입니다. 혹은 잊고 싶은 일은 해시하고 더 이상 이것을 걱정하지 않아도 됩니다. 다행히도 저는 음... 그러니까 제가 말하려는 것은... 루비에는 몇가지 간단한 해시 라이브러리가 포함되어 있고, 또 설치해서 사용할 수 있는 암호화 라이브러리 젬도 있다는 겁니다.

1.3.1. 해시

단방향 암호화를 위하여 해시를 쓴다고 생각해 봅시다. 암호화된 문자열은 데이터 스트림으로부터 오는 것입니다. 전형적인 사용처는 패스워드 체크(예를 들어 여러분이 데이터베이스에 해시를 저장해 두고 사용자의 입력을 해시해서 이 두 해시가 일치하는지를 보는 것입니다)와 파일 체크(같은 두 파일에서 같은 해시값이 나와야 합니다) 입니다. 루비는 두가지 가장 자주 쓰이는 해시 타입인 MD5와 SHA-1을 내장 모듈로 제공합니다.

MD5 MD5는 널리 사용되는 암호화 해시 함수입니다. 1994년 MIT교수인 로널드 라이베스트가 MD4의 후속작으로 개발하였습니다. 취약점 때문에 보안 해시 함수의 주력에서는 떨어져 나왔지만 여전히 값을 대응시키는데 아주 유용합니다. 루비의 MD5 기능은 PHP의 것(예를 들어 md5('your data');) 만큼 쉽지는 않습니다만 그래도 사용하기 편리하고 쉽습니다.

require 'digest/md5'
md5 = Digest::MD5.digest('I have a fine vest!')
         → sXm(1r\371\353\027\367\235u!\266\001\262
md5 = Digest::MD5.hexdigest('I have a fine vest!')
         → 73586d283172f9eb17f79d7521b601b2

MD5 클래스는 해시 다이제스트를 얻기 위한 두 가지 메소드를 제공합니다. 첫 번째 것은 그냥 digest 메소드입니다. 이것은 해시 다이제스트의 조금은 위험한 바이트 스트림을 돌려줍니다. 위험하다고 한 이유는 이것이 제대로 동작한다고 생각하고 XML이나 HTML 혹은 데이터에비스에 쓴다면 안 되기 때문입니다. 이런 상황에서 더 나은 선택은 hexdigest 메소드일 것입니다. 이것은 base64 hex 알고리즘을 통하여 결과를 출력하는데 이것이 더 친근할 것입니다.

SHA-1 SHA-1 해시 알고리즘은 MD5보다 훨씬 더 보안에 강합니다. 이것이 가장 보안에 강한 것은 아닙니다만 대부분의 상항에서 쓸 수 있습니다. 여러 보안이 필요한 상황에서 해시 알고리즘으로 널리 쓰이고 TLS, SSL, PGP, SSH, IPsec 등의 패키지에서 많이 쓰입니다. 루비는 SHA-1 알고리즘을 MD5 알고리즘과 같은 인터페이스로 사용할 수 있게 합니다.

require 'digest/sha1'
sha1 = Digest::SHA1.digest('I have a fine vest!')
          → \225J{{\233\025\236\273\344\003X\233\33              [...]
sha1 = Digest::SHA1.hexdigest('I have a fine vest!')
          → 954a7b7b9b159ebbe403589bdaa8f981003a2fbc

보시다시피 이것이 더 강력한 해시라는 것만 제외하면 MD5의 함수와 동일합니다. 이제 해시를 끝내고 암호화에 대해서 살펴보도록 합시다.

1.3.2. 암호화

루비는 내장으로 암호화 능력을 보유하고 있지는 않습니다. 따라서 젬을 설치해야 합니다. 이것이 루비의 내장 라이브러리가 아니지만 이것은 기술적으로 매우 중요해서 짧게나마 언급을 해야 한다고 생각하빈다. 써드파티의 암호화 라이브러리는 http://crypt.rubyforge.org 에 있고 이것은 순수 루비 암호화 라이브러리입니다. 이것을 gem install crypt 명령으로 설치할 수 있습니다. 루비 설치를 아직 해 보시지 않으셨다면 부록 A에 어떻게 RubyGem을 설치하는지에 대해 나와 있는 곳의 링크가 있습니다.

암호화 라이브러리는 네 가지 암호화 알고리즘이 있습니다. Blogfish, GOST, IDEA, Rijndael이 그것입니다. 다행히도 각 인터페이스는 모두 동일합니다. Blowfish 알고리즘을 문서에 적용하는 예제를 봅시다.

require 'crypt/blowfish'
blowfish = Crypt::Blowfish.new("A key up to 56 bytes long")
plainBlock = "ABCD1234"
encryptedBlock = blowfish.encrypt_block(plainBlock)
       → \267Z\347\331~\344\211\250
decryptedBlock = blowfish.decrypt_block(encryptedBlock)
       → ABCD1234

이것이 가장 쉬운 암호화 라이브러리 중에 하나입니다. 간단히 키를 생성자로 넘겨서 생성하고 encrypt_block 메소드를 호출하여 데이터를 암호화하고 decrypt_block을 통하여 해제합니다. 개발자들이 API를 같은 형태로 유지했기 때문에 다른 알고리즘 이름을 Blowfish 대신에 집어넣기만 하면 동작한다는 것입니다. 그렇지만 서로 다른 키 길이에 대한 제한이 있습니다.그리고 다른 메소드와 함수를 더 이용할 수 있습니다. http://crypt.rubyforge.org/ 에서 더 배울 수 있습니다.

1.4. 유닛 테스팅

테스트 주도 개발이 열정적으로 이루어지고 있는데, 특히 루비 세계에서는 더욱 그렇습니다. 루비 코어 팀이 유닛 검사 프레임워크인 Test::Unit를 언어의 표준 라이브러리로 넣은것만 봐도 알 수 있습니다. 루비의 유닛 테스트 프레임워크는 훌륭하면서도 매우 간편하게 사용할 수 있습니다.

테스트의 기본은 프로그램을 통해서 당신의 코드가 제대로 돌아가지는지 확인하는겁니다. 이것을 바보 같다고 생각하시겠지만 저를 믿으세요. 유닛 테스팅을 사용하다 보면 버그가 두배가까이 늘어나게 됩니다. 그렇다고 이것이, 프로그램이 개발자를 차별한다는 뜻은 아닙니다. 무슨 뜻인지 아시겠지요. 아무도 응용 프로그램이 멈추는 것을 원하지 않기 때문에 무의식적으로 버그가 생길 수 있는 곳에서는 살금살금 걷게 되는 것입니다. 저는 항상 이렇게 합니다. 이것이 제가 검사를 하는 이유입니다.

루비의 유닛 검사 프레임워크는 검사를 하기 위한 간단한 인터페이스를 제공합니다. 로컬 네트워크에서 사용하는 클라이언트 응용 프로그램 중에서 맥 주소를 저장하는 클래스를 검사한다고 합시다.

class MacAddr
  def to_s
    return @mac_parts.join(":")
  end
  def initialize(mac)
    if mac.length < 17
      fail "MAC is too short; make sure colons are in place"
    end
    @mac_parts = mac.split(':')
  end
  def [](index)
    return @mac_parts[index]
  end
end

이것은 메소드 세 개짜리 간단한 클래스입니다. to_s, initialize, 인덱스 메소드로 이루어 져 있습니다. 생성자인 initialize는 맥 주소의 문자열을 받습니다. 만일 문자열의 길이가 잘못됐다면 예외가 던져지고 그렇지 않으면 콜론으로 이것을 나누어서 인스턴스 변수에 넣습니다. to_s 메소드는 배열을 서로 합쳐서 콜론을 붙여서 보냅니다. 인덱스 메소드는 요청된 인덱스로 맥 주소 배열에 접근합니다. 이제 할 일이 생겼으니 검사를 해 봅시다.

Test::Unit로 검사하는 것은 Test::Unit::TestCase를 각 테스트 케이스에서 상속하는 것입니다. 테스트 케이스를 만들고자 한다면 다음과 같이 하면 됩니다.

require 'test/unit'
class TestMac < Test::Unit::TestCase
end

아주 간단하죠? 이제 테스트 케이스 클래스를 만들었습니다. 이제 이것에 검사를 채워야 합니다. 검사는 지저분한 if 문들의 다발로 이루어져 있습니다만 유닛 검사 프레임워크는 단정문(이런 조건문들을 좀 더 의미있게 둘러 수 있게 됩니다)을 통하여 이런 것들을 최대한 없앨 수 있게 해 줍니다. 첫 번째 가정 형태는 등가 검사입니다. 두 개의 등가 가정이 있습니다. assert_equal과 assert_not_equal입니다. 클래스를 만들어놓은 파일안에 이 테스트 코도들을 만들어 봅시다.

require 'test/unit'
class TestMac < Test::Unit::TestCase
  def test_tos
    assert_equal("FF:FF:FF:FF:FF:FF",
        MacAddr.new("FF:FF:FF:FF:FF:FF").to_s)
    assert_not_equal("FF:00:FF:00:FF:FF",
        MacAddr.new("FF:FF:FF:FF:FF:FF").to_s)
  end
end

두 개의 테스트를 만들었습니다. 첫 번째 것은 맥 주소를 주어서 to_s가 제대로 된 값을 반환하는지를 알아봅니다. 두 번째 것은 같은 것이지만 반대의 조건으로 다른 값을 주고 이것이 서로 다른 것을 확인하는 것입니다. 테스트를 해보면 희망대로 성공하는 것을 볼 수 있습니다.

Loaded suite unit_test
Started
.
Finished in 0.0 seconds.
1 tests, 2 assertions, 0 failures, 0 errors

훌륭하게 잘 되었습니다. 이제 다른 형태의 가정을 봅시다. nil 가정입니다. 이 가정은 assert_nil과 assert_not_nil로 이루어져 있고 assert_equal을 할 때와 같은 방법으로 nil에 대 검사를 하게 됩니다. assert_not_nil을 이용한 검사를 해 봅시다.

require 'test/unit'
class TestMac < Test::Unit::TestCase
  def test_tos
    assert_equal("FF:FF:FF:FF:FF:FF",
        MacAddr.new("FF:FF:FF:FF:FF:FF").to_s)
    assert_not_equal("FF:00:FF:00:FF:FF",
        MacAddr.new("FF:FF:FF:FF:FF:FF").to_s)
    assert_not_nil(MacAddr.new("FF:AE:F0:06:05:33"))
  end
end

다시 새로운 검사를 실행해 보고 에러 없이 성공적으로 수행되어야 합니다.

Loaded suite unit_test
Started
.
Finished in 0.0 seconds.
1 tests, 3 assertions, 0 failures, 0 errors

그리고 에러가 없이 수행이 되었습니다. 멋지군요! 이제 단정문의 마지막 형태를 살펴보고 예외를 다루어 보겠습니다. 루비의 검사 프레임워크는 코드 단위의 반환값을 검사 하는 것 뿐만 아니라 예외가 발생하였는지의 여부를 검사할 수도 있습니다. 맥 주소가 올바르지 않으면 예외가 발생하고 이것에 대한 검사를 수행하 것을 작성해 봅시다.

require 'test/unit'
class TestMac < Test::Unit::TestCase
  def test_tos
    assert_equal("FF:FF:FF:FF:FF:FF",
        MacAddr.new("FF:FF:FF:FF:FF:FF").to_s)
    assert_not_equal("FF:00:FF:00:FF:FF",
        MacAddr.new("FF:FF:FF:FF:FF:FF").to_s)
    assert_not_nil(MacAddr.new("FF:AE:F0:06:05:33"))
    assert_raise RuntimeError do
      MacAddr.new("AA:FF:AA:FF:AA:FF:AA:FF:AA")
    end
  end
end

이제 다시 검사를 수행해 보시고, 성공적인 수행이 되어야 할 것입니다.

Loaded suite unit_test
Started
F
Finished in 0.015 seconds.
  1) Failure:
test_tos(TestMac) [test21.rb:27]:
<RuntimeError> exception expected but none was thrown.
1 tests, 4 assertions, 1 failures, 0 errors

이런! 생성자를 보시면 맥 주소가 너무 짧은 것만 검사하게 되어 있습니다. 이제 < 연산자를 != 연산자로 바꾸어서 너무 짧거나 너무 긴 경우를 모두 검사하게 하고 검사를 다시 해 봅시다.

Loaded suite unit_test
Started
.
Finished in 0.0 seconds.
1 tests, 4 assertions, 0 failures, 0 errors

좋아요! 우리 클래스의 조그마한 검사 조를 만들었습니다. 물론 이것은 응용 프로그램 전체에 하나의 클래스가 있는 것이지만, 각 클래스에 검사 조를 둘 수 있습니다. 검사 조가 점점 커지면 여러 개의 파일에 나누고 싶을 것입니다. 그렇지 않으면 1,200개의 검사 상황을 한 파일의 브레이크 댄스 추는 팬더 스크린 세이버에 다 넣어야 하기 때문이죠.다행히도 Test::Unit은 똑똑해서 여러 테스트 파일을 하나의 검사 벌에 넣을 수 있씁니다. 그 말은 다음과 같이 할 수 있다는 뜻입니다.

require 'test/unit'
require 'pandatest'
require 'breakdancetest'
require 'breakdancingpandatest'
require 'somewhatperipheralelementstest'
require 'somewhatperipheralelementsbestfriendsunclestest'

기본적인 검사법을 알려 드렸습니다. 링크 목록을 부록 A에 두어서 검사 구동형의 개발과 루비에서의 검사를 더 깊이 알고 싶은 분들이 참고하시게 했습니다. Test::Unit의 문서를 http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/classes/Test/Unit.html 에서 찾아보시고 쓸 수 있는 다른 가정을 살펴보세요. 자주 쓰지 않는 것은 여기서 다루지 않았으니까요.




sponsored by andamiro
sponsored by cdnetworks
sponsored by HP

Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2011-12-29 10:56:26
Processing time 0.0118 sec