티스토리 뷰

프로그래밍 공부/TIL : Rails Tutorial

Chapter 11 : Testing Rails Applications

마음 따뜻한 개발자, 나른한 하루 2020. 7. 16. 15:42
이 글은 Rails 5.0 Guide 기준으로 작성됩니다.
https://guides.rubyonrails.org/v5.0/api_app.html

 

 

  • Testing Rails Applications Intro

Application 테스트를 위한 Rails의 내장 메커니즘을 다룹니다.

 

이를 통해 우리는 다음 3가지의 과정을 배워나갈 겁니다 :

  • Rails 테스트 용어
  • 응용 프로그램에 대한 단위, 기능 및 통합 테스트를 작성하는 방법
  • 다른 인기있는 테스트 방식 및 플러그인

 

 

  • Why Write Tests for your Rails Applications?

Rails를 통해 테스트를 매우 쉽게 작성할 수 있습니다. Model 및 Controller가 생성될 때 기본 테스트 코드를 생성하는 것으로 시작됩니다.

Rails 테스트를 간단히 실행하면 주요 코드 리팩토링(변경) 후에도 코드가 잘 작동하는지 확인할 수 있습니다.

 

Rails 테스트는 브라우저 request을 모의테스트를 할 수 있으므로 직접 브라우저 접속을 통해 테스트하지 않고도 Application의 response을 테스트 할 수 있습니다.

 

 

  • Introduction to Testing

Rails 내 테스트 지원은 신기술로서가 아닌, Rails 프레임워크가 처음 선보일 때 부터 지원되어왔습니다.

 

1. Rails Sets up for Testing from the Word Go

rails new application_name

위 명령어를 통해 Rails 프로젝트를 생성하자마자 Rails는 테스트 디렉토리를 생성합니다.

Rails 프로젝트 내에서 test 폴더 내 리스트를 조회하면 아래와 같습니다 :

## 터미널 cmd 입력(input)
ls -F test
## Output (명령어 입력에 대한 결과)

controllers/    helpers/        mailers/        test_helper.rb
fixtures/       integration/    models/

test 폴더 내 디렉토리들은 아래와 같은 특징들이 있습니다.

  • models 디렉토리는 Model에 대한 테스트를 유지하기위한 것이고,
  • controllers 디렉토리는 Controller에 대한 테스트를 유지하기위한 것이며,
  • integration 디렉토리는 여러 컨트롤러가 상호 작용하는 테스트를 유지하기위한 것입니다.
  • fixtures 디렉토리는 Fixtures은 테스트 데이터를 구성합니다.
  • test_helper.rb 파일은 기본적인 테스트 구성이 설정되어 있습니다.
  • 그 외 Mailer를 테스트하기 위한 디렉토리와 View helper를 테스트 하기 위한 디렉토리도 있습니다.

2. The Test Environment

Rails Application에는 기본적으로 3가지 Environment들이 존재합니다 : development, test, production


각 Environment 설정에 대해서는  config/environments  에서 할 수 있는데, 이 중 Test Evironment는  config/environments/test.rb  에서 설정 할 수 있습니다.

 

3. Rails meets Minitest

과거 Getting Started with Rails guide의 상황을 다시 기억해볼 때, Rails에서 Model 파일을 명령어를 통해 generate 시, Model 파일과 함께 몇몇 test 파일들이 생성되는것을 보았을 겁니다 :

## 터미널 cmd 입력(input)
rails generate model article title:string body:text

 

...
create  app/models/article.rb
create  test/models/article_test.rb
create  test/fixtures/articles.yml
...

기본적으로 생성된 테스트 코드를  test/models/article_test.rb 보면 아래와 같이 보여집니다.

require 'test_helper'
 
class ArticleTest < ActiveSupport::TestCase
  # test "the truth" do
  #   assert true
  # end
end

 

위 파일을 한 줄씩 살펴보면 Rails 테스트 코드 및 용어에 초점을 맞춰진 것을 볼 수 있습니다.

require 'test_helper'

위와같이 require 'test_helper' 을 정의 시, test_helper.rb 테스트를 실행하기 위한 기본 구성이 로드됩니다. 우리가 작성한 모든 테스트 파일에 위 코드가 있으므로, 파일에 추가 된 위 require은 모든 테스트에서 사용할 수 있습니다.

class ArticleTest < ActiveSupport::TestCase

ArticleTest class이 test case로 정의될 수 있는건 ActiveSupport::TestCase 을 상속받았기 때문입니다.

또한 ArticleTest은 ActiveSupport::TestCase에서 지원하는 모든 메소드들을 사용할 수 있습니다. 후에 우리는 ActiveSupport::TestCase에서 지원하는 메소드들을 살펴볼겁니다.

 

test_ (대소문자 구분)로 시작하는 Minitest::Test (이의 부모 클래스는 ActiveSupport::TestCase)에서 상속받는 class 내 메소드들을 간단히 테스트라고 합니다. 이를테면 test_password 및 test_valid_password는 올바른 test name이면서도, test case 실행 시 자동으로 돌아갑니다.

 

Rails는 테스트 이름과 블록을 취하는 테스트 방법을 추가합니다. 여기서 테스트 코드 내에서는 메소드 이름 앞에 test_ 가 붙은 일반 Minitest::Unit 테스트를 생성합니다. 따라서 테스트 코드의 메소드 이름 지정에 대해 걱정할 필요가 없으며, 테스트 코드에 대해선 다음과 같이 작성됩니다.

test "the truth" do
  assert true
en

위의 예시에 대해선 아래와 동일한 코드라고 보면 됩니다.

def test_the_truth
  assert true
end

바로 위 코드처럼 일반 메소드 정의 방식을 사용할 수 있긴 하나, test 매크로를 쓰는것이 test name을 더 읽기 쉽게 합니다.

 

 참고  test 코드 내 block 형식의 이름에 있어 공백은 밑줄로 대체하여 생성됩니다. 결과는 Ruby 식별자일 필요는 없지만 이름에는 문장 부호 문자 등이 포함될 수 있습니다. 이는 Ruby에서 기술적으로 모든 문자열이 메소드 이름 일 수 있기 때문입니다. 이 경우 define_method를 사용해야하고 제대로 호출하기 위해 호출을 보내야하지만 공식적으로는 이름에 제한이 거의 없습니다.

 

다음으로 첫 assertion(추론) 코드를 봅시다 :

assert true

assertion은 예상 결과에 대한 개체 (또는 식; expression)를 평가하는 코드입니다.

예를 들어 assertion은 아래 사항을 점검할 수 있습니다.

  • 이 값(value) = 저 값(value) 인가?
  • 객체가 nil(NULL)인가?
  • 해당 줄의 코드에서 Exception이 발생하는가?
  • password 문자가 5글자 이상인가?

모든 테스트에는 허용되는 assertion 수에 대한 제한 없이, 하나 이상의 assertion이 포함될 수 있습니다.

테스트 검증은 모든 어설 션이 성공한 경우에만 테스트에 통과합니다.

 

1) Your first failing test

테스트 실패 결과를 확인하기 위해 article_test.rb 파일 내 Test case에 실패한 테스트를 추가 할 수 있습니다.

test "should not save article without title" do
  article = Article.new
  assert_not article.save
end

위 테스트를 실행 해 봅시다.

