IBM®
메인 컨텐츠로 가기
    Korea [국가변경]    이용약관
 
 
   
        제품    서비스 & 솔루션    고객지원 & 다운로드    회원 서비스    
메인 컨텐츠로 가기

한국 developerWorks  >  Information Management | 오픈 소스  >

DB2와 Ruby on Rails, Part 3: DB2와 Ruby on Rails에서의 테스팅 (한글)

Ruby on Rails 빌트인 테스트 장치와 DB2용 진단 툴

developerWorks
문서 옵션

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.

토론

샘플 코드

영어원문

영어원문


제안 및 의견
피드백

난이도 : 중급

John Chun, DB2 Advanced Support Specialist, IBM 
Alex Pitigoi, Advisory Software Engineer, IBM
Christine Law, DB2 Advanced Support Specialist, IBM 
Naomi Ngan, Senior Software Engineer, Autonomy

2007 년 11 월 06 일

애자일(agile) 애플리케이션 개발에서 중요한 요구 사항은 회귀 테스팅(regression testing)이 보장된 코드의 점증적인 통합입니다. Ruby on Rails 프레임웍은 전보다 더 쉬워지기 위해 노력을 하고 있습니다. Rails는 단위 테스팅 및 함수 테스팅 지원을 하고 있고, 스마트 템플릿을 통해 대부분의 인프라스트럭처, 디렉토리, 테스트 파일, 테스트 케이스를 만들고 있습니다. DB2와 Ruby on Rails 시리즈, 세 번째 글에서는, 두 번째 기술자료에 소개되었던 Team Room 데모 샘플의 정확 속에 Rails 빌트인 테스트 프레임웍을 적용시켜 봅니다.

머리말

DB2와 Ruby on Rails 시리즈 Part 1에서는, IBM_DB Ruby 드라이버, Rails 마이그레이션, Team Room 애플리케이션을 소개했다. Part 2에서는, 기존 Team Room 애플리케이션을 기반으로 Rails 애플리케이션에 DB2® pureXML™ 지원을 활용하는 방법을 배웠다.

소셜 북마크

mar.gar.in mar.gar.in
digg Digg
del.icio.us del.icio.us
Slashdot Slashdot

Team Room 애플리케이션이 발전했고, 더욱 복잡해졌기 때문에, 이제 애플리케이션이 기대한 대로 작동하도록 할 때이다. 일반적으로, 다음 단계는 Team Room 애플리케이션의 테스트 단계이다. 이제는 테스팅이 DB2 on Rails 환경에서 어떻게 작동하는지, 그리고 Ruby on Rails 프레임웍에서 테스트를 작성하는 것이 얼마나 쉬운지를 설명하겠다.

Rails 프레임웍은 내장된 테스팅 지원 덕택에 웹 애플리케이션을 편리하게 테스트 할 수 있다. Rails에서는 핵심 애플리케이션을 구현 및 실행한 후 또는 애플리케이션을 구현하면서, 그리고 애플리케이션을 작성하기도 전에 [Test Driven Development (TDD)] 테스트를 작성할 수 있다.

Team Room 애플리케이션 테스트 하기

새로운 Rails 프로젝트를 만들 때, Rails는 테스트 인프라스트럭처를 생성한다. D:\rails\teamroom\에 있는 Rails Team Room 프로젝트 디렉토리로 가면, \test 하위 디렉토리가 있다. \test 아래, 다섯 개의 기존 디렉토리와 하나의 helper 파일이 있다.


Listing 1. 테스트 디렉토리 콘텐트
                
/unit
/functional
/fixtures
/integration
/mocks
test_helper.rb

모든 테스트를 /test directory에 두고, 특정 테스트를 속성과 기능에 따라 다섯 개의 하위 디렉토리에 배치한다. /test directory 안에 있는 각각의 컴포넌트들은 다음과 같다.

Unit

Rails에서, 모델을 테스트 하기 위해 작성된 테스트를 단위 테스트(unit test)라고 한다. 일반적으로, 모델 당 한 개의 단위 테스트를 작성한다. 하나의 단위 테스트에서, 모델을 나눌 수 있는 모든 것을 테스트 한다. 기본 테스트에는 밸리데이션 코드와 선언(assertion), 그리고 Create, Read, Update, Delete (CRUD) 같은 데이터베이스 연산이 포함되어야 한다.

Functional

컨트롤러를 테스트 하기 위해 작성된 테스트를 함수 테스트(functional test)라고 한다. 단위 테스트 보다 높은 레벨에서 애플리케이션을 테스트 한다. 일반적으로, 각 컨트롤러에 하나의 함수 테스트를 작성한다. 함수 테스트의 예로는 성공적인 웹 요청, 정확한 페이지로의 리다이렉션, 올바른 인증, 특정 액션에 대한 정확한 응답이 포함된다.

Fixtures

픽스처(fixture)는 모델 내의 콘텐트를 지정한다. 하나의 픽스처에 데이터를 지정하고, 단위 테스트를 실행할 때, Rails에게 그 데이터를 로드 할 것을 명령한다. 이는 DB2의 import 또는 load 함수와 비슷하다.

Mocks

Mock은 제어권이 전혀 없는 외부 시스템과의 인터랙션 보다는 Rails 프로젝트의 범위 내에서 중요한 기능들을 테스트하는데 집중할 수 있도록 도와주는 "가짜" 객체이다. 예를 들어, 우리 Team Room 애플리케이션은 새로운 문서가 특정 subject에 추가될 때 이메일로 사용자에게 공지를 보낸다. 우리는 사용자가 제공한 이메일이 유효 이메일 주소라는 것을 확인할 수 있는 이메일 밸리데이션 시스템이 없다. 대신, 이메일 계정 프로바이더나 도메인 서버에서 온 에러 리포트에 의존하여 사용자가 제공한 이메일 주소를 검사한다. 외부 네트워크에 액세스 하지 않고, 이메일 밸리데이션 시스템 없이 Team Room 공지 기능을 테스트 하기 위해서, Mock 테스트 메소드를 작성할 수 있다.

Integration

통합 테스트(integration test)는 다중 컨트롤러와 액션들을 확장하는 테스트이다. 이름에서 시사하듯, 이 테스트는 다른 컨트롤러와 액션들이 함께 작동하는 것을 확인한다. 통합 테스트는 Rails 애플리케이션 내의 다양한 컴포넌트들 간 고급 인터랙션을 다룬다.

test_helper.rb

test_helper.rb 파일은 여러 테스트 케이스에 공통적인 기본 Rails 작동을 만든다. 예를 들어, test_helper.rb에서, RAILS_ENV 환경 변수는 "test"로 설정되어, Rails는 테스팅에 테스트 데이터베이스(database.yml 파일에 설정됨)를 사용한다. 모든 단위 테스트들은 Rails에 test_helper.rb 파일을 로드한다. 커스텀 선언 역시 test_helper.rb에 정의되어, 같은 Rails 애플리케이션 내의 다중 테스트들이 이러한 커스텀 선언들을 공유하고 사용할 수 있도록 한다.

다양한 데이터베이스 환경 설정하기

DB2와 Ruby on Rails 시리즈 Part 1에서는, DB2 XMLDB 개발 데이터베이스로 연결하기 위해 D:\rails\teamroom\config\database.yml 파일을 편집했다. YAML 파일의 "development" 섹션 아래, "test"와 "production"이라고 하는 두 개의 추가 섹션들이 있다. 이 부분을 명확히 채우지 않았다. 이름에서 시사하듯, "test" 섹션에서는 테스트 데이터베이스 환경을 설정하고, "production" 섹션에서는 실행 데이터베이스 환경을 설정한다. 실제로, 개발이나 테스트 환경은 같은 데이터베이스를 가리키지만, 실행 환경은 개발 또는 테스트 데이터베이스와 반드시 분리되어야 한다. 이제, TESTDB 테스트 데이터베이스를 생성하여 database.yml 파일에 테스트 환경을 설정해 보자.

TESTDB 데이터베이스 생성하기:

db2 create db testdb using codeset utf-8 territory us

다음과 같이 database.yml을 편집한다.


Listing 2. database.yml 파일
                
# IBM DB2 Database configuration file
#
# Install the IBM DB2 driver and get assistance from:
# http://www.alphaworks.ibm.com/tech/db2onrails
development:
  adapter:      ibm_db
  database:     xmldb
  username:     user
  password:     secret
  schema:       teamroom
  application:  TeamRoom
  account:      devuser
  workstation:  devbox
# == remote TCP/IP connection (required when no local database catalog entry available)
# host:         bigserver     // fully qualified hostname or IP address
# port:         50000         // data server TCP/IP port number

test:
  adapter:      ibm_db
  database:     testdb
  username:     testuser
  password:     secret
  schema:       teamroom
  application:  TeamRoom
  account:      testuser
  workstation:  testbox

production:
  adapter:      ibm_db
  database:     proddb
  username:     produser
  password:     secret
  schema:       teamroom
  application:  TeamRoom
  account:      produser
  workstation:  prodbox

