제공 : 한빛 네트워크
저자 : Bill Walton
역자 : 노재현
원문 : Cookin" with Ruby on Rails - Designing for Testability
[이전 기사 보기]
Cooking with Ruby on Rails - Designing for Testability (1)
CB : 이제부터 우리의 테스트 데이터를 fixture 라고 불리는 곳에 만들게 될거야. Rails에는 3가지의 테스트가 있다고 했지? Integration, Functional, Unit 테스트가 있어. 테스트 fixture 안에 있는 테스트 데이터를 테스트 케이스에 있는 테스트 함수를 이용해서 테스트 결과를 받아내면 되는거야.
아마도 이제 보여주게 될 걸 본 적이 없을 거야. 이 걸 사용할때 말해주지 않았었거든. 우리가 우리의 조리법을 위한 scaffolding과 카테고리 models, controllers들을 만들때 Rails가 Unit 테스트와 Functional 테스트를 할때 필요한 스텁(stub) 코드를 만들었거든.
그림 8.
그림 9.
테스트 케이스 파일은 test/unit 디렉토리에 들어있지. 여기 Unit 단계의 테스트 케이스들이 있어.
그림 10.
그림 11.
Paul : 네. 확실히 스텁(stub) 코드로 보이네요. 어떻게 활용을 하면 좋을까요?
CB : 아직 많은 걸 하지는 못하겠지만 이미 유용하게 사용되고 있어. 자 한 번 테스트 케이스를 실행해 보자고. 카테고리 테스트 부터 시작해 보자고. 애플리케이션의 루트 디렉토리에서 다음을 실행하면
ruby testunitcategory_test.rb
그림 12.
CB : 이제 조리법 Unit 테스트를 실행하면
ruby testunitrecipe_test.rb
그림 13.
Paul : 흠 뭔가가 잘못된 것 같은데요?
CB : 아니 잘된 것 같은데. 우선 이 두 가지의 테스트 케이스에서 어떤 일을 했는지에 대해서 말해줄께. 우선 테스트 데이터베이스에서 카테고리 테이블을 보면
그림 14.
그리고 이건 조리법 테이블이야.
그림 15.
CB : 카테고리 테이블은 2개의 레코드를 가지고 있는 걸 볼 수 있지. 레코드는 id 필드를 제외하고 나머지 필드는 비어 있는 상태야. 그건 우리가 Rails에게 제공한 fixture가 id 필드 밖에 가지고 있지 않아서 그런거지. 테스트 케이스를 실행하게 되면 Rails는 다음 라인을 발견하게 되지.
fixtures :categories
이 명령은 Rails에게 테스트 함수를 실행하기 전에 categories fixture(categories.yml)를 로딩하도록 하는 명령어지. Rails는 각각의 테스트가 서로 간섭받지 않도록 하기 위해서 매 테스트 함수를 수행하기 전에 fixture에 설정된데로 데이터베이스를 리셋을 하게 되어 있어. 테이블의 모든 데이터를 지우고 fixture에서 지정된 데이터로 채우게 되는 거지. 그러고 나서 Rails가 카테고리 테스트 케이스를 다 실행하고 나면 다음과 같이 결과를 출력하지.
1 tests, 1 assertion, 0 failures, 0 errors
위 결과를 보면 우리가 테스트 케이스를 실행했고(category_test.rb), Rails에게 실패하지 않았음을 알리는 assertion(assert true) 명령을 가지고 있는 하나의 테스트 함수(test_truth)를 발견했고(0 failures), 아무런 문제가 없었다는 것을 알 수 있어(0 errors)
Paul : 그러면 assertion 명령어가 실제로 우리가 검사하려는 값하고 비교하는 함수겠네요?
CB : 맞았어. Rails가 바로 우리를 위해서 이런 assertion 함수들을 제공해 주고 있는거지. 이외에도 assert_equal, assert_nil, assert_not_nil과 같은 함수들을 제공하고 있어. 물론 우리가 만든 함수를 이용할 수도 있지. 지금 바로 만들어 보지는 않겠지만 자네가 가기 전에 참고할 수 있는 문서들을 좀 알려주도록 할께. assertion은 보통 두 가지 형태를 취하는데 하나는
assert expression
다음과 같은 방법으로 이용할 수 있지.
assert Recipe.count <= 3
사용자 정의 assertion도 사용할 수 있는데 Rails에서 제공하는 assert_equal과 같은 것 말이지. 아니면 test_helper.rb 파일에 직접 하나 만들어 볼 수도 있어. 사용자 정의 assertion은 다음의 형태를 가지게 돼.
assert_some_condition(expected_value, thing_to_test_for_that_value)
Paul : 그럼 fixture라는 건 우리가 데이터를 데이터베이스에 어떻게 로드하는가에 대한 것이고, 매번 테스트 함수를 수행하기 전에 데이터베이스가 리셋되는거죠?
CB : 그래. 그리고 테스트 함수를 실행하기 전에 환경을 준비하는 다른 방법이 하나 더 있어. category_test.rb 파일을 예로 들면, 이 파일에 setup 이라는 함수가 있다고 해보지. Rails는 이 setup 함수를 모든 테스트 함수 이전에 실행을 하게 되어 있어. setup 함수는 특정 테스트 케이스를 위한 데이터를 설정하고 싶을때 유용하게 사용할 수 있어. 그리고 상응하는 teardown이라는 함수가 있는데 이건 테스트 함수가 매번 수행된 후에 한 번씩 실행되는 함수야. 나중에 더 자세히 알아보도록 하자고.
Paul : 그리고 assertion은 특정 오브젝트가 우리가 원하는 값을 가지고 있는지 확인하는 기능이고요. 좋아요. 이제 조리법 테스트로 가봐요. 아까보니까 무슨 에러가 나는것 같았는데, 제대로 수행된 거라고 하셨잖아요?
CB : 맞아. recipes 테이블을 보도록 하자고.
그림 16.
그리고 recipes fixture도 다시 한 번 보자고.
그림 17.
이제 에러 메시지를 다시 보면 4번째 줄에 있는 에러 메시지 보여? "foreign key constraint fails"라고 시작하는 줄 말이야. fixture에서 뭔가가 빠진 걸 찾을 수 있겠어?
Paul : 네. category_id라는 foreign key의 값이 아무것도 설정되어 있지 않네요.
CB : 잘 봤는데. 그럼 category_id 를 각 fixture에 설정하도록 하자고. 가독성을 위해서 가능하면 편집할때 줄을 맞추고 싶은데 이건 YAML 파일이라 조심해야 해. YAML 파일은 탭키를 인식하지 못하거든. 그래서 스페이스 바를 눌러서 맞추던가 아니면 텍스트 편집기에 탭키를 누르면 스페이스 바 키로 변환하도록 설정해 놓아야 만해. 그렇지 않으면 에러가 날 수도 있지.
그림 18.
이제 저장하고 다시 테스트를 실행해 보자.
그림 19.
Paul : 제가 제대로 이해하고 있다면 MySQL이 우리가 스키마에서 정의한 foreign key를 설정하도록 강제를 했겠네요. 맞아요?
CB : 정확하게 봤어. 바로 그걸 알아보기 위해서 Rails가 실패(failure)가 아닌 에러(error)를 발생하도록 테스트 케이스를 미리 실행해 봤었던거야. Rails가 실패(failure)를 출력할 때는 assertion이 실패한 거야. 에러(error)는 애플리케이션에 문법 오류가 있거나 시스템에 문제가 있을때 발생하는 거라고 보면 되지. Rails가 이전에 출력했던 에러는 "MySQL이 뭔가 잘못된 연산을 하고 있습니다"라고 말한거나 다름없지.
Paul : 네. 그런데 생각해보니 우리의 스키마에서는 categories 테이블은 이름을 가지게 되어 있고, recipes 테이블은 제목을 가지게 되어 있는데 MySQL이 foreign key가 없다고 에러를 출력할 때와는 다르게 작동하네요? 이런 작동방식을 설정하는 건 어떻게 하는 건가요?
CB : 좋은 질문 했어. 그 질문은 내가 왜 "테스트를 먼저 개발은 다음"의 개발방식으로 옮겨 가야하는지를 설명해 주지. 생각해보자고, 우리가 지금의 애플리케이션을 고객에게 주었다고 해보자고. 그럼 이미 버그 리포팅을 작성하고 있을 거야. Mongrel을 실행하고 애플리케이션을 브라우저에서 띄운후에 이름이 없는 새로운 카테고리를 하나 만들어봐. 어때?
그림 20.
Paul : 오 이런. 분명히 recipes 테이블에도 같은 현상이 발생하겠네요. 버그 리포팅이 눈덩이 처럼 불어나는게 눈에 보이네요. 어떻게 이 조건을 테스트 할 수가 있을까요?
CB : 우선 데이터베이스에서 왜 비어 있는 값을 허용하고 있는지를 확인해 봐야겠지. db 디렉토리에 있는 스키마 파일을 보자고.
그림 21.
문제가 바로 여기에 있어. :default => "" 라고 되어 있지? 데이터베이스에 null 값은 안되도록 해놨지만 "" 은 null이 아니니까 그랬던 거야. 왜 그랬는지는 모르겠지만 처음 시간에 서둘러서 작업을 하다가 그랬던 것 같아. 아마도 이렇게 생각했을 수도 있지. "우선 이렇게 해두고 나중에 고치지 뭐". 이게 바로 왜 우리가 테스트를 먼저 만들어야 되는지를 설명해주는 예 중에 하나야. 이제 자네가 물어본 질문에 대답해 주기 위해서 Rails에 있는 validation 함수를 보도록 할까.
아니 validation을 보기전에 여태까지 봤던걸 생각해 봐. 우리는 두 단계에 걸쳐서 테스트 데이터베이스를 만들었어. Rails가 생성했던 스텁(stub)을 이용해서 테스트 해보니 Rails와 데이터베이스 모두 정상작동하고 있었어. 그리고 나서 테스트 결과를 보니 코드를 작성할 때 뭔가 문제가 있다는 걸 알 수 있었지. 지금은 우리에게 버그를 고치기 위해서 무엇을 고쳐야 하는지를 말해주고 있어. 우리가 테스트를 먼저 만들었다면 이런 버그는 발생하지 않았을 거라는 거야.
Paul : 와 이렇게 쉽게 여기까지 올 수 있다는게 참 놀랍네요. 그리고 우리가 확인하지 않고 고객에게 보냈으면 버그로 돌변할 수 있었던 문제점들도 다 드러났고요. 그런데 사실 코드를 작성하기 전에 테스트를 먼저 작성한다는 말이 무슨 뜻인지 잘 모르겠어요.
CB : 걱정말 그건 정상적인 거야. 우선 우리가 작성해야 하는 코드가 좀 있으니 이 코드를 작성하면서 같이 익혀보면 어떨까? 우선 테스트 데이터베이스에서 우리가 만들었던 레코드를 모두 지우고 테스트 할때 사용할 수 있도록 fixture로 정상적인 데이터를 채워보자고. 이전에 봤던 브라우저에 있는 데이터를 가져와서 채워보면 되겠다.
그러면 categories.yml 파일은 다음처럼 될거야.
one:
id: 1
name: main course
two:
id: 2
name: beverages
그리고 recipes.yml 파일은
one:
id: 1
category_id: 1
title: pizza
description: CB"s favorite lunch
date: 2007-05-09
instructions: Phone pizza joint. Place order. Pay delivery guy. Chow down!
two:
id: 2
category_id: 2
title: iced tea
date: 2007-05-09
역자 노재현님은 어렸을 때부터 컴퓨터를 접하게 된 덕에 프로그래밍을 오랫동안 정겹게 하고 있는 프로그래머 입니다. 특히나 게임 및 OS 개발에 관심이 많으며, 심심할 때면 뭔가 새로운 프로그램을 만들어내는 것을 좋아합니다. 다음에서 웹 관련 개발을 한 후에 현재는
www.osguru.net이라는 OS관련 웹사이트를 운영하며 넥슨에서 게임 개발을 하고 있습니다.
* e-mail:
wonbear@gmail.com
* homepage:
http://www.oguru.net