## 터미널 cmd 입력(input)
# 여기서 6은 테스트가 정의 된 행 수입니다.

rails test test/models/article_test.rb:6
## [Failed] 테스트 실행 결과

Run options: --seed 44656
 
# Running:
 
F
 
Failure:
ArticleTest#test_should_not_save_article_without_title [/path/to/blog/test/models/article_test.rb:6]:
Expected true to be nil or false
 
 
bin/rails test test/models/article_test.rb:6
 
 
 
Finished in 0.023918s, 41.8090 runs/s, 41.8090 assertions/s.
 
1 runs, 1 assertions, 1 failures, 0 errors, 0 skips

 

위 테스트 코드에 있어 원래의 정답은 '저장이 된다.' 입니다. 하지만 '저장이 안될것이다.' 라는 것을 가정하여 테스트코드를 돌리게 되었고, 테스트 결과에서 F를 반환하게 되었습니다.

 

여기서 F는 '실패'를 나타냅니다.

1) 실패한 테스트 이름과 함께 실패 아래에 해당 추적이 표시됩니다.

2) 그 다음 몇 줄에는 스택 추적과 실제 값 및 assertion의 예상 값을 언급하는 메시지가 옵니다.

3) 기본 assertion 메시지는 오류를 정확히 찾아 낼 수있는 충분한 정보를 제공합니다.

4) assertion 실패 메시지를 보다 읽기 쉽게하기 위해 모든 어설 션은 다음과 같이 선택적 메시지 매개 변수를 제공합니다.

 

assertion 실패 메시지를보다 읽기 쉽게하기 위해 모든 assertion은 다음과 같이 선택적으로 message parameter를 제공합니다.

test "should not save article without title" do
  article = Article.new
  assert_not article.save, "Saved the article without a title"
end

 

위 test 코드를 돌리면, 아래와 같은 assertion message가 보입니다.

## Output (명령어 입력에 대한 결과)

Failure:
ArticleTest#test_should_not_save_article_without_title [/path/to/blog/test/models/article_test.rb:6]:
Saved the article without a title

 

 

다시 테스트코드 본론으로 돌아와서, 이제 테스트 코드의 '저장이 안될것이다.' 라는 추론이 통과되려면 기존의 Model 파일 속에 validates 조건을 추가시키면 됩니다. 

 

class Article < ApplicationRecord
  validates :title, presence: true
end

이제 validates 조건을 통해 '저장이 안될것이다' 라는 추론은 정답으로 이어지는 순간입니다.

다시한번 테스트 코드를 돌려봅시다 :

## 터미널 cmd 입력(input)
rails test test/models/article_test.rb:6
## Output (명령어 입력에 대한 결과)

Run options: --seed 31252
 
# Running:
 
.
 
Finished in 0.027476s, 36.3952 runs/s, 36.3952 assertions/s.
 
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips

 

이제 우리는 먼저 원하는 기능에 실패한 테스트를 작성한 다음 기능을 추가하는 코드를 작성하여 테스트를 통과했습니다. 이러한 소프트웨어 개발 접근 방식을 TDD (Test-Driven Development) 라고 합니다.

 

2) What an error looks like

오류가 보고되는 사례에 대해 알아봅시다.

아래에 오류를 일으키는 테스트 코드가 있습니다 :

test "should report error" do
  # some_undefined_variable is not defined elsewhere in the test case
  some_undefined_variable
  assert true
end

위의 코드를 테스트 후, 아래와 같은 결과물을 볼 수 있습니다 :

## 터미널 cmd 입력(input)
rails test test/models/article_test.rb
## Output (명령어 입력에 대한 결과)
Run options: --seed 1808
 
# Running:
 
E
 
Error:
ArticleTest#test_should_report_error:
NameError: undefined local variable or method `some_undefined_variable' for #<ArticleTest:0x007fee3aa71798>
    test/models/article_test.rb:11:in `block in <class:ArticleTest>'

이번에는 E 라는 결과를 반환합니다. 이는 테스트의 에러(Error)를 뜻합니다.

 

 참고  모든 test 메소드는 무작위 순서로 실행됩니다. test 메소드 작업 도중 assertion 오류가 발생하면 다른 test 메소드의 테스트 결과 출력은 안되나, 내부적으로 다른 test 메소드는 계속 검사가 진행됩니다. 또한 config.active_support.test_order 옵션을 사용하여 테스트 메소드의 순서를 설정할 수 있습니다.

* 이 부분에 대해 답변을 주신 시니어개발자 님께 감사! 🙏

중간에 테스트 실패 및 에러가 발생하더라도, 테스트는 멈추지 않고 계속 이루어집니다.

테스트가 실패하면 backtrace가 표시됩니다. 기본적으로 Rails는 backtrace - Application에 관련된 행에 대해서만 부분적으로 backtrace를 출력합니다. 이를 통해 코드에 집중할 수 있습니다. 그러나 전체적인 역 추적을보고 싶은 경우, 명령어에 -b (또는 --backtrace) 옵션을 설정하면됩니다.

rails test -b test/models/article_test.rb

 

만약 테스트 메소드에 있어 raise를 통과(skip)하려면 assert_raises를 사용하도록 다음과 같이 수정하면됩니다.

test "should report error" do
  # some_undefined_variable is not defined elsewhere in the test case
  assert_raises(NameError) do
    some_undefined_variable
  end
end

 

4. Available Assertions

assertion은 작업자의 테스트 코드를 기반으로 작동하는 일꾼같은 개념입니다. 실제로 테스트 코드를을 실행하여 제대로 동작하는지 확인합니다.

 

다음은 Rails에서 사용하는 기본 테스트 라이브러리인 Minitest와 함께 사용할 수 있는 Assertion을 보여줍니다. [msg] parameter는 테스트 실패 메시지를 더 명확하게 하기 위해 지정할 수 있는 선택적인 기능입니다.