테스팅을 위해서, 테스트 데이터베이스가 개발 데이터베이스와 같은 구조를 갖고 있다는 것을 확인해야 한다. 스키마를 로딩하기 위해 DDL 스크립트를 관리하는 대신, Rake 명령어를 사용하여 테스트 환경을 생성한다. rake --tasks 명령어를 실행하면 테스트 데이터베이스를 구현 또는 비우는 관련 명령어들이 보인다.


Listing 3. Rake --tasks 아웃풋
                
rake db:test:clone             # Recreate the test database from the current 
                               # environment's  database schema
rake db:test:clone_structure   # Recreate the test databases from the development 
                               # structure
rake db:test:prepare           # Prepare the test database and load the schema
rake db:test:purge             # Empty the test database

주 1: DB2에서 rake db:test:* 태스크를 실행시키려면, http://db2onrails.com/에 설명된 단계를 참조하여 schema_dumper.rbdatabases.rake를 수정 및 패치한다.

구 파일들을 백업하고 다음 디렉토리에 제공된 두 개의 새로운 파일들로 대체한다.

<ruby_path>\lib\ruby\gems\1.8\gems\activerecord-1.15.3\lib\active_record\schema_dumper.rb

<ruby_path>\lib\ruby\gems\1.8\gems\rails-1.2.3\lib\tasks\databases.rake

rake db:test:prepare 명령어를 사용하여 데이터베이스에 테이블을 구현한다. 다음 명령어를 실행하면 개발 데이터베이스에서 온 스키마가 테스트 데이터베이스에 중복된다.


Listing 4. rake db:test:prepare
                
D:\rails\teamroom> rake db:test:prepare
(in D:\rails\teamroom)

testdb 데이터베이스로 연결하여 모든 테이블들이 잘 만들어졌는지를 확인한다.


Listing 5. testdb의 테이블
                
D:\rails\teamroom>db2 list tables for schema teamroom

Table/View                      Schema          Type  Creation time
------------------------------- --------------- ----- --------------------------
DOCUMENTS                       TEAMROOM        T     2007-06-05-11.21.46.343002
SCHEMA_INFO                     TEAMROOM        T     2007-06-05-11.21.48.306001
SUBJECTS                        TEAMROOM        T     2007-06-05-11.21.46.633003
SUBJECTS_SUBSCRIPTIONS          TEAMROOM        T     2007-06-05-11.21.47.004000
SUBSCRIPTIONS                   TEAMROOM        T     2007-06-05-11.21.47.194005
USERS                           TEAMROOM        T     2007-06-05-11.21.47.595003
XML_CONTENTS                    TEAMROOM        T     2007-06-05-11.21.47.955001

  
  7 record(s) selected.

테스트 데이터베이스에 모든 테이블들이 정의되었다면, 테스트 데이터를 로딩할 수 있다. SQL insert 또는 import를 통해 직접 수행할 수 있지만, 텍스트 픽스처(text fixture)를 사용하기로 한다.

Ruby의 기본 단위 테스트

주 2: 클래스 선언에서 "<" 부호는 이 부호의 왼편에 선언된 클래스가 오른편에 지정된 클래스의 하위 클래스라는 것을 나타낸다.

Ruby(1.8.1 이후 버전)에 있는 핵심 라이브러리들 중 하나는 test/unit이다. Ruby 테스트 스크립트를 작성하려면,

  1. .rb 파일에 "require 'test/unit'"을 지정한다. 이 문으로 Test::Unit 모듈을 로딩한다.
  2. require 문 다음에, 이 클래스는 Test::Unit::TestCase의 하위 클래스라는 것을 명시하는 클래스 선언이 추가되어야 한다. (Listing 6)

Listing 6. 단위 테스트에 "require 'test/unit'" 지정하기
                
require 'test/unit'

class SubTestCase < Test::Unit::TestCase
  ...
end

Team Room 애플리케이션 경로의 test\unit 하위 디렉토리에서, 스카폴드를 통해 생성된 모든 모델 객체들에 상응하는 .rb 파일들을 찾을 수 있다. 이 파일들은 객체 이름에 "_test"를 붙인다.

test\unit 하위 디렉토리 아래 테스트 스크립트를 생성할 때, require 'test/unit' 문은 지정될 필요가 없다. 기존 require 문에서 참조된 test_helper.rb 파일에 이미 필요한 정보가 포함되어 있다.


Listing 7. test\unit 경로 리스팅
                
D:\rails\teamroom\test\unit>dir
 Volume in drive D has no label.
 Volume Serial Number is C8D3-5819

 Directory of D:\rails\teamroom\test\unit

06/05/2007  09:27 AM    <DIR>          .
06/05/2007  09:27 AM    <DIR>          ..
06/05/2007  09:27 AM               208 customer_info_test.rb
05/31/2007  07:42 PM               199 document_test.rb
06/02/2007  05:14 PM               251 subjects_subscription_test.rb
05/31/2007  07:42 PM               315 subject_test.rb
05/31/2007  07:42 PM             1,211 subscription_mailer_test.rb
05/31/2007  07:42 PM               207 subscription_test.rb
05/31/2007  07:42 PM               191 user_test.rb
05/31/2007  07:42 PM               204 xml_content_test.rb
               8 File(s)          2,786 bytes
               2 Dir(s)  15,359,959,040 bytes free

"test_" 접두사를 가진 메소드

Ruby의 모든 테스트 메소드 이름들은 "test"로 시작해야 한다. 그렇지 않으면, 테스팅 프레임웍이 이를 테스트로 인식하지 못한다. "test" 접두사가 없다면, 일반 Ruby 메소드로 취급되고, 테스팅 프레임웍은 이를 자동으로 실행하지 못할 것이다.


Listing 8. 간단한 단위 테스트
                
require File.dirname(__FILE__) + '/../test_helper'

class Sub_Test < Test::Unit::TestCase
  def test_truth
    assert true
  end
end

이제 파일을 sub_test.rb로 저장한다. 이것이 실행되면, 다음과 같은 결과가 나온다.


Listing 9. sub_test.rb 실행하기
                
D:\rails\teamroom\test\unit>ruby sub_test.rb
Loaded suite sub_test
Started
.
Finished in 0.631 seconds.

1 tests, 1 assertions, 0 failures, 0 errors

주 3: 위 테스트의 결과는 "."이다. Rails에서, "."가 의미하는 것은 통과(pass) 또는 성공이다. "F"는 실패(failure)를 의미하고(예상하지 못한 결과로 인한 실패한 선언 때문에 생김), "E"는 에러(error)를 뜻한다. (일반적으로 Ruby 문제)

위와 같은 간단한 테스트 시나리오에서는 한 개의 선언을 수행했고, "assert true" 문으로 인해 성공적인 시나리오가 되었다. Assertion은 테스트이고, Rails 애플리케이션 테스팅에 사용할 수 있는 수 많은 assert 문이 있다.

Rails에서, script/generate 모델, script/generate 컨트롤러, script/generate 스카폴드를 사용하는 모든 모델과 컨트롤러는 테스트 하위 디렉토리에 상응하는 테스트 스텁을 만든다. 이러한 테스트 스텁을 사용할 때, "require 'test/unit'"은 지정될 필요가 없다.

Assertions

Assertion은 사전 조건들을 확인하고, 애플리케이션이나 모듈에 대한 조건을 붙여서 유효 상태임을 확인한다. Test::Unit assertion은 기본 패턴을 따르는데, 첫 번째 매개변수는 예상 결과이고, 두 번째 매개변수는 실제 결과이다. 기대치와 실제 값이 매치하지 않으면, 무엇이 잘못되었는지를 설명하는 메시지가 만들어진다.

중요한 Ruby Assertion 리스트는 Test::Unit::Assertions를 참조하라.

Rails 개발자들이 사용할 수 있는 많은 Assertion들이 있다. 자세한 내용은 Module Test::Unit::Assertions Information을 참조하라.

Assertion은 앞서 언급한 것처럼, 유효 상태임을 입증하기 위해 사용된다. 그렇다면, 무엇이 유효 상태를 정의하는가?

대부분, 유효 상태는 우리 코드의 밸리데이션(validation)에 의해 제어된다.

밸리데이션 사용하기

인풋 데이터는 데이터베이스에 저장되기도 전에 확인될 수 있다. 이렇게 하려면, 커스텀 밸리데이션을 정의하거나, 표준 밸리데이션을 사용할 수 있다. 애트리뷰트의 이름, 길이, 포맷을 검사하는 것 같은 표준 밸리데이션의 경우, Active Record에서 제공하는 밸리데이션을 사용할 수 있다.

주 4: 모든 Active Record 밸리데이션 메소드 이름은 "validates_"로 시작하고, 한 개 이상의 애트리뷰트 이름 또는 한 개 이상의 밸리데이션 옵션을 취한다.