Assertion 특징
assert( test, [msg] ) 내부 테스트 결과가 true
assert_not( test, [msg] ) 내부 테스트 결과가 false
assert_equal( expected, actual, [msg] ) expected와 actual이 == 일 경우 true
assert_not_equal( expected, actual, [msg] ) expected와 actual이 != 일 경우 true
assert_same( expected, actual, [msg] ) expected.equal?(actual) 일 경우 true
assert_not_same( expected, actual, [msg] ) expected.equal?(actual) 일 경우 false
assert_nil( obj, [msg] ) obj.nil? == true 일 것이다.
assert_not_nil( obj, [msg] ) obj.nil? == false 일 것이다.
assert_empty( obj, [msg] ) obj.empty? == true 일 것이다.
assert_not_empty( obj, [msg] ) obj.empty? == false 일 것이다.
assert_match( regexp, string, [msg] ) string이 정규식(regexp)에 매칭 될 것이다.
assert_no_match( regexp, string, [msg] ) string이 정규식(regexp)에 매칭이 안 될 것이다.
assert_includes( collection, obj, [msg] ) obj는 collection이 가진 텍스트 내에 존재할 것이다.
assert_not_includes( collection, obj, [msg] ) obj는 collection이 가진 텍스트 내에 존재하지 않을 것이다.
assert_in_delta( expected, actual, [delta], [msg] ) | expected - actual | <= delta 일 것이다.
assert_not_in_delta( expected, actual, [delta], [msg] ) | expected - actual | <= delta 가 아닐 것이다.
assert_throws( symbol, [msg] ) { block } 블록 내 코드의 결과가 symbol을 던지도록합니다.
assert_raises( exception1, exception2, ... ) { block } 블록 내 코드가 주어진 exception 에러 중 하나를 발생시킵니다.
assert_nothing_raised { block } 블록 내 코드가 exception 에러 없이 잘 작동됩니다.
assert_instance_of( class, obj, [msg] ) obj가 클래스의 인스턴스인지 확인합니다.
assert_not_instance_of( class, obj, [msg] ) obj가 클래스의 인스턴스가 아닌지 확인합니다.
assert_kind_of( class, obj, [msg] ) obj가 클래스의 인스턴스이거나 그로부터 내려 오는지 확인합니다.
assert_not_kind_of( class, obj, [msg] ) obj가 클래스의 인스턴스가 아니며 그 하위 클래스가 아닌지 확인합니다.
assert_respond_to( obj, symbol, [msg] ) obj가 심볼에 응답하도록 합니다.
assert_not_respond_to( obj, symbol, [msg] ) obj가 심볼에 응답하지 않도록 합니다.
assert_operator( obj1, operator, [obj2], [msg] ) obj1.operator(obj2)가 true인지 확인합니다.
assert_not_operator( obj1, operator, [obj2], [msg] ) obj1.operator(obj2)가 false인지 확인합니다.
assert_predicate ( obj, predicate, [msg] ) obj.predicate가 true인지 확인합니다. (예 : assert_predicate str, :empty?)
assert_not_predicate ( obj, predicate, [msg] ) obj.predicate가 false인지 확인합니다. (예 : assert_not_predicate str, :empty?)
assert_send( array, [msg] ) array[2 and up]의 parameters를 사용하여 array[0]의 객체에서 array[1]에 나열된 메소드를 실행하는 것이 올바른지 확인하십시오 (예 : assert_send [@user, :full_name, 'Sam Smith'])
flunk( [msg] ) 실패를 추측합니다. 아직 완료되지 않은 테스트를 명시 적으로 표시하는 데 유용합니다.

위의 내용은 최소한의 지원을 제공하는 assertion의 일부입니다.

전체 목록과 최신 목록을 보려면 Minitest API Document, 특히 Minitest::Assertions를 확인하십시오.

 

 

5. Rails Specific Assertions

Rails는 몇 가지 커스텀 assertion을 minitest 프레임 워크에 추가합니다.

Assertion 특징
assert_difference(expressions, difference = 1, message = nil) {...} yield 된 block 내에서 반영된 결과로 block 내 코드 반영 전 및 후 사이의 숫자 차이를 테스트합니다.
assert_no_difference(expressions, message = nil, &block) yield 된 block 내 코드 반영 전 및 후 사이의 숫자 차이를 테스트합니다.
assert_recognizes(expected_options, path, extras={}, message=nil) yield 된 block 내 코드 반영 전 및 후 사이의 숫자 차이가 없어야 합니다.
assert_generates(expected_path, options, defaults={}, extras = {}, message=nil) assert에서 제공된 옵션을 통해 Path를 생성 할 수 있습니다. (assert_recognizes의 반대적 특징을 가집니다.) 추가적인 parameters는 request Query string에 있는 추가 request parameters의 이름과 값을 알려주는 데 사용됩니다. message parameters를 사용하면 assertion failures에 대한 사용자 지정 오류 메시지를 지정할 수 있습니다.
assert_response(type, message = nil)

응답에 특정 상태 코드가 제공되는지 확인합니다.

Success: 200-299 / Redirect: 300-399 / not found(miss): 404 / error: 500-599

상태 코드 혹은 그에 맞는 symbolic을 전달할 수도 있습니다. 자세한 내용은 상태 코드의 전체 목록과 매핑 작동 방식을 참조하십시오.

assert_redirected_to(options = {}, message=nil) 전달 된 redirection 옵션이 최근 Action에서 호출 된 redirection 옵션과 일치하는지(match) 확인합니다. 이러한 match는 assert_redirected_to (controller : "weblog")이 redirect_to (controller : "weblog", action : "show") 등과 일치될 수 있습니다. 또한 assert_redirected_to와 같은 라우터에서 지어진 이름(root_path) 및 assert_redirected_to(@article)과 같은 Active Record 객체를 전달할 수 있습니다.

 

6. A Brief Note About Test Cases

Minitest::Assertions에 정의된 assert_equal과 같은 모든 기본 assertion은 자체 테스트 사례에서 사용하는 class에서도 사용할 수 있습니다. 실제로 Rails는 다음과 같은 클래스를 제공합니다.

ActiveSupport::TestCase
ActionMailer::TestCase
ActionView::TestCase
ActionDispatch::IntegrationTest
ActiveJob::TestCase

 

이러한 각 클래스에는 Minitest::Assertions가 포함되어 있으므로 테스트에서 모든 기본 assertion을 사용할 수 있습니다.

 

7. The Rails Test Runner

특정 파일을 가리키는 테스트코드 실행

Rails에서는 아래 명령어와 같이 test 파일 이름을 가리키기만 하면 바로 테스트 코드를 실행시킬 수 있습니다.

rails test test/models/article_test.rb

 

특정 파일 내 메소드를 가리키는 테스트코드 실행

테스트코드 내 특정 메소드를 작동하고 싶다면, -n(--name) 옵션과 메소드 이름을 추가적으로 입력해주면 됩니다.

rails test test/models/article_test.rb -n test_the_truth

 

특정 파일 내 method line을 가리키는 테스트코드 실행

특정 테스트 코드 method line을 돌리고 싶다면, 테스트 method 블록이 시작하는 부분을 가리키면 됩니다.

rails test test/models/article_test.rb:6 # run specific test and line

 

특정 디렉토리 전체를 가리키는 테스트코드 실행

테스트 파일을 가진 디렉터리 전체에 대해서도 실행시킬 수 있습니다.

rails test test/controllers # run all tests from specific directory

 

이 외 옵션은 터미널에서  rails test -h  를 치면 나오는 가이드라인을 참고해주세요.

## 터미널 cmd 입력(input)
rails test -h
## Output (명령어 입력에 대한 결과)

minitest options:
    -h, --help                       Display this help.
    -s, --seed SEED                  Sets random seed. Also via env. Eg: SEED=n rake
    -v, --verbose                    Verbose. Show progress processing files.
    -n, --name PATTERN               Filter run on /regexp/ or string.
        --exclude PATTERN            Exclude /regexp/ or string from run.
 
Known extensions: rails, pride
 
Usage: bin/rails test [options] [files or directories]
You can run a single test by appending a line number to a filename:
 
    bin/rails test test/models/user_test.rb:27
 
You can run multiple files and directories at the same time:
 
    bin/rails test test/controllers test/integration/login_test.rb
 
By default test failures and errors are reported inline during a run.
 
Rails options:
    -e, --environment ENV            Run tests in the ENV environment
    -b, --backtrace                  Show the complete backtrace
    -d, --defer-output               Output test failures and errors after the test run
    -f, --fail-fast                  Abort test run on first failure or error
    -c, --[no-]color                 Enable color in the output

 

 
  • The Test Database

Rails Application은 데이터베이스와 많이 상호작용 하므로 결과적으로 테스트를 수행 할 데이터베이스도 필요합니다. 효율적인 테스트를 작성하려면, 데이터베이스를 설정하고 샘플 데이터로 채우는 방법을 이해해야합니다.

 