모델에 밸리데이션 메소드를 추가하면 객체가 데이터베이스에 저장되기 전에 밸리데이션이 발생한다. 밸리데이션이 통과하면, 객체는 데이터베이스에 작성된다. 밸리데이션이 실패하면, 실패와 관련한 에러 메시지가 에러 리스트에 추가된다. 이 리스트를 참고하여 사용자는 문제 해결을 위해 적절한 조치를 취한다.

이 글에서는 Active Record의 밸리데이터 세트를 사용하여 모델의 유효성 검사를 수행하는 방법을 예제를 통해 설명하겠다.

우리 Team Room 예제의 경우, 한 subject에 대해 다음과 같은 밸리데이션을 수행해야 한다고 가정해 보자.

  • subject에 대한 모든 제휴된 문서 객체들이 유효 객체이다.
  • subject 이름이 비어있지 않다.
  • subject 이름이 20문자가 넘지 않는다.

위 사항을 검사하기 위해, 표준 밸리데이션 메소드를 사용한다. 다음과 같은 밸리데이션을 app\models\subject.rb에 추가한다.


Listing 10. app\models\subject.rb의 밸리데이션
                
  validates_associated      :documents
  validates_associated      :subscriptions				
  validates_presence_of     :name
  validates_length_of       :name,
                            :maximum => 20,   # maximum 20 characters
                            :too_long => "is too long, maximum 20 characters"
  validates_numericality_of :size,            # value is numeric and only integer accepted
                            :only_integer => true,
                            :greater_than => 0                             
  validates_presence_of     :description
  validates_length_of       :tag,
                            :maximum => 10,   # maximum 10 characters
                            :too_long => "is too long, maximum 10 characters"   

"validates_associated" 메소드는 subject와 관련한 모든 문서들이 유효한지를 검사한다. "validates_presence_of" 메소드는 subject 이름이 비어있지 않은지를 검사한다.

subject의 이름 길이가 20문자를 넘지 않는지를 검사하기 위해, "validates_length_of" 메소드를 사용한다. "maximum" 설정 옵션을 사용하여 subject 이름의 최대 사이즈를 나타낸다. "too_long" 옵션은 subject 이름이 20 문자를 초과했을 때 생성되는 에러 메시지이고, "message" 옵션 대신에 사용된다. 기본적으로, "too_long" 메시지는 "is too long (maximum is %d characters)"을 의미한다.

주 5: validates_size_of 메소드는 validates_length_of 메소드와 같은 의미이다.

subject 인풋 데이터는 규칙 세트에 따라서 유효성이 검사된다. 사용자가 문제가 있는 데이터를 입력하면, 에러 메시지 리스트가 디스플레이 되어, 사용자가 인풋 데이터를 정정할 수 있도록 해야 한다.

ActionView 메소드가 도움이 된다.

  • error_message_on - 객체의 지정된 애트리뷰트에 대한 에러 스트링을 리턴한다.
  • error_messages_for - 지정된 객체에 대한 에러 리스트를 리턴한다.

이 메소드에 대한 자세한 내용은 여기를 참조하라.

우리 Team Room 예제에서, 새로운 subject에 입력된 모든 인풋 데이터 에러 메시지를 리스팅 하려면, app\views\subjects\_form.rhtml에 다음을 추가할 수 있다.


Listing 11. 인풋 데이터 에러 메시지
                
<%= error_messages_for 'subject' %>

이 메소드는 호출은 모든 에러 메시지들과 함께 div를 리턴하기 때문에, 쉽게 디스플레이 될 수 있다.


그림 1. 너무 긴 subject 이름을 입력했을 때의 에러 메시지
에러 메시지

클래스 범위에 정의될 수 있는 Active Record 모델 밸리데이션 리스트는 여기를 참조하라.

지금까지 subject 모델 밸리데이션을 수행했으니, Assertion을 사용하여 위 밸리데이션을 실제로 테스트 할 수 있다.

Assertion 사용하기

Assertion을 사용하여 SUBJECTS 테이블과 관련된 모델들을 테스트 할 수 있다. SUBJECTS 테이블과 제휴된 모델들 중 하나는 이 테이블이 유효 길이를 가진 필수 정보로 채워진다는 것을 확인한다. 이러한 밸리데이션은 백엔드 데이터베이스에 불필요한 부담을 주지 않도록 하기 때문에 중요하다. 테스트 될 밸리데이션은 \app\models\subject.rb에 나타나 있다. (Listing 10).

"test_invalid_with_empty_values" 메소드는 간단한 예로서, 빈 값을 가진 에러 조건을 선언하는 것을 나타내고 있다. "test_invalid_with_empty_values" 메소드로는, 빈 subject 값이 에러가 되는지 여부를 테스트 한다.


Listing 12. "test_invalid_with_empty_values" 테스트 메소드
                
require File.dirname(__FILE__) + '/../test_helper'

class SubjectTest < Test::Unit::TestCase

# Tests validation failure using empty values
  def test_invalid_with_empty_values
    subject = Subject.new
    assert !subject.valid?
    assert subject.errors.invalid?(:name)
    assert subject.errors.invalid?(:size)
    assert subject.errors.invalid?(:description)
  end
  ...

이제 유효 subject 데이터를 선언하는 두 번째 테스트와, 부정확한 TAG 길이를 지정할 때 예상 에러 메시지가 생기는지를 확인하는 세 번째 테스트를 추가해 보자.

다음 세 개의 테스트 메소드는 이제 test/unit/subject_test.rb에 있어야 한다.


Listing 13. subject_test.rb용 테스트 메소드
                
require File.dirname(__FILE__) + '/../test_helper'

class SubjectTest < Test::Unit::TestCase

# Tests validation failure using empty values
  def test_invalid_with_empty_values
    subject = Subject.new
    assert !subject.valid?
    assert subject.errors.invalid?(:name)
    assert subject.errors.invalid?(:size)
    assert subject.errors.invalid?(:description)
  end
  
# Tests validation using valid data
  def test_valid_subject
    subject = Subject.new(:name=>"Perl",
                          :size=>2,
                          :description=>"test",
                          :tag=>"123456789")
    assert subject.valid?
  end

# Tests invalid TAG length  
  def test_invalid_tag_length
    subject = Subject.new(:name=>"AJAX",
                          :size=>3,
                          :description=>"test",
                          :tag=>"abcdefghijklm")
    assert !subject.valid?
    assert_equal "is too long, maximum 10 characters",subject.errors.on(:tag)
  end 
end

위 단위 테스트의 성공적인 결과는 다음과 같다.


Listing 14. subject 단위 테스트 실행하기
                
D:\rails\teamroom>ruby test/unit/subject_test.rb
Loaded suite subject_test
Started
...
Finished in 13.891 seconds.

3 tests, 7 assertions, 0 failures, 0 errors

주 6: SubjectTest는TestCase인데, 이것은 Test::Unit::TestCase에서 상속을 받은 클래스이고, 한 개 이상의 테스트를 가질 수 있다. 테스트 케이스용 컬렉션은 TestSuite라고 한다. TestSuite에는 많은 Assertion들이 포함된다.

이 아웃풋에 있는 세 개의 모든 테스트들은 subject_test.rb 파일에 정의된 세 개의 테스트 메소드를 의미하고, 세 개의 Assertion은 파일에 있는 모든 assert 문을 의미한다. 주 3에서 언급했듯이, "."은 성공적인 결과를 의미한다.

위 subject_test.rb 예제에는 Listing 15에 있는 일곱 개의 Assertion이 포함되어 있다.


Listing 15. subject_test.rb의 Assertion
                
assert !subject.valid?
assert subject.errors.invalid?(:name)
assert subject.errors.invalid?(:size)
assert subject.errors.invalid?(:description)

assert subject.valid?

assert !subject.valid?
assert_equal "is too long, maximum 10 characters",subject.errors.on(:tag)

subject_test.rb에는 세 개의 테스트가 있다. (Listing 16)


Listing 16. subject_test.rb의 테스트 메소드
                
test_invalid_with_empty_values
test_valid_subject
test_invalid_tag_length

픽스처를 이용한 테스팅

브라우저에 직접 값을 입력하여 Rails 애플리케이션을 테스트 하는 대신, 테스트 픽스처를 사용하여 샘플 데이터를 데이터베이스 테이블에 로딩한다. 테스트를 실행하기 전에, 픽스처를 사용하여 데이터베이스를 사전 정의된 데이터 값으로 채울 수 있다. 픽스처는 두 개의 다른 포맷으로 되어 있다. YAML과 콤마 분리형 값(CSV) 포맷이다. 픽스처는 데이터베이스에 독립적이기 때문에 같은 픽스처를 사용하여 다른 데이터베이스 시스템에 대해 Rails 애플리케이션을 테스트 할 수 있다.