기본적으로 모든 Rails 애플리케이션에는 development, test, production의 세 가지 Environment가 있습니다. 각 Environment에 대한 데이터베이스는  config/database.yml  에 구성되어 있습니다.

 

데이터베이스는 Environment별로 독립적으로 사용할 수 있습니다. 이는 즉 development 혹은 production 데이터베이스의 데이터에 대해 걱정할 필요없이 test Environment에서 독립적으로 테스트 데이터를 처리 할 수 ​​있습니다.

 

1. Maintaining the test database schema

테스트를 실행하려면 test Environment의 데이터베이스에 구조(스키마)가 있어야합니다. test helper는 테스트 데이터베이스에 보류중인 마이그레이션이 있는지 확인합니다.  db/schema.rb  또는  db/structure.sql  을 테스트 데이터베이스에 로드하려고 시도할겁니다. 마이그레이션이 여전히 보류 중이면 오류가 발생합니다. 일반적으로 이는 스키마가 완전히 마이그레이션 되지 않았음을 의미합니다. 개발 데이터베이스 (rake db:migrate)에 대해 마이그레이션을 실행하면 스키마가 최신 상태가됩니다.

 

2. The Low-Down on Fixtures

좋은 테스트를 위해서는 테스트 데이터 설정을 생각해야 합니다. Rails에서는 fixtures를 정의 및 커스터마이징 하여 이를 처리 할 수 ​​있습니다. 이에대한 더욱 자세한 내용은 Fixtures API Document 에서 찾아볼 수 있습니다.

 

1) What Are Fixtures?

Fixtures를 사용하면 테스트를 실행하기 전에 테스트 데이터베이스를 사전 정의 된 데이터로 채울 수 있습니다. Fixture은 데이터베이스 독립적이며 YAML로 작성됩니다. Model 당 하나의 파일이 있습니다.

 

2) YAML

YAML 형식의 Fixtures는 Sample Data를 설명하기위한 인간 친화적 인 방법입니다. 이러한 유형의 Fixtures는 .yml 파일 확장자를 갖습니다 .

 

다음 YAML Fixture 파일에 대한 예시를 확인해보세요 :

# lo & behold! I am a YAML comment!
david:
  name: David Heinemeier Hansson
  birthday: 1979-10-15
  profession: Systems development
 
steve:
  name: Steve Ross Kellock
  birthday: 1974-09-27
  profession: guy with keyboard

 

각 fixture에는 이름과 콜론(:)으로 구분 된 key: value 쌍의 들여 쓰기 목록이 표시됩니다. record는 일반적으로 빈 줄로 구분됩니다. fixture 파일에 # 문자를 통해 주석을 작성할 수 있습니다.

 

Association 작업을 하는 경우, 두 개의 다른 fixture 사이에 참조 노드를 간단히 정의 할 수 있습니다.

다음은 Model 간 belongs_to 및 has_many 연계가 되어있는 예제입니다 :

# In fixtures/categories.yml
about:
  name: About
 
# In fixtures/articles.yml
first:
  title: Welcome to Rails!
  body: Hello world!
  category: about

 

첫 번째 article의 category key 값은  fixtures/articles.yml  에 있는 값 입니다.

 

 참고  연관된 타 model 이름으로 서로를 참조하기 위해 fixture에서 id: attribute 을 지정하는 대신 fixtures name을 활용할 수 있습니다. Rails는 서로 일관된 기본키를 자동으로 할당합니다. association 동작에 대한 자세한 정보는 Fixtures API Document를 읽으십시오.

 

3) ERB'in It Up

ERB를 사용하면 템플릿 내에 Ruby 코드를 포함시킬 수 있습니다. YAML Fixture 형식은 Rails가 Fixture를 로드 할 때 ERB로 사전 처리(pre-processed)됩니다. 이를 통해 Ruby를 사용하여 일부 Sample Data를 생성 할 수 있습니다.


예를 들어 다음 코드는 수천 명의 사용자를 생성합니다.

<% 1000.times do |n| %>
user_<%= n %>:
  username: <%= "user#{n}" %>
  email: <%= "user#{n}@example.com" %>
<% end %>

 

4) Fixtures in Action

Rails는 기본적으로  test/fixtures  디렉토리에서 모든 fixtures를 자동으로 로드합니다.

load에는 세 단계가 포함됩니다.

 

  1. fixture에 해당하는 테이블에서 기존 데이터를 제거합니다  

  2. fixture 데이터를 테이블에 작성합니다.

  3. 직접 액세스하려는 경우, fixture 데이터를 method 내에 Dump하세요.

 

 참고  Rails는 데이터베이스에서 기존 데이터를 제거하기 위해 외래키 및 검사 제약조건과 같은 참조 무결성 트리거를 비활성화 하려고 합니다. 테스트를 실행할 때 권한 오류가 발생 시 데이터베이스 사용자에게 테스트 환경에서 testing environment의 trigger에 있어, 권한에 대해 비활성화 할 권한이 있는지 확인하십시오. (PostgreSQL에서는 super user만 모든 트리거를 비활성화 할 수 있습니다. PostgreSQL 권한에 대한 자세한 내용은 여기를 참조하십시오)

 

 

5) Fixtures are Active Record objects

Fixtures은 Active Record의 instance입니다. 그렇다보니 본문의 'The Test Database' 언급과 같이, 객체에 바로 접근이 가능합니다. scope가 local test case로 method로서 자동으로 사용할 수 있기 때문입니다.

 

다음 예시를 확인해보세요 :

# this will return the User object for the fixture named david
users(:david)
 
# this will return the property for david called id
users(:david).id
 
# one can also access methods available on the User class
david = users(:david)
david.call(david.partner)

 

한 번에 여러 개의 fixtures를 얻으려면 fixture name 목록을 전달하면됩니다.

예를 들면 다음과 같습니다. :

# this will return an array containing the fixtures david and steve
users(:david, :steve)

 

 

  • Model Testing

Model 테스트는 Application의 다양한 Model을 테스트하는데 사용됩니다.

 

Rails Model 테스트는  test/models  디렉토리에 저장됩니다.

또한 Model 테스트는 뼈대(skeleton)를 직접 생성(generate) 할 수도 있습니다.

## 터미널 cmd 입력(input)
rails generate test_unit:model article title:string body:text
## Output (명령어 입력에 대한 결과)

create  test/models/article_test.rb
create  test/fixtures/articles.yml

 

Model 테스트는 ActionMailer::TestCase와 같은 super class(상위 클래스)를 상속하지 않으나, 대신 ActiveSupport::TestCase로 부터 상속합니다.

 

 

  • Integration Testing

통합 테스트(Integration tests)는 Application의 다양한 부분이 어떻게 상호 작용하는지 테스트하는 데 사용됩니다. 일반적으로 Application 내에서 중요한 workflows를 테스트하는데 사용됩니다.

 

Rails 통합 테스트를 생성하기 위해 Application에  test/integration  디렉토리를 사용합니다. Rails는 통합 테스트 뼈대 생성을 아래 명령어를 통해 해낼 수 있습니다 :

## 터미널 cmd 입력(input)
rails generate integration_test user_flows
## Output (명령어 입력에 대한 결과)

exists  test/integration/
create  test/integration/user_flows_test.rb

 

새로 생성 된 통합 테스트(Integration tests)는 아래와 같습니다.

require 'test_helper'
 
class UserFlowsTest < ActionDispatch::IntegrationTest
  # test "the truth" do
  #   assert true
  # end
end

 

여기서 테스트는 ActionDispatch::IntegrationTest에서 상속됩니다. 이를 통해 통합 테스트에서 사용되는 helper 문법을 사용할 수 있습니다.

 

1. Helpers Available for Integration Tests

표준 테스트 helper 외에도 ActionDispatch::IntegrationTest 을 상속을 함으로서 추가적으로 사용할 수 있는 helper가 있습니다.

추가적으로 제공되는 세 가지 helper에 대해 간단히 살펴보겠습니다.

 

2. Implementing an integration test

blog Application에 통합 테스트를 적용해보겠습니다. 모든 blog가 제대로 작동하는지 확인하기 위해 새 blog 글을 작성하는 기본 워크 플로부터 시작하겠습니다.

 

통합 테스트 기본 파일을 생성해봅시다. :

## 터미널 cmd 입력(input)
rails generate integration_test blog_flow

It should have created a test file placeholder for us. With the output of the previous command we should see:

우리를 위해 테스트 파일 자리 표시자를 만들었을 것입니다. 이전 명령의 출력으로 다음을 볼 수 있습니다.

## Output (명령어 입력에 대한 결과)

invoke  test_unit
create    test/integration/blog_flow_test.rb

이제 만들어진 파일을 열고 첫 번째 assertion을 작성해봅시다 :

require 'test_helper'
 
class BlogFlowTest < ActionDispatch::IntegrationTest
  test "can see the welcome page" do
    get "/"
    assert_select "h1", "Welcome#index"
  end
end

 

"Testing Views" 에서 request의 결과 HTML을 쿼리하기 위해 assert_select를 살펴 보겠습니다. 주요 HTML 요소 및 해당 컨텐츠의 존재를 확인하여 request의 response을 테스트하는데 사용됩니다.

 

root path를 방문하면  welcome/index.html.erb  가 view에 보여집니다. 이는 곧 assertion을 통과해야 합니다.

 

1) Creating articles integration

blog에서 새 글을 작성 및 결과를 확인해봅시다 :

test "can create an article" do
  get "/articles/new"
  assert_response :success
 
  post "/articles",
    params: { article: { title: "can create", body: "article successfully." } }
  assert_response :redirect
  follow_redirect!
  assert_response :success
  assert_select "p", "Title:\n  can create"
end

위 코드는 articles Controller 에서 :new action을 호출하는 것으로 시작합니다.

이 응답은 성공(Success)해야 합니다.

 

그런 다음 articles Controller의 :create Action에 POST Method를 기반으로 request 합니다.

post "/articles",
  params: { article: { title: "can create", body: "article successfully." } }
assert_response :redirect
follow_redirect!

 

request 후(params 다음 줄)에 나오는 두 줄은 새 article을 만들 때 설정한 redirection을 처리합니다.

 참고  redirect 생성 후 subsequent request(후속 요청)을 할 경우, follow_redirect! 를 추가하세요.

 

마지막으로 response이 성공적이며, 페이지에서 새 articles를 읽을 수 있다고 assertion(주장) 할 수 있습니다.

 

2) Taking it further

blog를 방문 후 새 article를 작성하기 위한 매우 작은 워크 플로우를 성공적으로 테스트 할 수 있었습니다. 더 나아가고 싶다면 주석 달기, articles 제거 또는 주석 편집을 위한 테스트를 추가 할 수 있습니다. 통합 테스트는 Application에 대한 모든 종류의 사용 사례를 실험하기 좋습니다.

 

 

  • Functional Tests for Your Controllers

Rails에서 Controller의 다양한 Action 테스트는 기능 테스트 작성의 한 형태입니다. Controller는 Application으로 들어오는 웹 request을 처리하고 보여지는(렌더링) 된 view로 response 합니다. 기능 테스트를 작성할 때 Action 처리에 대한 대한 request 및 예상 결과 또는 response(일부는 HTML 보기)을 다루는 법을 테스트합니다.

 

1. What to include in your Functional Tests

다음과 같은 사항을 테스트해야합니다.

  • web request가 성공적인가? (was the web request successful?)
  • 유저가 올바른 page로 redirect 되었는가? (was the user redirected to the right page?)
  • 유저 인증 처리가 잘 되었는가? (was the user successfully authenticated?)
  • response 템플릿에 올바른 객체가 저장되었는가? (was the correct object stored in the response template?)
  • view에서 의도에 맞는 메시지가 사용자에게 표시 되었는가? (was the appropriate message displayed to the user in the view?)

Controller 내 Action의 기능 테스트를 확인하는 가장 쉬운 방법은 scaffold를 통해 Controller를 생성하는 것입니다. :

## 터미널 cmd 입력(input)
rails generate scaffold_controller article title:string body:text
## Output (명령어 입력에 대한 결과)

...
create  app/controllers/articles_controller.rb
...
invoke  test_unit
create    test/controllers/articles_controller_test.rb
...

scaffold를 통해 Controller 코드를 생성하고 Articles resource를 테스트합니다.  test/controllers  디렉토리의 articles_controller_test.rb 파일을 살펴볼 수 있습니다.

 

이미 Controller가 있고, 7개의 기본 Action(index, show, new, edit, create, update, destroy) 각각에 대해 테스트 scaffold 코드를 생성하려는 경우, 아래 명령을 사용할 수 있습니다.

## 터미널 cmd 입력(input)
rails generate test_unit:scaffold article
## Output (명령어 입력에 대한 결과)

...
invoke  test_unit
create test/controllers/articles_controller_test.rb
...

 articles_controller_test.rb  파일에서 test_should_get_index 테스트를 살펴 봅시다. :

# articles_controller_test.rb
class ArticlesControllerTest < ActionDispatch::IntegrationTest
  test "should get index" do
    get articles_url
    assert_response :success
  end
end

 

test_should_get_index 테스트 메소드에서 Rails는 index라는 Action에 대한 request를 시뮬레이션 하여 request가 성공했는지 확인하고, 올바른 response body가 생성되었는지 확인합니다.


GET 메소드는 웹 request을 시작하고 @response에 결과를 채 웁니다. 최대 6개의 인수를 사용할 수 있습니다. :

  • request한 Controller Action의 URI입니다. string 또는 Path Helper(예 : articles_url) 형식 일 수 있습니다.
  • params: request parameters의 해시가 있는 옵션은 Action으로 전달됩니다. (예: 쿼리 문자열 parameters 또는 article 변수)
  • headers: request와 함께 전달 될 header를 설정합니다.
  • env: 필요에 따라 요청 환경을 사용자 정의합니다.
  • xhr: Ajax request인지 판단 후, Ajax 요청일 경우 true로 설정할 수 있습니다.
  • as: 다른 컨텐츠 유형으로 요청을 인코딩합니다. 기본적으로 :json 을 지원합니다.

위의 키워드 인수는 모두 선택 사항입니다.

 

 예시 1  :show Action을 호출하여 params[:id] = 12를 전달 및 HTTP_REFERER 헤더를 설정합니다. :