픽스처를 사용하여 Team Room 애플리케이션에서 테스트 subject와 하위 subject를 테스트 할 것이다. 우리 테스트에는 YAML 픽스처를 사용한다. /test/fixtures 디렉토리에 있는 subjects.yml 파일을 편집한다. 상응하는 subject 단위 테스트를 생성할 때, 비어있는 subjects.yml 파일이 Rails에서 생성된다. ruby script/generate model을 실행하여 새로운 모델을 만들거나, ruby script/generate scaffold를 사용하여 스카폴드를 생성할 때, 픽스처 스텁이 자동으로 생성되어 /test/fixtures 디렉토리에 놓인다.

주 7: 픽스처 파일의 이름은 데이터베이스에 있는 테이블 이름과 매치되어야 한다. 따라서, 데이터를 Team Room의 SUBJECTS 테이블로 로딩하려면, subject 픽스처 파일 이름은 subjects.yml이 되어야 한다.

주 8: 각 YAML 픽스처 파일에는 싱글 모델용 데이터가 포함된다.

subjects.yml을 편집하여, 여기에 24개의 다른 subject를 채웠다. 본문 다운로드 섹션에서 제공하는 샘플 코드에 /test/fixtures/subject.yml이 있다. 각 픽스처는 이름이 있고, 콜론으로 분리된 키-값 쌍의 리스트가 뒤따른다.

subjects.yml의 샘플 콘텐트:


Listing 17. subjects.yml의 샘플 콘텐트
                
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
photography:
  id: 1
  name: Photography
  size: 0
  description: Digital Photos, images
  tag: portraits

gardening:
  id: 2
  name: Gardening
  size: 0
  description: Gardening info
  tag: flowers

golf:   
  id: 3
  name: Golf
  size: 0
  description: Golf information, golf club, golf course discount available to members
  tag: golf

tennis:
  id: 4
  name: Tennis
  size: 0
  description: Tennis information, tennis club, tennis court bookings
  tag: tennis 

<etc ..>

Rails 픽스처 로딩 메커니즘은 이미 자리를 잡았기 때문에, 테스트 데이터를 로딩할 것을 Rails에게 명령하는 코드를 추가할 필요가 없다. Listing 13에서 /test/unit/subject_test.rb를 수정하여 코드를 추가하여 픽스처로 테스트 한다.


Listing 18. /test/unit/subject_test.rb의 픽스처 관련 엔트리
                
require File.dirname(__FILE__) + '/../test_helper'

class SubjectTest < Test::Unit::TestCase
  fixtures :subjects

  # count the number of subject fixtures
  def test_subject_fixtures
    assert_equal 24, Subject.count
  end
  ...  
  # Previous test methods and assertions are listed below
  ...
end

fixtures 메소드는 테스트 케이스의 각 테스트 메소드의 시작 시 해당 모델 이름에 상응하는 픽스처를 자동으로 로딩한다. 기본적으로, :subjects는 Rails에게 subjects.yml 파일에서 샘플 데이터를 로딩할 것을 명령한다. 그리고 나서, "test_subject_fixtures" 메소드를 추가하여 24개의 모든 subject가 올바르게 로딩되었는지를 테스트 한다.

테스트를 실행하려면, ruby test/unit/subject_test.rb를 실행한다. 결과는 다음과 같다.


Listing 19. 픽스처를 사용한 subject 단위 테스트 결과
                
D:\rails\teamroom>ruby test/unit/subject_test.rb
Loaded suite test/unit/subject_test
Started
....
Finished in 3.15 seconds.

4 tests, 8 assertions, 0 failures, 0 errors

WEBrick 서버를 시작했고 등록된 사용자로서 로그인 했다고 가정하고, 이제 http://localhost:3000/subjects/list로 가보자. 24개의 subject들이 subscription에 사용되고 있다는 것을 볼 수 있다.


그림 2. subject 항목들
subject 항목들

가끔, ERb (embedded Ruby)와 함께 동적인 픽스처를 사용하여 픽스처 데이터를 만들어야 할 때도 있다. 가장 일반적인 예가 테스트를 실행할 때 실제 타임스탬프를 생성하는 것이다. <% ... %>를 사용하면 Ruby 코드를 실행할 수 있고, <%= ... %>를 사용하면 Ruby 코드를 실행하면서, 결과를 디스플레이 할 수 있다. 다음 섹션에서 함수 테스트와 성능 테스트를 수행하는 방법을 설명하면서 동적인 픽스처와 ERb를 사용하는 방법도 함께 설명하겠다.

픽스처 공유와 다대다(many-to-many) 제휴 테스트 하기

한 단계 더 나아가서, 픽스처를 사용하여 subject와 subscription들 간 제휴를 테스트 할 수 있다. 물론, http://localhost:3000/subjects/list로 가서, 현재 로그인 한 사용자로서 등록하고자 하는 주제들을 검색할 수 있고, Team Room subject-subscription 제휴를 테스트 할 수 있다. 하지만, 이 태스크는 많은 주제들을 포함하고 있는 많은 사용자 subscription을 테스트 할 경우 매우 지루해 질 수 있다. 많은 사용자 subscription을 직접 생성하고, 각각의 사용자로서 로그인 하여, 각 사용자가 등록하기 원하는 주제를 검색해야 한다는 것을 의미한다.

모델들 간 다대다 관계를 테스트 하는 보다 효율적인 방법은 연합 테이블(join table)에 데이터를 포함하고 있는 새로운 픽스처를 만드는 것이다. Part 2를 기억해 보면, SUBJECTS_SUBSCRIPTIONS 테이블을 만들어서 SUBJECTS와 SUBSCRIPTIONS 테이블 간 다대다 관계를 나타냈고, 데이터베이스가 정상화 되었음을 확인했다. /test/fixtures/subjects_subscriptions.yml 파일을 편집하여 SUBJECTS_SUBSCRIPTIONS 테이블에 데이터를 채운다.


Listing 20. subjects_subscriptions.yml
                
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
# Refer to /test/fixtures/subjects.yml for more information
# Subject ID 2 = Gardening
subscription160_gardening:
  subscription_id: 160
  subject_id: 2
 
# Subject ID 3 = Golf  
subscription160_golf:
  subscription_id: 160
  subject_id: 3

위 리스팅은 subscription 아이디가 160인 사용자가 두 개의 subject, Gardening과 Golf에 등록했다는 것을 의미한다.

subject와 subscription 제휴를 테스트 하는 것은 SUBJECTS와 SUBSCRIPTIONS 테이블은 물론, 연합 테이블 SUBJECTS_SUBCRIPTIONS에 있는 데이터에 액세스 해야 한다는 것을 의미한다. 따라서, /test/unit/subjects_subscription_test.rb 테스트에서, 세 개의 모든 픽스처를 로딩해야 한다.


Listing 21. 픽스처
                
class SubjectsSubscriptionTest < Test::Unit::TestCase
   fixtures :subjects_subscriptions, :subjects, :subscriptions
   [...]
end   

여러 테스트 케이스들간 테스트 데이터를 공유할 때 이 픽스처는 매우 강력한 힘을 발휘한다. 픽스처에 대한 자세한 정보는 Ruby on Rails 문서의 Class Fixtures를 참조하라.

Mock 객체 사용하기

외부 시스템으로 액세스 할 걱정 없이 애플리케이션의 핵심 기능을 테스트 해야 할 때 Mock이 유용하다. 사용자가 Team Room에 등록할 때 이메일 주소가 올바른 포맷으로 되었는지를 확인하는데도, 제공되는 이메일 주소가 존재하는 지 여부에 대해 알지 못한다. 사용자 이메일이 유효한 것이라는 것을 확인할 수 있는 유일한 방법은 제공된 이메일 주소로 이메일을 보내는 것이다. 이메일 주소가 유효하지 않거나 존재하지 않으면, 이메일 공급자로부터 에러를 받게 된다. 이러한 등록 실패 공지를 전달하는 시나리오를 테스트 하기 위해 Mock을 사용할 수 있다.

예를 들어, 테스팅 환경에서 이메일 딜리버리 방식을 모방할 수 있다. 모방하고자 하는 딜리버리 방식을 정의한 /test/mocks/test 디렉토리에 email_provider.rb 파일을 만든다. Mock 파일이 만들어 지면, Rails는 /test/mocks/test/email_provider.rb를 로딩한 다음, /app/models/email_provider.rb를 확인한다.

주 9: Mock 파일은 /app/models 디렉토리에 있는 모델과 정확히 같은 이름을 갖고 있어야 한다. 여러분은 Mock 파일에서 모방하고자 하는 메소드만 재 정의하면 되고, 상응하는 Mock 파일에 모든 기존 메소드를 정의할 필요가 없다.

/test/mocks/test/email_provider.rb 는 다음과 같다.


Listing 22. 픽스처
                
require 'models/email_provider'
 
class EmailProvider
  def deliver(request)
    #Mock method
    :success
  end
end

Mock을 사용하면 접근 불가능한 외부 리소스에 대한 걱정 없이 Team Room 애플리케이션에 있는 핵심 기능을 테스트 할 수 있다.

함수 테스트: 컨트롤러 테스팅