get article_url, params: { id: 12 }, headers: { "HTTP_REFERER" => "http://example.com/home" }

 

 예시 2  :update Action을 호출하여 Ajax request로 params[:id] = 12를 전달합니다.

patch article_url, params: { id: 12 }, xhr: true

 참고  새로 추가 된 모델 레벨 유효성 검사로 인해 articles_controller_test.rb에서 test_should_create_article 테스트를 실행 시 실패합니다.

 

아래와 같이 articles_controller_test.rb에서 test_should_create_article 테스트를 수정하여 모든 테스트를 통과시켜 보세요! :

test "should create article" do
  assert_difference('Article.count') do
    post articles_url, params: { article: { body: 'Rails is awesome!', title: 'Hello Rails' } }
  end
 
  assert_redirected_to article_path(Article.last)
end

이제 모든 테스트를 통과할 수 있습니다.

 

2. Available Request Types for Functional Tests

Rails 기능 테스트에서는 GET과 같은 6개의 HTTP Method Request가 지원됩니다.

  • get
  • post
  • patch
  • put
  • head
  • delete

모든 request 유형에는 이에 동등한 Method가 있는데, 대표적으로 C.R.U.D.(get, post, put, delete Method)에 사용되는 Application 입니다.

 

 참고  기능 테스트는 특정 request 유형이 Action에 의해 승인되는지 검증하지 않으므로 결과가 정확한지에 대한 우려가 큽니다. 이와같은  request 테스트 사례가 존재하므로 테스트의 중요성이 더욱 높아집니다.

 

3. Testing XHR (AJAX) requests

AJAX 요청을 테스트하기 위해 xhr: true 옵션을 지정하여 GET, POST, PATCH, DELETE 메소드를 통해 메소드 별 역할에 맞게 이벤트를 진행(조회, 생성, 수정, 삭제)할 수 있습니다. 예를 들면 다음과 같습니다.

test "ajax request" do
  article = articles(:one)
  get article_url(article), xhr: true
 
  assert_equal 'hello world', @response.body
  assert_equal "text/javascript", @response.content_type
end

 

4. The Three Hashes of the Apocalypse

After a request has been made and processed, you will have 3 Hash objects ready for use:

Request가 처리되고 나면 3개의 해시 객체를 사용할 수 있습니다. :

  • cookies  설정이 된 쿠키
  • flash  flash에 남겨져 있는 객체
  • session  session 변수에 존재하는 객체

일반 Hash 객체의 경우와 마찬가지로 문자열을 기준으로 Key를 참조하여 Value에 접근할 수 있습니다.

아래와 같이 String을 Symbol로 참고할 수 있습니다.

flash["gordon"]               flash[:gordon]
session["shmession"]          session[:shmession]
cookies["are_good_for_u"]     cookies[:are_good_for_u]

 

5. Instance Variables Available

기능 테스트에서 세 가지 인스턴스 변수에 대해서도 접근할 수 있습니다.

  • @controller  요청을 처리하는 Controller
  • @request  request 객체
  • @response  response 객체

6. Setting Headers and CGI variables

HTTP headersCGI 변수(CGI 한글 정의) 역시 heaqders로 전달될 수 있습니다 :

# setting an HTTP Header
get articles_url, headers: "Content-Type" => "text/plain" # simulate the request with custom header
 
# setting a CGI variable
get articles_url, headers: "HTTP_REFERER" => "http://example.com/home" # simulate the request with custom env variable

 

7. Testing flash notices

Hash를 활용하여 쓰일 수 있는 세 개의 기능 중 하나가 flash 란 것 기억하시나요?

 

누군가 새 article를 생성할 때 마다 blog application에 flash 메시지를 띄어주려 합니다.

이 assertion을 test_should_create_article 테스트에 추가되는 코드는 아래와 같습니다 :

test "should create article" do
  assert_difference('Article.count') do
    post article_url, params: { article: { title: 'Some title' } }
  end
 
  assert_redirected_to article_path(Article.last)
  assert_equal 'Article was successfully created.', flash[:notice]
end

 

위 테스트를 실행하면 실패(F) 결과가 나옵니다. :

## 터미널 cmd 입력(input)
rails test test/controllers/articles_controller_test.rb -n test_should_create_article
## Output (명령어 입력에 대한 결과)

test_should_create_article
Run options: -n test_should_create_article --seed 32266
 
# Running:
 
F
 
Finished in 0.114870s, 8.7055 runs/s, 34.8220 assertions/s.
 
  1) Failure:
ArticlesControllerTest#test_should_create_article [/test/controllers/articles_controller_test.rb:16]:
--- expected
+++ actual
@@ -1 +1 @@
-"Article was successfully created."
+nil
 
1 runs, 4 assertions, 1 failures, 0 errors, 0 skips

이제 Controller에서 flash 메시지를 구현해 봅시다 :create Action은 이제 아래와 같이 수정해야 합니다.

def create
  @article = Article.new(article_params)
 
  if @article.save
    flash[:notice] = 'Article was successfully created.'
    redirect_to @article
  else
    render 'new'
  end
end

테스트 코드를 실행하면 아래와 같은 결과를 확인할 수 있습니다.

## 터미널 cmd 입력(input)
rails test test/controllers/articles_controller_test.rb -n test_should_create_article
## Output (명령어 입력에 대한 결과)

Run options: -n test_should_create_article --seed 18981
 
# Running:
 
.
 
Finished in 0.081972s, 12.1993 runs/s, 48.7972 assertions/s.
 
1 runs, 4 assertions, 0 failures, 0 errors, 0 skips

 

8. Putting it together

articles 컨트롤러는 :index, :new, :create Action을 테스트합니다.

그런데 여기서, 기존 데이터를 처리하는 것은 어떨까요? :show Action에 대한 테스트를 작성해 봅시다. :

test "should show article" do
  article = articles(:one)
  get article_url(article)
  assert_response :success
end

앞에서 fixtures 특징을 말했다 싶이, articles() 메소드를 활용해서 Controller 단에서 fixture 객체에 대한 접근을 할 수 있습니다.

 

기존 article 데이터를 삭제하는 것은 어떨까요?

test "should destroy article" do
  article = articles(:one)
  assert_difference('Article.count', -1) do
    delete article_url(article)
  end
 
  assert_redirected_to articles_path
end

 

기존 article 데이터를 수정하는 테스트를 추가 할 수도 있습니다.

test "should update article" do
  article = articles(:one)
 
  patch article_url(article), params: { article: { title: "updated" } }
 
  assert_redirected_to article_path(article)
  # Reload association to fetch updated data and assert that title is updated.
  article.reload
  assert_equal "updated", article.title
end

 

그런데 위 세가지 테스트 메소드 코드에서 중복이 보입니다. (셋 다 동일한 Article Fixture 데이터에 접근) Rails는 D.R.Y 철학을 가진 프레임워크로서, 코드의 중복을 줄일 수 있다는 특징이 존재합니다.

 

위 코드의 중복을 줄이고자 한다면, ActiveSupport::Callbacks에서 제공하는 setup(before callback)teardown(after callback) 문법을 활용하면 됩니다.

 

DRY 철학이 적용된(코드 중복을 제거한) 코드는 아래와 같습니다.

require 'test_helper'
 
class ArticlesControllerTest < ActionDispatch::IntegrationTest
  # called before every single test
  setup do
    @article = articles(:one)
  end
 
  # called after every single test
  teardown do
    # when controller is using cache it may be a good idea to reset it afterwards
    Rails.cache.clear
  end
 
  test "should show article" do
    # Reuse the @article instance variable from setup
    get article_url(@article)
    assert_response :success
  end
 
  test "should destroy article" do
    assert_difference('Article.count', -1) do
      delete article_url(@article)
    end
 
    assert_redirected_to articles_path
  end
 
  test "should update article" do
    patch article_url(@article), params: { article: { title: "updated" } }
 
    assert_redirected_to article_path(@article)
    # Reload association to fetch updated data and assert that title is updated.
    @article.reload
    assert_equal "updated", @article.title
  end
end

Rails의 다른 콜백과 마찬가지로 블록, 람다 또는 method 이름을 호출할 symbol로 전달하여 setup(before callback) 및 teardown(after callback) 문법을 사용할 수 있습니다.

 

9. Test helpers

코드 중복을 피하기 위해 자체 테스트 helper를 추가 할 수 있습니다.

아래 로그인 helper가 좋은 예시가 될 수 있습니다.

#test/test_helper.rb
 
module SignInHelper
  def sign_in_as(user)
    post sign_in_url(email: user.email, password: user.password)
  end
end
 
class ActionDispatch::IntegrationTest
  include SignInHelper
end
require 'test_helper'
 
class ProfileControllerTest < ActionDispatch::IntegrationTest
 
  test "should show profile" do
    # helper is now reusable from any controller test case
    sign_in_as users(:david)
 
    get profile_url
    assert_response :success
  end
end

 

 

  • Testing Routes

Rails 애플리케이션의 다른 모든 것들과 마찬가지로 Path를 테스트 할 수 있습니다.

 참고  Application에 복잡한 Path가 있는 경우, Rails는 여러 가지 유용한 helper를 제공하여 테스트 때 쓸 수 있습니다.

 

Rails에서 사용 가능한 라우팅 assertion에 대한 자세한 정보는 ActionDispatch::Assertions::RoutingAssertions API 문서를 참고해 주세요.

 

 

  • Testing Views

HTML 요소와 해당 내용의 존재를 확인하여 request에 대한 response을 테스트 하는 것이 Application의 view를 테스트하는 일반적인 방법입니다. Path 테스트와 마찬가지로 view 테스트는  test/controllers  디렉터리에 있거나 controller 테스트의 일부입니다. assert_select 메소드를 사용하면 간단하면서도 syntax를 활용해서 응답의 HTML 요소를 확인할 수 ​​있습니다.

 

assert_select에 대해선 두 가지 작성양식이 있습니다 :

  • assert_select (selector, [equality], [message])  selected를 통해 선택한 요소에서 동등 조건이 충족되도록 합니다. selected는 CSS selected 표현식(문자열) 이거나 대체 값이있는 표현식 일 수 있습니다.
  • assert_select (element, selector, [equality], [message])  요소(Nokogiri::XML::Node 인스턴스 또는 Nokogiri::XML::NodeSet) 및 해당 하위 항목에서 시작하여 selected를 통해 선택된 모든 요소에서 동등 조건이 충족되도록합니다. 

예를 들어 다음과 같이 응답에서 title 요소의 내용을 확인할 수 있습니다.

assert_select 'title', "Welcome to Rails Testing Guide"

더 자세한 조사를 위해 중첩 된 assert_select 블록을 사용할 수도 있습니다.

 

다음 예제에서 li.menu_item에 대한 내부 assert_select는 외부 블록에 의해 selected 내에서 실행됩니다.

assert_select 'ul.navigation' do
  assert_select 'li.menu_item'
end

assert_select는 각 요소에 대해 개별적으로 호출 될 수 있도록 선택된 요소들의 컬렉션이 반복 될 수 있습니다.

 

예를 들어 응답에 각각 네 개의 중첩 된 목록 요소가 있는 두 개의 정렬 된 목록이 포함 된 경우 다음 테스트가 모두 통과됩니다.

assert_select "ol" do |elements|
  elements.each do |element|
    assert_select element, "li", 4
  end
end
 
assert_select "ol" do
  assert_select "li", 8
end

해당 assertion은 매우 강력합니다.

더 자세한 사용법에 대해서는 해당 설명서를 참조하세요.

 

1. Additional View-Based Assertions

views 테스트에 주로 사용되는 assertion이 더 있습니다.

Assertion 특징
assert_select_email 메일 본문에 대해 assertion을 작성할 수 있습니다.
assert_select_encoded 인코딩 된 HTML에서 assertion을 만들 수 있습니다. 각 요소의 내용을 인코딩 해제 후, 인코딩 되지 않은 모든 요소를 ​​사용하여 블록을 호출하면됩니다.
css_select(selector) 혹은 css_select(element, selector) selector가 선택한 모든 요소의 배열을 반환합니다. 두 번째 변형에서는 먼저 기본 요소와 일치 후, 하위 요소의 selected 표현식을 일치시킵니다. 일치하는 항목이 없으면 두 변형이 모두 빈 배열을 반환합니다.

assert_select_email 에 대한 예시는 아래와 같습니다. :

assert_select_email do
  assert_select 'small', 'Please click the "Unsubscribe" link if you want to opt-out.'
end

 

 

  • Testing Helpers

helper는 view에서 사용할 수있는 메소드(method)를 정의 할 수 있는 간단한 Module입니다.

 

헬퍼를 테스트하려면 헬퍼 메소드의 출력이 예상 한 것과 일치하는지 확인하면 됩니다.

헬퍼와 관련된 테스트는  test/helpers  디렉토리에 있습니다.

 

아래 helper 예시를 봅시다 :

module UserHelper
  def link_to_user(user)
    link_to "#{user.first_name} #{user.last_name}", user
  end
end

method의 output을 다음과 같이 테스트 할 수 있습니다.

class UserHelperTest < ActionView::TestCase
  test "should return the user's full name" do
    user = users(:david)
 
    assert_dom_equal %{<a href="/user/#{user.id}">David Heinemeier Hansson</a>}, link_to_user(user)
  end
end

 

또한 테스트 Class는 ActionView::TestCase에서 확장되므로 link_to 또는 pluralize와 같은 Rails의 helper 메소드에 액세스 할 수 있습니다.

 

 

  • Testing Your Mailers

mailer classes를 테스트하려면 작업을 수행하기위한 특정 도구가 필요합니다.

 

1. Keeping the Postman in Check

Rails Application의 다른 모든 부분과 마찬가지로 mailer classes를 테스트하여 예상대로 작동하는지 확인해야합니다. :

  • 이메일 처리가 잘 되는지 (작성 및 전송)
  • 이메일 내용이 정확한지? (제목, 발신자, 본문 등)
  • 올바른 시간에 이메일이 전송되는지?

1) From All Sides

mailer 테스트에는 단위 테스트(unit tests)와 기능 테스트(functional tests)의 두 가지 측면이 있습니다.

  • 단위 테스트에서는 엄격하게 제어된 입력을 사용하여 메일러를 분리하여 실행하고, 출력을 알려진 값(fixtures)과 비교합니다.
  • 기능 테스트에서는 mailer가 생성한 세부 정보를 많이 테스트하지 않습니다. 대신 Controller와 Model이 Mailer를 올바르게 사용하고 있는지, 올바른 시간에 이메일이 전송되었음을 증명(assertion)하기 위해 테스트합니다.

 