Rails 애플리케이션 컨트롤러를 테스트 하는 목적은 웹 브라우저에서 오는 모든 요청들이 사용자 인풋에 기반하여 기대한 대로 응답을 받는지를 확인하면서, 적절한 상태 변화를 만들어 낸다. 하나의 컨트롤러에 있는 각 액션의 경우, 최소한 지정 경로(URL)을 실행하는 한 개 이상의 테스트 케이스가 있어야 하고, 때때로 한 개 이상의 테스트가 다른 상태들을 다루어야 한다. (예를 들어, 액션에 액세스 하면서, 사용자 인증 하기)

함수 테스팅 특성상, UsersController의 케이스가 로그인과 등록 액션에 포함된 것으로 간주한다. 제휴된 UsersControllerTest는 모델과 컨트롤러 클래스와 함께 만들어졌고, 여기에는 이미 실행 경로를 핸들하는 최소한의 커스터마이징만 기대하는 setup과 stub 메소드용 템플릿이 포함되어 있다.


Listing 23. 사용자 컨트롤러 테스트
                
require File.dirname(__FILE__) + '/../test_helper'
require 'users_controller'

# Re-raise errors caught by the controller.
class UsersController; def rescue_action(e) raise e end; end

class UsersControllerTest < Test::Unit::TestCase
  fixtures :users

  def setup
    @controller = UsersController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new

    @first_id = users(:first).id
  end
  [...]
end

필수 메소드인 setup은 함수 테스트에 언제나 필요한 세 개의 인스턴스 변수를 실행한다. 테스트 될 controller, 사용자 인풋과 HTTP 헤더 정보를 포함하고 있는 request, 템플릿에서 실행되는 데이터와 상태 코드를 압축한 response이다.

컨트롤러 테스트의 핵심적인 측면은 시나리오에 테스트 데이터를 제공하는 제휴 픽스처이고, 이러한 특정 컨트롤러를 통해 픽스처에서 동적인 측면들의 사용법을 이해할 수 있다. 위 테스트 케이스에서, 인증된 사용자를 구성할 때, 암호화 된 패스워드를 연산하기 위해 USERS 테이블을 유효 엔트리로 채울 필요가 없다. 암호화 된 패스워드가 개별적으로 계산될 수 있고 픽스처에서 직접 사용될 수 있지만, 뷰와 파셜에 사용된 템플릿 대체물도 비슷하게 작동하면서, 태스크를 단순화 한다.


Listing 24. 사용자 픽스처
                
<% SALT = "tstsalt" unless defined?(SALT) >
first:
  id: 141
  usertype:  usr
  firstname: Homer
  lastname:  Simpson
  extension: '9955'
  email:     homer@simpson.org
  userid:    homers
  salt: <%= SALT %>
  hash_passwd: <%= User.encrypt('secret' , SALT) %> 

초기 test_index 테스트 케이스는 사용자가 인증될 때 보다 정밀한 요청 핸들링을 수행하는 두 개의 다른 테스트 케이스로 나뉘어야 한다.


Listing 25. Users 컨트롤러에서의 테스트 인덱스 액션
                
  def test_index_without_user
    get :index
    assert_redirected_to :action => "login"
    assert_equal "Please sign-in" , flash[:notice]
  end

  def test_index_with_user
    get :index, {}, {:user_id => users(:first).id}
    assert_response 302
  end

사용자가 아직 인증을 받지 못했다면 로그인 페이지로 머물게 될 컨트롤러용 인덱스 액션을 실행하기 위해, 테스트 케이스는 브라우저에서 GET 요청을 시뮬레이트 해야 한다. HTTP 요청을 생성하려면, ActionController::Integration::Session에 의해 정의된 get 메소드는 URL, 액션과 함께 전달된 HTTP 매개변수를 나타내는 해시(위 경우 비어있음), 세션을 채우는 매개변수들을 취한다. 이제, 특정 테스트 케이스를 호출하기만 하면 된다.


Listing 26. 사용자 컨트롤러 테스트 실행하기
                
D:\rails\teamroom>ruby test\functional\users_controller_test.rb -n test_index_with_user
Loaded suite test/functional/users_controller_test
Started
.
Finished in 0.719 seconds.
1 tests, 1 assertions, 0 failures, 0 errors

이메일 공지 및 섭스크립션 확인 테스트

이메일 공지 테스팅에는 단위 테스팅과 생성된 이메일 콘텐트 확인뿐만 아니라, 이메일 공지가 예상대로 보내지는지를 확인하는 함수 테스팅이 포함되어 있다. Action Mailer에 기반하여 Part 2에 구현된 이메일 공지 컨트롤러에 기반하여, 우리는 스텁 함수 테스트 SubscriptionMailerTest를 활용하고 공지 액션에 개입된 모델을 위해 생성된 픽스처를 활용해야 한다.

단위 테스팅 이메일 공지와 섭스크립션 확인의 경우, 메일러 테스트 드라이브는 로컬 이메일 딜리버리용 ActionMailer를 설정해야 하고(delivery_method = :test), 모델 픽스처를 사용하여 공지와 확인을 호출하는데 필요한 객체를 설정해야 한다.


Listing 27. 메일러 함수 테스트 설정
                
class SubscriptionMailerTest < Test::Unit::TestCase
  FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures'
  CHARSET = "utf-8"

  include ActionMailer::Quoting
  fixtures :users
  fixtures :subscriptions
  fixtures :subjects
  fixtures :documents

  def setup
    ActionMailer::Base.delivery_method = :test
    ActionMailer::Base.perform_deliveries = true
    ActionMailer::Base.deliveries = []

    @user = users(:first)
    @subscription = subscriptions(:one)
    @doc  = documents(:first)
    @subject = subjects(:ruby_on_rails)
    @subject.documents << @doc
    @subject.subscriptions << @subscription
  end
  [...]
end

테스트 구현은 메일러 클래스를 사용하여 이메일 콘텐트를 생성(create_notify)하지만, 실제로 SMTP 서버로 보내지 않고, 선언 사용을 통해서, 특정 바디 섹션을 포함한 메시지의 부분들(수신자, 송신자, 타임스탬프 등)을 확인한다.


Listing 28. 공지 이메일 테스트
                
  def test_notify
    response = SubscriptionMailer.create_notify(@doc)

    assert_equal("TeamRoom new shared document notification", response.subject)
    assert_equal("teamroom@developerWorks.ibm.com", response.from[0])
    assert_equal(nil, response.to)
    assert_equal("homer@simpson.org", response.bcc[0])
    assert_in_delta(Time.now, response.date, 1.0)
    assert_match(/A new document DB2onRails-logo.gif/, response.body)
    assert_match(/subject: Ruby on Rails/, response.body)
  end

비슷한 방식이 메시지의 콘텐트를 생성한 다음 확인하면서 섭스크립션 확인 이메일을 테스트 하는데 사용되고 있다. 더욱이, 이메일러의 함수 테스트에는 이메일을 전달하지 않는 컨트롤러 테스트 케이스를 함축하고 있고, 이메일 메시지를 인-메모리 어레이(ActionMailer::Base.deliveries = [])에 붙인다. 이로서, 보내진 이메일의 수를 확인하면서, 각 메시지의 콘텐트를 확인할 수 있다.

성능 테스트

이와 같은 템플릿이 단위, 함수, 통합 영역 밖에 있는 테스트 디렉토리의 리스트에서 기본적으로 생성되지 않지만, 개발자들은 일부 애플리케이션 성능 특성을 측정하고 싶을 수 있다. 개발 시 너무 일직 성능에 초점을 맞추는 것은 현명하지 못하지만, 개발 프로세스가 끝으로 갈수록 성능 시나리오에서는 용량 플래닝을 다루어야 하고, 계속적인 반복 시 무엇인가 느려지기 시작할 때 회귀 테스팅을 해야 한다.

성능 테스팅이 다른 테스트 항목들과 함께 어떻게 구현될 수 있는지를 보기 위해, 비교적 큰 엔터프라이즈 환경에 전개된 Team Room 애플리케이션에 과도한 부하를 주는 시나리오를 생각해 보자. 전과 마찬가지로, 몇 가지 테스트 데이터부터 시작하고, 성능 테스팅을 위해서만 로딩된 픽스처를 제공하고, 비교적 대형 테스트 데이터가 실행될 때 로컬화 한다.


Listing 29. 대형 데이터 테스트 세트를 위한 동적인 사용자 픽스처
                
<% SALT = "tstsalt" unless defined?(SALT) %>
<% 200.upto(1200) do |i| %>user_<%= i %>:
  id: <%= i %>
  usertype:  usr
  firstname: New<%= i %>
  lastname:  One
  extension: '9999'
  userid:    new<%= i %>
  email:     new<%= i %>@domain.net
  salt:      <%= SALT %>
  hash_passwd: <%= User.encrypt('secret' , SALT) %>
<% end %>

성능 테스트가 일반 사용 시나리오에 기반하도록 고려할 수 있고, 극도의 부하를 부과하는 대형 데이터 테스트 세트로 확장할 수 있다. 테스트 컨트롤러는 일반 함수 테스팅에 사용되는 것과 비슷하지만, 이 테스트 케이스는 특정 측면을 과장하고 있다. 사용자 컨트롤러와 로그인 액션이 극단의 부하로 테스트 된다고 생각해 보자. 테스트 데이터가 로딩되는 장소를 제외하고는(self.fixture_path), 이 테스트 케이스 설정은 동일해야 한다.


Listing 30. 사용자 로그인 성능 테스트 설정
                
class UserLoginTest < Test::Unit::TestCase
  self.fixture_path = Pathname.new(File.dirname(__FILE__)).parent + 
                                   'fixtures' + 'performance'
  fixtures :users

  def setup
    @controller = UsersController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
  end
  [...]
end

이제, 많은 동시 사용자들을 다루는 Team Room 기능을 테스트 하기 위해, 픽스처에 정의된 모든 사용자들을 한번에 로그인 하는 것을 시도할 수 있고, 이러한 액션들에 최소한의 시간 제한도 부과할 수 있다. Listing 31에서, 이것을 루프에 구현했고, 로거(logger)가 꺼져있다는 것을 확인했다. Ruby 표준 라이브러리 벤치마크를 사용한 시간 계산을 모으기 위해서이다.


Listing 31. 로그인 액션 동안 사용자 컨트롤러 성능 테스트 하기
                
  def test_1000_login
    @controller.logger.silence do
      user_count = 0
      elapsed_time = Benchmark.realtime do
        1200.downto(200) do |id|
          user = users("user_#{id}")
          post :login, :userid => user.userid, :password => 'secret'
          user_count += 1
        end
      end
      assert_equal 1001, user_count
      assert elapsed_time < 10.0
    end
  end

보다 현실적인 성능 시나리오에는 마케팅 리포트 뒤에 있는 XML 쿼리가 포함되어 있다. 이 부분은 독자 여러분에게 맡기도록 하겠다. 성능 테스팅 구현의 중요한 측면은 대형 테스트 데이터 세트와 확장 로드와 연결된 일반 함수 및 통합 테스트 케이스의 사용이다. 이러한 방식은 성능 회귀 테스트의 구현을 보다 쉽고 효과적으로 만든다.

Rake를 사용한 애플리케이션 테스트

rails teamroom 명령어를 사용하여 Team Room 애플리케이션을 구현하기 시작할 때, 디렉토리 구조가 만들어지고, 더 중요한 것은 Rails gem에 구현된 유틸리티 세트가 "연결(hook)"된다. Rails gem은 뒤에서 다른 모든 컴포넌트들의 통합을 조정하는 얇고 간단한 컴포넌트이다. Active RecordAction View가 대표적이다. 동시에, Rails gem은 강력한 Rake gem 헬퍼를 통해 프레임웍을 사용하여 모든 웹 애플리케이션들에 걸쳐 공통 영역을 다루고 있다.

Ruby의 make 유틸리티와 같은 Rake는 마이그레이션, 스키마 로드/덤프, Rails gem 업데이트/동결, 문서 생성을 단순하게 할 뿐만 아니라, 테스팅도 보장한다. Team Room 애플리케이션 루트 디렉토리에 있는 태스크인 Rake 를 실행하면서, 믿을 수 있는 조력자인 rake에 의해 제공된 사전 정의된 전체 태스크 세트에 익숙해 질 수 있다. 이것은 디렉토리 구조가 만들어지고, Rake 파일이 생성되는 초기부터 Team Room 애플리케이션에 주입되는데, 사전 정의된 태스크 세트를 포함하고 있는 tasks/rails 라이브러리 경로가 공유된다.

rake test 명령어를 사용하여, 지금까지 구현된 모든 단위 테스트 및 함수 테스트가 함께 실행될 수 있고, 개발 사이클 동안 쉽게 호출될 수 있다. 이 테스트는 카테고리에 기반하여 실행될 수 있고, Rails 애플리케이션 표준 디렉토리 구조는 Listing 32처럼, test/unit 경로에 모든 정의된 테스트를 쉽게 로딩한다.


Listing 32. Team Room 애플리케이션용 모든 단위 테스트 실행하기
                
D:\rails\teamroom>rake test:units
(in D:/rails/teamroom)
D:/ruby/bin/ruby -Ilib;test 
"D:/ruby/lib/ruby/gems/1.8/gems/rake-0.7.2/lib/rake/rake_test_loader.rb" 
"test/unit/customer_info_test.rb" "test/unit/document_test.rb" 
"test/unit/subjects_subscription_test.rb" "test/unit/subject_test.rb" 
"test/unit/subscription_mailer_test.rb" "test/unit/subscription_test.rb" 
"test/unit/user_test.rb" "test/unit/xml_content_test.rb"
Loaded suite D:/ruby/lib/ruby/gems/1.8/gems/rake-0.7.2/lib/rake/rake_test_loader
Started
..........
Finished in 11.553 seconds.

rake test:functionals를 호출하는 동안, 같은 기본 테스트 로딩이 발생한다.

DB2 Rails 애플리케이션 문제 결정

Rails 애플리케이션을 개발할 때, 예기치 못한 결과나 에러를 만나게 된다. 첫 번째로 검사해야 할 것은 /log 디렉토리에 있는 로그 파일들이다. (우리 예제의 경우, d:\rails\teamroom\log). Rails는 고유의 로그에 각 환경에 대한 로그와 에러를 작성한다. 개발 환경에 생긴 문제의 경우, development.log를 보고, 테스트 환경에 생긴 문제의 경우, test.log를 본다. Rails 로그 파일과 애플리케이션 자체의 문제를 진단하는 것 외에도, DB2 트레이스 장치와 로그도 Rails 애플리케이션 문제를 진단하는데 도움이 된다.

CLI 트레이스

본 시리즈 Part 1에서 설명한 것처럼, ibm_db 드라이버는 Open Database Connectivity (ODBC)용 IBM 드라이버와 Call Level Interface (CLI)를 사용하여 IBM 데이터 서버로 연결한다. IBM 데이터 서버와의 모든 인터랙션은 ODBC용 IBM 드라이버와 CLI를 통해 이루어지고, CLI 트레이스를 사용하여 포착될 수 있다. CLI 트레이스는 Ruby 애플리케이션에 의해 만들어진 API 호출을 ODBC용 IBM 드라이버와 CLI 드라이버로 포착하여 드라이버에서 애플리케이션으로 리턴된 모든 인풋 매개변수와 값들을 기록한다. 따라서, CLI 트레이스는 인풋과 아웃풋 값에 대한 통찰력을 제공하고, 뒤에서 발생한 Rails 프레임웍에 의해 생성된 실제 SQL 문에 대한 통찰력도 준다.

CLI 트레이스를 실행하는 단계는 Infocenter를 참조하라.

주 10: CLI 트레이스가 디스크에 작성될 때, 애플리케이션 성능에 영향을 미친다. 또한, CLI 트레이스는 트레이스(또는 db2cli.ini의 다른 CLI 설정)가 적용되기 위해서는 재시작 되어야 하는 애플리케이션이나 웹 서버를 필요로 하기 때문에 동적이지 못하다.