2. Unit Testing

Mailer가 예상대로 작동하는지 테스트하기 위해 단위 테스트를 사용하여 미리 작성된 양식과 전송된 메일 내용과 비교할 수 있습니다.

 

1) Revenge of the Fixtures

Mailer의 단위 테스트를 위해 출력물이 어떻게 보일지에 대한 예를 제공하기 위해 fixtures가 사용됩니다. 이들은 다른 fixtures와 같이 Active Record 데이터가 아닌 이메일 sample이므로 다른 fixtures와는 별도로 자체 하위 디렉토리에 보관됩니다.  test/fixtures  내의 디렉토리 이름은 Mailer 이름과 같아야 합니다. 따라서 UserMailer라는 Mailer의 경우, fixtures들은  test/fixtures/user_mailer  디렉토리에 있어야합니다.

 

2) The Basic Test Case

아래 코드는 friend게 초대장을 보내는 데 사용되는 UserMailer라는 Mailer를 테스트하는 단위 테스트입니다. 초대 Action 생성을 위해 generator에서 생성된 기본 테스트 양식을 수정한 코드입니다.

require 'test_helper'
 
class UserMailerTest < ActionMailer::TestCase
  test "invite" do
    # Create the email and store it for further assertions
    email = UserMailer.create_invite('me@example.com',
                                     'friend@example.com', Time.now)
 
    # Send the email, then test that it got queued
    assert_emails 1 do
      email.deliver_now
    end
 
    # Test the body of the sent email contains what we expect it to
    assert_equal ['me@example.com'], email.from
    assert_equal ['friend@example.com'], email.to
    assert_equal 'You have been invited by me@example.com', email.subject
    assert_equal read_fixture('invite').join, email.body.to_s
  end
end

테스트에서 우리는 이메일을 보내고 return 된 객체를 email 변수에 저장합니다. 그 후, 메시지가 전송되었는지 확인하고 (첫 번째 assertion) 두 번째 asseerion에서 이메일에 실제로 예상 한 내용이 포함되었는지 확인합니다. read_fixture helper는 Email 양식의 내용을 확인하는데 사용됩니다.

 

invite fixture의 내용은 아래와 같습니다.

Hi friend@example.com,
 
You have been invited.
 
Cheers!

 

Mailer를 위한 테스트 작성에 대해 조금 더 이해해야 할 때입니다. 테스트 Environment 설정을 관리하는  config/environments/test.rb  파일 내에 있는 ActionMailer::Base.delivery_method = :test 코드는 전달 방법을 Test mode로 설정하여 이메일이 실제로 전달되지는 않지만(테스트 하는동안 Spam 피하는데 유용함) ActionMailer::Base.deliveries 에 배열 형태로 생성됩니다.

 

 참고  ActionMailer::Base.deliveries 의 배열은 ActionMailer::TestCaseActionDispatch::IntegrationTest 테스트에서만 자동으로 리셋 됩니다. 이러한 테스트 사례 이외의 깨끗한 slate를 원할 경우 ActionMailer::Base.deliveries.clear를 사용하여 수동으로 재설정 할 수 있습니다.

 

 

3. Functional Testing

Mailer의 기능 테스트에는 이메일 본문, 수신자 등이 잘 작성 되었는지 등을 확인니다. 메일 기능 테스트에서는 메일 전달 메소드를 호출 및 해당 이메일이 전달 목록에 추가되었는지 확인할 수 있습니다. deliver 메소드 자체가 작업을 수행한다고 가정하는 것이 좋습니다.

 

예를 들어, invite friend 작업에 대해 이메일을 잘 보내는지 확인할 수 있습니다.

require 'test_helper'
 
class UserControllerTest < ActionDispatch::IntegrationTest
  test "invite friend" do
    assert_difference 'ActionMailer::Base.deliveries.size', +1 do
      post invite_friend_url, params: { email: 'friend@example.com' }
    end
    invite_email = ActionMailer::Base.deliveries.last
 
    assert_equal "You have been invited by me@example.com", invite_email.subject
    assert_equal 'friend@example.com', invite_email.to[0]
    assert_match(/Hi friend@example.com/, invite_email.body.to_s)
  end
end

 

 

  • Testing Jobs

사용자 지정 job은 Application 내에서 서로 다른 level의 enqueue에서 대기 할 수 있으므로, 작업(enqueue에 있을 때의 동작)과 다른 엔티티가 enqueue에 잘 들어가졌는지 테스트해야합니다.

 

1. A Basic Test Case

기본적으로 job을 생성하면  test/jobs  디렉토리 아래에 연관된 테스트가 생성됩니다.

billing job으로 테스트하는 예제는 다음과 같습니다.

require 'test_helper'
 
class BillingJobTest < ActiveJob::TestCase
  test 'that account is charged' do
    BillingJob.perform_now(account, product)
    assert account.reload.charged_for?(product)
  end
end

간단한 위 예제는 예상대로 job을 완료한다고 주장(asserts)합니다.

 

기본적으로 ActiveJob::TestCase는 모든 테스트 실행 전에 이전에 수행되고 대기중인 모든 job이 지워지게 보장하므로 각 테스트 범위에서 이미 실행 된 작업이 없다고 가정 할 수 있습니다. 그렇다보니 Queue 어댑터를 :async 로 설정하여 job이 비동기 방식으로 수행된다고 '가정'하면 됩니다.

 

2. Custom Assertions And Testing Jobs Inside Other Components

Active Job에는 테스트의 상세한 정도를 줄이는데 사용할 수 있는 다양한 사용자 지정 assertion이 제공됩니다. 사용 가능한 assertion의 전체 목록은 ActiveJob::TestHelper API 문서를 참고하세요.

 

Job을 호출하는 모든 위치(예 : Controller 내부)에서 작업이 올바르게 enqueue에 포함되거나 수행되도록 하는 것이 좋습니다. Active Job에서 제공하는 사용자 지정 ​assertion이 매우 유용한 곳입니다.

 

다음은 Model에 대한 테스트 예시입니다.

require 'test_helper'
 
class ProductTest < ActiveJob::TestCase
  test 'billing job scheduling' do
    assert_enqueued_with(job: BillingJob) do
      product.charge(account)
    end
  end
end

 

 

  • Additional Testing Resources

1. Testing Time-Dependent Code

Rails는 시간(Time)에 민감한 코드가 예정대로 작동하도록 assertion 할 수있는 내장 helper method를 제공합니다.

다음은 travel_to helper 를 사용하는 예시코드 입니다. :

# Lets say that a user is eligible for gifting a month after they register.
user = User.create(name: 'Gaurish', activation_date: Date.new(2004, 10, 24))
assert_not user.applicable_for_gifting?
travel_to Date.new(2004, 11, 24) do
  assert_equal Date.new(2004, 10, 24), user.activation_date # inside the travel_to block `Date.current` is mocked
  assert user.applicable_for_gifting?
end
assert_equal Date.new(2004, 10, 24), user.activation_date # The change was visible only inside the `travel_to` block.

time helper에 대한 자세한 내용은 ActiveSupport::TimeHelpers API 문서를 참고해주세요.

 

 

 

댓글
댓글쓰기 폼
공지사항
Total
37,630
Today
22
Yesterday
231
링크
«   2020/08   »
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          
글 보관함