CLI 트레이스는 올바른 SQL 문이 애플리케이션에 의해 실행되고 정확한 값이 데이터베이스에서 리턴되는지를 확인하는 값진 툴이다. 대부분의 SQL 문들이 Team Room 애플리케이션에서 Rails에 의해 생성되기 때문에, 다음과 같은 CLI 트레이스 아웃풋은 그림 2.에 보여진 XML 문서를 업로드 함으로써 DB2 데이터베이스로 보내진다. XML 문서 업로딩에는 데이터를 세 개의 개별 테이블로 삽입하는 것이 포함된다.

  1. 콘텐트 유형, 이름, 플랫폼, 크기, 주제, 파일을 업로드 했던 사용자 아이디, 업데이트 및 생성 시간을 문서 테이블에 삽입한다.



    Listing 33. CLI 트레이스: Part 1
                            
    SQLExecDirect( hStmt=1:8 )
        ---> Time elapsed - +1.844000E-003 seconds
    ( pszSqlStr="INSERT INTO documents (content_type, name, platform, size, updated_at, 
    subject_id, user_id, created_at, data) VALUES('text/xml', 'CAN-Central.xml', 'Any', 
    125177, '2007-06-07 13:12:57', NULL, 100, '2007-06-07 13:12:49', NULL)", cbSqlStr=225 )
    ( StmtOut="INSERT INTO documents (content_type, name, platform, size, updated_at, 
    subject_id, user_id, created_at, data) VALUES(?, ?, ?, 125177, ?, NULL, 100, ?, NULL)" )
    ( Package="SYSSH200          ", Section=11 )
        sqlccsend( Handle - 84681200 )
        sqlccsend( ulBytes - 349 )
        sqlccsend( ) rc - 0, time elapsed - +1.991000E-003
        sqlccrecv( timeout - +0.000000E+000 )
        sqlccrecv( ulBytes - 188 ) - rc - 0, time elapsed - +2.471000E-003
    ( Row=1, iPar=1, fCType=SQL_C_CHAR, rgbValue="text/xml" - x'746578742F786D6C', pcbValu...
    ( Row=1, iPar=2, fCType=SQL_C_CHAR, rgbValue="CAN-Central.xml" - x'43414E2D43656E74726...
    ( Row=1, iPar=3, fCType=SQL_C_CHAR, rgbValue="Any" - x'416E79', pcbValue=3, piIndicato...
    ( Row=1, iPar=4, fCType=SQL_C_CHAR, rgbValue="2007-06-07 13:12:57" - x'323030372D30362...
    ( Row=1, iPar=5, fCType=SQL_C_CHAR, rgbValue="2007-06-07 13:12:49" - x'323030372D30362...
        sqlccsend( Handle - 84681200 )
        sqlccsend( ulBytes - 223 )
        sqlccsend( ) rc - 0, time elapsed - +2.380000E-004
        sqlccrecv( timeout - +0.000000E+000 )
        sqlccrecv( ulBytes - 127 ) - rc - 0, time elapsed - +1.909000E-003
    
    SQLExecDirect( )
        <--- SQL_SUCCESS   Time elapsed - +1.504370E-001 seconds
    ...
    

  2. XML 파일의 이름, 문서 테이블의 상응 아이디, XML 플레이스홀더를 삽입한다.



    Listing 34. CLI 트레이스: Part 2
                            
    SQLExecDirect( hStmt=1:8 )
        ---> Time elapsed - +5.003000E-003 seconds
    ( pszSqlStr="INSERT INTO xml_contents (name, document_id, data) VALUES('CAN-Central.xml',
     101, '<ibm>@@@IBMXML@@@</ibm>')", cbSqlStr=108 )
    ( StmtOut="INSERT INTO xml_contents (name, document_id, data) VALUES(?, 101, ?)" )
    ( Package="SYSSH200          ", Section=11 )
        sqlccsend( Handle - 84681200 )
        sqlccsend( ulBytes - 261 )
        sqlccsend( ) rc - 0, time elapsed - +1.799000E-003
        sqlccrecv( timeout - +0.000000E+000 )
        sqlccrecv( ulBytes - 137 ) - rc - 0, time elapsed - +1.865000E-003
    ( Row=1, iPar=1, fCType=SQL_C_CHAR, rgbValue="CAN-Central.xml" - x'43414E2D43656E747261...
    ( Row=1, iPar=2, fCType=SQL_C_CHAR, rgbValue="<ibm>@@@IBMXML@@@</ibm>" - x'3C69626D3E40...
        sqlccsend( Handle - 84681200 )
        sqlccsend( ulBytes - 221 )
        sqlccsend( ) rc - 0, time elapsed - +1.000000E-005
        sqlccrecv( timeout - +0.000000E+000 )
        sqlccrecv( ulBytes - 89 ) - rc - 0, time elapsed - +1.840000E-003
    
    SQLExecDirect( )
        <--- SQL_SUCCESS   Time elapsed - +8.456800E-002 seconds
    ...
    

  3. 그 다음에는 실제로 XML 데이터를 삽입하는 업데이트 문이다.



    Listing 35. CLI 트레이스: Part 3
                            
    SQLPrepare( hStmt=1:8 )
        ---> Time elapsed - +1.861000E-003 seconds
    ( pszSqlStr="UPDATE xml_contents SET (data) = (?) WHERE id = 101", cbSqlStr=51 )
    ( StmtOut="UPDATE xml_contents SET (data) = (?) WHERE id = 101" )
    ( Package="SYSSH200          ", Section=11 )
        sqlccsend( Handle - 84681200 )
        sqlccsend( ulBytes - 244 )
        sqlccsend( ) rc - 0, time elapsed - +1.000000E-005
        sqlccrecv( timeout - +0.000000E+000 )
        sqlccrecv( ulBytes - 120 ) - rc - 0, time elapsed - +1.923000E-003
    
    SQLPrepare( )
        <--- SQL_SUCCESS   Time elapsed - +3.831300E-002 seconds
    ...
    SQLDescribeParam( hStmt=1:8, usPar=1, psSQLType= ...
        ---> Time elapsed - +2.315000E-003 seconds
    
    SQLDescribeParam( psSQLType=SQL_XML, puiParamDef=0, psScale=0, ...
        <--- SQL_SUCCESS   Time elapsed - +2.457500E-002 seconds
    
    SQLBindParameter( hStmt=1:8, iPar=1, fParamType=SQL_PARAM_INPUT, fCType=SQL_C_BINARY, 
    fSQLType=SQL_XML, cbColDef=0, ibScale=0, rgbValue= ...
        ---> Time elapsed - +2.225000E-003 seconds
    
    SQLBindParameter( )
        <--- SQL_SUCCESS   Time elapsed - +3.743500E-002 seconds
    
    SQLExecute( hStmt=1:8 )
        ---> Time elapsed - +1.848000E-003 seconds
    E0D0A202020202020202020203C2F6974656D3E0D0A202020....., pcbValue=125177 )
        sqlccsend( Handle - 84681200 )
        sqlccsend( ulBytes - 61440 )
        sqlccsend( ) rc - 0, time elapsed - +2.710000E-004
        sqlccsend( Handle - 84681200 )
        sqlccsend( ulBytes - 61440 )
        sqlccsend( ) rc - 0, time elapsed - +2.120000E-004
        sqlccsend( Handle - 84681200 )
        sqlccsend( ulBytes - 2450 )
        sqlccsend( ) rc - 0, time elapsed - +1.300000E-005
        sqlccrecv( timeout - +0.000000E+000 )
        sqlccrecv( ulBytes - 89 ) - rc - 0, time elapsed - +2.345000E-003
    
    SQLExecute( )
        <--- SQL_SUCCESS   Time elapsed - +9.107600E-002 seconds
    ...
    

  4. 마지막으로, insert 문을 사용하여 SUBJECTS 테이블이 채워진다.



    Listing 36. CLI 트레이스: Part 4
                            
    SQLExecDirect( hStmt=1:10 )
        ---> Time elapsed - +2.166000E-003 seconds
    ( pszSqlStr="INSERT INTO subjects (name, size, tag, description) VALUES('Marketing', 1, 
    'sales', '@@@IBMTEXT@@@')", cbSqlStr=100 )
    ( StmtOut="INSERT INTO subjects (name, size, tag, description) VALUES(?, 1, ?, ?)" )
    ( Package="SYSSH200          ", Section=14 )
        sqlccsend( Handle - 84681200 )
        sqlccsend( ulBytes - 263 )
        sqlccsend( ) rc - 0, time elapsed - +1.920000E-004
        sqlccrecv( timeout - +0.000000E+000 )
        sqlccrecv( ulBytes - 154 ) - rc - 0, time elapsed - +1.823000E-003
    ( Row=1, iPar=1, fCType=SQL_C_CHAR, rgbValue="Marketing" - x'4D61726B6574696E67', ...
    ( Row=1, iPar=2, fCType=SQL_C_CHAR, rgbValue="sales" - x'73616C6573', pcbValue=5, ...
    ( Row=1, iPar=3, fCType=SQL_C_CHAR, rgbValue="@@@IBMTEXT@@@" - x'40404049424D5445 ...
        sqlccsend( Handle - 84681200 )
        sqlccsend( ulBytes - 182 )   
        sqlccsend( ) rc - 0, time elapsed - +3.160000E-004
        sqlccrecv( timeout - +0.000000E+000 )
        sqlccrecv( ulBytes - 89 ) - rc - 0, time elapsed - +2.128700E-002
    
    SQLExecDirect( )
        <--- SQL_SUCCESS   Time elapsed - +1.243580E-001 seconds
    

DB2 트레이스

DB2 트레이스는 추적 가능한 모든 내부 DB2 함수 호출을 포착하며, DB2와 관련된 애플리케이션 문제들을 조사하는 귀중한 툴이다. 애플리케이션 실행 시 DB2 내부 액티비티에 관한 정보를 제공한다. 효과적인 정보 모으기를 가능케 하는 DB2 트레이스의 여러 특성들이 있다.

  1. 트레이스는 동적으로 켜지고 꺼질 수 있어야 한다. 트레이스는 애플리케이션을 순환시키지 않고 실행 또는 실행 불가 되어야 한다. 정확한 오류 지점이 알려지면, 트레이스는 관련 정보만을 모으는 상황 이전에 실행될 수 있다.
  2. DB2 트레이스 정보는 메모리나 디스크에 저장될 수 있다.
  3. 트레이스 마스크를 사용하여 특정 컴포넌트만 추적할 수 있다.

DB2 트레이스 실행에 대한 내용은 Infocenter를 참조하라.

Rails 애플리케이션에서의 DB2 문제를 해결할 때, 애플리케이션이 있는 클라이언트에서 CLI 트레이스와 DB2 트레이스를 함께 모우는 것이 유용하다.

db2diag.log와 db2diag 툴

db2diag.log는 DB2에서 생긴 에러와 경고를 기록한다. DB2 에러나 경고가 애플리케이션 실행 중에 기록되었는지 여부를 파악하기 위해 db2diag.log가 검토될 수 있다. 로그의 민감성은 기본값 3부터 dbm cfg의 다른 값으로 변할 수 있다.

db2diag 분석 툴은 DB2 9®에 사용되어 db2diag.log 파일을 필터링 및 포맷한다. 특정 데이터베이스나 타임스탬프 값과 관련된 로그 엔트리들을 필터링 한다. db2diag 툴에 대한 자세한 내용은 Infocenter를 참조하라.

FAQ와 경고

  1. ibm_db Ruby 어댑터와 드라이버를 사용하기 위해 DB2 클라이언트 설치가 필요한가?

    필요하다. DB2와 Ruby on Rails 시리즈 Part 1에서 언급했듯이, IBM_DB 어댑터 (ibm_db_adapter.rb)는 ibm_db Ruby 드라이버를 직접적으로 의존하는데, 이는 ODBC용 IBM 드라이버와 CLI를 사용하여 IBM 데이터 서버로 연결한다. 따라서, 최소 요구 사항은 ODBC용 IBM DB2 드라이버와 CLI이지만, DB2 9 FP2 또는 이후 버전의 클라이언트 패키지(CLI 드라이버 포함)로도 충분히 IBM_DB 어댑터 연결을 할 수 있다.

  2. DB2에 Rails 애플리케이션을 실행할 때 다음과 같은 에러가 관찰되었다.

    SQL0954C: Not enough storage is available in the application heap to process the statement.

    Part 1에서 언급했듯이, DB2 9 상의 Rails 애플리케이션들은 최소 1024의 APPLHEAPSZ를 필요로 한다. APPLHEAPSZ를 검사하려면, 데이터베이스로 연결하여 설정 매개변수를 확인하라.

  3. IBM Ruby Driver를 사용하여 DB2 i5 또는 DB2 for z/OS에 액세스 하기 위해 DB2 Connect가 필요한가?

    필요하다. DB2 클라이언트가 DB2 i5 또는 DB2 z/OS 서버로 연결하기 위해서는 DB2 Connect가 필요하다. ODBC용 IBM DB2 드라이버와 CLI를 사용할 때, 유효 라이센스 파일이 드라이버 설치 경로에 필요하다.

  4. rake db:test:* 명령어를 사용하는 경우 테스트 환경을 복제할 때 rake aborted 에러를 얻었다.

    database.yml 파일을 검사해 보라. 테스트 환경은 rails 명령어에 의해 생성된 기본 설정에서 제공된 database.yml 스팩에 따라서 설정되어야 한다. 또한, 주 1에 기록된 두 개의 픽스가 필요하다.

  5. IBM_DB 어댑터 최신 버전에서 픽스된 것으로 알려진 문제를 만났다. 하지만, gem list --local 명령어는 최신 버전의 IBM_DB 어댑터가 설치되었음을 보여준다.

    또는 유닉스 상에서 비슷한 경로를 갖고 있지 않은지를 확인하라.®.

    GEM_HOME path <ruby_path>\lib\ruby\gems\1.8\gems\ibm_db-<version>-mswin32\lib\active_record\connection_adapters (또는 유닉스 상의 비슷한 경로)에 설치된 최신 IBM_DB gem에는 단 한 개의 ibm_db_adapter.rb만 있어야 한다. 이것은 Rails 환경에 로딩된 유일한 IBM_DB 어댑터이다.

  6. 향후 Rails 1.2.4, config.connection_adaptersRAILS_CONNECTION_ADAPTERS는 제거될 예정이다. 따라서, Part 1의 수동 단계를 통해 Rails 프레임웍에 있는 연결 어댑터 리스트에 "ibm_db"를 등록할 필요가 없다. gem install ibm_db 명령어를 사용하여 IBM_DB 어댑터가 설치되면, 찾아서 로딩 할 Rails 환경에 즉각 사용할 수 있다.

결론

Rails 프레임웍의 빌트인 테스트 지원으로 테스트가 쉬워졌다. Rails 애플리케이션은 config/database.yml 파일에 정의된 테스트, 개발, 실행 환경을 갖고 있어서 다양한 목적을 가진 다른 데이터베이스를 설정할 수 있다. 새로운 Rails 프로젝트를 처음 만들면, Rails는 테스트 인프라스트럭처를 만든다. 여러분이 만드는 모든 모델과 컨트롤러의 경우, 이에 상응하는 테스트 스텁이 만들어 진다. 단위 테스트는 Rails 모델을 테스트 하는 반면, 함수 및 통합 테스트는 보다 고급 레벨에서 Rails 애플리케이션이 디자인 대로 작동하는지를 검사한다. 픽스처 역시 테스팅을 위한 데이터를 지정할 수 있고, Mock 객체들을 사용하면 네트워크 연결이나 외부 시스템으로의 액세스에 신경 쓰지 않고 핵심 애플리케이션을 테스트 하는데 집중할 수 있다. 이러한 기능들은 Rails에 내장되어 있어서 테스트가 매우 수월하다.





위로


다운로드 하십시오

설명이름크기다운로드 방식
Team room 샘플 코드Teamroom3.zip10KBHTTP
다운로드 방식에 대한 정보


참고자료

교육

제품 및 기술 얻기

토론


필자소개

John Chun은 DB2 Advanced Support 팀의 전문가로서, 애플리케이션 개발과 툴링 분야에서 일하고 있다. IBM DBT Toronto 연구실에서 Java, C, C++, Perl, REXX, C#을 포함한 다양한 언어들로 DB2 애플리케이션 문제를 해결하고 있다. DB2 CLI와 OLEDB 드라이버 및 .NET 프로바이더 관련 수 많은 프로젝트를 수행했다. DB2 Certified Solutions Expert 및 Certified WebSphere Administrator이다.


Alex Pitigoi는 IBM Toronto Lab의 소프트웨어 엔지니어이다. 1998년 이후 Information Management 분야에서 다양한 소프트웨어 개발 프로젝트에 참여했으며, 웹 기술과 데이터베이스 관리를 전문으로 했다. 최근에는 SQLModel 개발 프로젝트를 이끌었고, 이것은 현재 Eclipse Data Tools Project로 통합되었고, 많은 IBM 데이터 서버들에서 데이터베이스 관리 웹 툴용 아키텍처로서 사용되고 있다. DB2 Satellite Administration Center에서도 일했으며 DB2용 최초의 웹 툴 개발도 이끌었다. 현재 새로운 오픈 소스 기술(Ruby, Python, PHP)을 위한 IBM의 데이터 서버 인에이블먼트에 집중하고 있다.


Christine Law는 IBM Toronto Lab, DB2 UDB Advanced Support 팀의 IBM 인증 솔루션 전문가이다. 2001년 IBM에 입사했으며, 4년 이상을 DB2 고객을 위해 일하고 있다. DB2 애플리케이션 개발 분야가 전문이며, 특히 JDBC, SQLJ, 저장 프로시저, 임베디드 SQL을 전문으로 하고 있다. 현재 Ajax와 Ruby 같은 오픈 소스 기술에 관심을 갖고 있다.


Naomi Ngan은 2000년 캐나다의 토론토대학교에서 컴퓨터 공학과 통계학과를 우수한 성적으로 졸업했다. 졸업 후에, IBM에 입사했으며, 애플리케이션 개발 환경에서 IBM DB2 RDBMS 제품 결함과 문제 해결 책임을 맡았다. IBM에서 4년을 근무한 후, UCSF에 있는 Ernest Gallo Clinic and Research Center로 옮겨와서 바이오인포매틱스 소프트웨어를 개발했다. 데이터베이스 객체, JSP, 자바 독립 애플리케이션, 리눅스와 윈도우즈 플랫폼 기반 XML 환경에서의 저장 프로시저의 디자인과 개발을 담당했다. 현재, Autonomy Corporation의 소프트웨어 엔지니어로서 J2EE 엔터프라이즈 소프트웨어를 개발하고 있다. DB2의 애플리케이션 개발과 툴링에 해박한 지식을 갖고 있으며, DB2, XML, WebSphere, Java/J2EE 등 수 많은 IBM 및 Sun 개발자 인증을 보유하고 있다.




기사에 대한 평가


보다 나은 서비스를 제공하기 위함이오니 잠시 짬을 내어 이 양식을 제출하여 주십시오.



아니오잘 모르겠음
 


 


12345
 



위로


developerWorks 콘텐트를 다른 사이트에 전재하기:
developerWorks 콘텐트에 대한 저작권은 IBM에 있습니다. IBM의 서면 허가나 원본 저자의 허락이 없이는 전재를 금합니다. 저희 콘텐트를 전재하시려면 IBM developerWorks 담당자 에게 문의하십시오.
    IBM 소개 개인정보 보호정책 문의