rubyonrails.org에서 더 보기:

다음은 https://guides.rubyonrails.org 에서 공식적으로 발행된 가이드입니다.

Rails 애플리케이션 테스팅

이 가이드는 Rails에 내장된 애플리케이션 테스트 메커니즘을 다룹니다.

이 가이드를 읽은 후에는 다음과 같은 내용을 알게 될 것입니다:

  • Rails 테스팅 용어
  • 애플리케이션의 unit, functional, integration, system 테스트를 작성하는 방법
  • 다른 인기 있는 테스팅 접근 방법과 플러그인

1 왜 Rails 애플리케이션을 위한 테스트를 작성해야 하나요?

Rails는 테스트 작성을 매우 쉽게 만듭니다. 모델과 컨트롤러를 생성하는 동안 기본 테스트 코드를 자동으로 생성하는 것으로 시작합니다.

Rails 테스트를 실행함으로써, 주요 코드 리팩토링 이후에도 코드가 원하는 기능을 준수하는지 확인할 수 있습니다.

Rails 테스트는 브라우저 요청을 시뮬레이션할 수 있으므로 브라우저를 통해 테스트하지 않고도 애플리케이션의 응답을 테스트할 수 있습니다.

2 테스팅 소개

테스트 지원은 처음부터 Rails의 기반으로 짜여졌습니다. "아! 테스트가 새롭고 멋지니까 테스트 실행 지원을 추가해보자"라는 갑작스러운 깨달음이 아니었습니다.

2.1 Rails는 처음부터 테스트를 위한 설정을 합니다

rails new application_name 을 사용해 Rails 프로젝트를 생성하면 Rails는 즉시 test 디렉토리를 생성합니다. 이 디렉토리의 내용을 보면 다음과 같습니다:

$ ls -F test
application_system_test_case.rb  controllers/                     helpers/                         mailers/                         system/
channels/                        fixtures/                        integration/                     models/                          test_helper.rb

helpers, mailers, models 디렉토리는 각각 view helper, mailer, model에 대한 테스트를 포함하기 위한 것입니다. channels 디렉토리는 Action Cable connection과 channel에 대한 테스트를 포함하기 위한 것입니다. controllers 디렉토리는 controller, route, view에 대한 테스트를 포함하기 위한 것입니다. integration 디렉토리는 controller 간의 상호작용에 대한 테스트를 포함하기 위한 것입니다.

system test 디렉토리는 애플리케이션의 전체 브라우저 테스트를 위한 system test를 포함합니다. System test는 사용자가 경험하는 방식으로 애플리케이션을 테스트할 수 있게 해주며 JavaScript도 테스트할 수 있도록 도와줍니다. System test는 Capybara를 상속받아 애플리케이션의 브라우저 내 테스트를 수행합니다.

Fixture는 테스트 데이터를 구성하는 방법이며, fixtures 디렉토리에 위치합니다.

관련 테스트가 처음 생성될 때 jobs 디렉토리도 생성됩니다.

test_helper.rb 파일은 테스트를 위한 기본 설정을 포함합니다.

application_system_test_case.rb는 system test를 위한 기본 설정을 포함합니다.

2.2 Test 환경

기본적으로 모든 Rails 애플리케이션은 development, test, production의 세 가지 환경을 가집니다.

각 환경의 설정은 비슷한 방식으로 수정할 수 있습니다. 이 경우, config/environments/test.rb에 있는 옵션을 변경하여 test 환경을 수정할 수 있습니다.

테스트는 RAILS_ENV=test 환경에서 실행됩니다.

2.3 Rails에서의 Minitest 사용

이전에 Rails 시작하기 가이드에서 bin/rails generate model 명령어를 사용했던 것을 기억하시나요? 우리의 첫 model을 생성했을 때, test 디렉토리에 test stub들이 함께 생성되었습니다:

$ bin/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의 기본 테스트 stub은 다음과 같습니다:

require "test_helper"

class ArticleTest < ActiveSupport::TestCase
  # test "진실" do
  #   assert true
  # end  
end

이 파일을 한 줄씩 검토하면 Rails testing 코드와 용어에 대해 이해하는데 도움이 될 것입니다.

require "test_helper"

이 파일인 test_helper.rb를 require하면 테스트를 실행하기 위한 기본 설정이 로드됩니다. 우리가 작성하는 모든 테스트에 이것을 포함시킬 것이므로, 이 파일에 추가된 모든 메서드는 모든 테스트에서 사용할 수 있습니다.

class ArticleTest < ActiveSupport::TestCase
  # ...
end

ArticleTest 클래스는 ActiveSupport::TestCase를 상속받기 때문에 test case를 정의합니다. 따라서 ArticleTestActiveSupport::TestCase에서 사용 가능한 모든 메서드를 가지고 있습니다. 이 가이드의 뒷부분에서 이러한 메서드들을 살펴볼 것입니다.

Minitest::Test(이는 ActiveSupport::TestCase의 상위 클래스입니다)를 상속받은 클래스 내에서 test_로 시작하는 모든 메서드는 단순히 테스트라고 불립니다. 따라서 test_passwordtest_valid_password로 정의된 메서드들은 유효한 테스트 이름이며 테스트 케이스가 실행될 때 자동으로 실행됩니다.

Rails는 또한 테스트 이름과 블록을 받는 test 메서드를 추가합니다. 이는 test_가 접두사로 붙은 메서드 이름으로 일반적인 Minitest::Unit 테스트를 생성합니다. 따라서 메서드 이름을 지정하는 것에 대해 걱정할 필요가 없으며, 다음과 같이 작성할 수 있습니다:

test "진실" do
  assert true
end

다음 코드를 작성하는 것과 거의 동일합니다:

def test_the_truth
  assert true
end

일반적인 메소드 정의를 여전히 사용할 수 있지만, test 매크로를 사용하면 더 읽기 쉬운 테스트 이름을 사용할 수 있습니다.

메소드 이름은 공백을 밑줄로 대체하여 생성됩니다. 하지만 결과가 유효한 Ruby 식별자일 필요는 없습니다 - 이름에 문장 부호 등이 포함될 수 있습니다. 이는 Ruby에서 기술적으로 어떤 문자열이든 메소드 이름이 될 수 있기 때문입니다. 이것이 제대로 작동하려면 define_methodsend 호출을 사용해야 할 수 있지만, 공식적으로는 이름에 대한 제한이 거의 없습니다.

다음으로, 우리의 첫 번째 assertion을 살펴보겠습니다:

assert true

어서션(assertion)은 기대한 결과에 대해 객체(또는 표현식)를 평가하는 코드입니다. 예를 들어, 어서션은 다음과 같은 것들을 확인할 수 있습니다:

  • 이 값이 저 값과 같은가?
  • 이 객체가 nil인가?
  • 이 코드가 exception을 발생시키는가?
  • 사용자의 비밀번호가 5자 이상인가?

모든 테스트는 하나 이상의 어서션을 포함할 수 있으며, 어서션 개수에는 제한이 없습니다. 모든 어서션이 성공했을 때만 테스트가 통과됩니다.

2.3.1 첫 실패하는 테스트

테스트 실패가 어떻게 보고되는지 보려면, article_test.rb 테스트 케이스에 실패하는 테스트를 추가할 수 있습니다.

test "제목 없이는 article이 저장되지 않아야 함" do
  article = Article.new
  assert_not article.save
end

이 새로 추가된 테스트를 실행해보겠습니다 (6은 테스트가 정의된 줄 번호입니다).

$ bin/rails test test/models/article_test.rb:6
Run options: --seed 44656

# 실행 중:

F

실패:
ArticleTest#test_should_not_save_article_without_title [/path/to/blog/test/models/article_test.rb:6]:
true가 nil 또는 false일 것으로 예상됨


bin/rails test test/models/article_test.rb:6



0.023918초 만에 완료, 41.8090 실행/초, 41.8090 assertion/초.

1 실행, 1 assertion, 1 실패, 0 에러, 0 건너뜀

출력에서 F는 실패를 나타냅니다. Failure 아래에 실패한 테스트의 이름과 함께 해당 추적이 표시됩니다. 다음 몇 줄에는 assertion이 예상한 값과 실제 값을 언급하는 메시지와 함께 stack trace가 포함되어 있습니다. 기본 assertion 메시지는 오류를 찾는 데 도움이 되는 최소한의 정보만 제공합니다. assertion 실패 메시지를 더 읽기 쉽게 만들기 위해, 모든 assertion은 다음과 같이 선택적 메시지 매개변수를 제공합니다:

test "제목이 없는 article은 저장되지 않아야 합니다" do
  article = Article.new
  assert_not article.save, "제목 없이 article이 저장되었습니다"
end

이 테스트를 실행하면 더 친숙한 assertion 메시지가 표시됩니다:

실패:
ArticleTest#test_should_not_save_article_without_title [/path/to/blog/test/models/article_test.rb:6]:
제목 없이 article이 저장됨

title 필드에 model level validation을 추가하면 이 테스트가 통과됩니다.

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

이제 테스트가 통과되어야 합니다. 테스트를 다시 실행하여 확인해보겠습니다:

$ bin/rails test test/models/article_test.rb:6
Run options: --seed 31252

# 실행 중:

.

0.027476초 만에 완료, 36.3952 runs/s, 36.3952 assertions/s.

1건 실행, 1건 assertion, 0건 실패, 0건 오류, 0건 건너뜀

방금 눈치채셨겠지만, 우리는 먼저 원하는 기능에 대해 실패하는 테스트를 작성하고, 그 다음 기능을 추가하는 코드를 작성한 뒤, 마지막으로 테스트가 통과되는 것을 확인했습니다. 이러한 소프트웨어 개발 방식을 Test-Driven Development (TDD)라고 합니다.

2.3.2 에러가 어떻게 보이는지

에러가 어떻게 보고되는지 확인하기 위해, 다음은 에러가 포함된 테스트입니다:

test "에러를 리포트 해야 한다" do
  # some_undefined_variable은 테스트 케이스의 다른 곳에서 정의되지 않았습니다
  some_undefined_variable 
  assert true
end

이제 테스트 실행에서 콘솔에서 더 많은 출력을 볼 수 있습니다:

$ bin/rails test test/models/article_test.rb  
Run options: --seed 1808

# 실행 중:

.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>'


bin/rails test test/models/article_test.rb:9



0.040609초 만에 완료, 49.2500 실행/초, 24.6250 assertions/초.

2 실행, 1 assertions, 0 실패, 1 오류, 0 건너뜀

출력의 'E'를 확인하세요. 이는 에러가 있는 테스트를 나타냅니다.

각 테스트 메서드의 실행은 에러나 assertion 실패가 발생하는 즉시 중단되며, 테스트 스위트는 다음 메서드로 진행됩니다. 모든 테스트 메서드는 무작위 순서로 실행됩니다. config.active_support.test_order 옵션을 사용하여 테스트 순서를 설정할 수 있습니다.

테스트가 실패하면 해당하는 backtrace가 표시됩니다. 기본적으로 Rails는 해당 backtrace를 필터링하여 애플리케이션과 관련된 라인만 출력합니다. 이는 프레임워크의 노이즈를 제거하고 여러분의 코드에 집중하는 데 도움을 줍니다. 하지만 전체 backtrace를 확인하고 싶은 상황이 있을 수 있습니다. -b(또는 --backtrace) 인자를 설정하여 이 동작을 활성화할 수 있습니다:

$ bin/rails test -b test/models/article_test.rb

이 테스트를 통과하게 하려면 다음과 같이 assert_raises를 사용하도록 수정할 수 있습니다:

test "에러를 보고해야 한다" do
  # some_undefined_variable은 테스트 케이스의 다른 곳에서 정의되지 않음
  assert_raises(NameError) do
    some_undefined_variable
  end 
end

이제 이 테스트가 통과해야 합니다.

2.4 사용 가능한 Assertions

지금까지 사용할 수 있는 assertion들의 일부를 살펴보았습니다. Assertion은 테스팅의 일꾼과 같습니다. 이들은 실제로 모든 것이 계획대로 진행되고 있는지 확인하는 검사를 수행합니다.

다음은 Rails의 기본 테스팅 라이브러리인 Minitest에서 사용할 수 있는 assertion들의 발췌본입니다. [msg] 매개변수는 테스트 실패 메시지를 더 명확하게 만들기 위해 지정할 수 있는 선택적 문자열 메시지입니다.

Assertion 목적
assert( test, [msg] ) test가 true인지 확인합니다.
assert_not( test, [msg] ) test가 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] ) objempty?인지 확인합니다.
assert_not_empty( obj, [msg] ) objempty?가 아닌지 확인합니다.
assert_match( regexp, string, [msg] ) 문자열이 정규표현식과 일치하는지 확인합니다.
assert_no_match( regexp, string, [msg] ) 문자열이 정규표현식과 일치하지 않는지 확인합니다.
assert_includes( collection, obj, [msg] ) objcollection에 포함되어 있는지 확인합니다.
assert_not_includes( collection, obj, [msg] ) objcollection에 포함되어 있지 않은지 확인합니다.
assert_in_delta( expected, actual, [delta], [msg] ) 숫자 expectedactual이 서로 delta 범위 내에 있는지 확인합니다.
assert_not_in_delta( expected, actual, [delta], [msg] ) 숫자 expectedactual이 서로 delta 범위 내에 없는지 확인합니다.
assert_in_epsilon ( expected, actual, [epsilon], [msg] ) 숫자 expectedactual의 상대 오차가 epsilon보다 작은지 확인합니다.
assert_not_in_epsilon ( expected, actual, [epsilon], [msg] ) 숫자 expectedactual의 상대 오차가 epsilon보다 작지 않은지 확인합니다.
assert_throws( symbol, [msg] ) { block } 주어진 블록이 symbol을 throw하는지 확인합니다.
assert_raises( exception1, exception2, ... ) { block } 주어진 블록이 주어진 예외 중 하나를 발생시키는지 확인합니다.
assert_instance_of( class, obj, [msg] ) objclass의 인스턴스인지 확인합니다.
assert_not_instance_of( class, obj, [msg] ) objclass의 인스턴스가 아닌지 확인합니다.
assert_kind_of( class, obj, [msg] ) objclass의 인스턴스이거나 이를 상속받은 것인지 확인합니다.
assert_not_kind_of( class, obj, [msg] ) objclass의 인스턴스가 아니며 이를 상속받지 않았는지 확인합니다.
assert_respond_to( obj, symbol, [msg] ) objsymbol에 응답하는지 확인합니다.
assert_not_respond_to( obj, symbol, [msg] ) objsymbol에 응답하지 않는지 확인합니다.
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_error_reported(class) { block } 에러 클래스가 보고되었는지 확인합니다. 예: assert_error_reported IOError { Rails.error.report(IOError.new("Oops")) }
assert_no_error_reported { block } 에러가 보고되지 않았는지 확인합니다. 예: assert_no_error_reported { perform_service }
flunk( [msg] ) 실패를 확인합니다. 아직 완성되지 않은 테스트를 명시적으로 표시할 때 유용합니다.

위의 내용은 minitest가 지원하는 assertion들의 일부입니다. 완전하고 더 최신의 목록을 보려면 Minitest API 문서, 특히 Minitest::Assertions를 확인해주세요.

테스팅 프레임워크의 모듈식 특성으로 인해 자신만의 assertion을 만드는 것이 가능합니다. 실제로 Rails가 바로 그렇게 하고 있습니다. Rails는 당신의 작업을 더 쉽게 만들기 위해 몇 가지 특화된 assertion들을 포함하고 있습니다.

자신만의 assertion을 만드는 것은 이 튜토리얼에서 다루지 않을 고급 주제입니다.

2.5 Rails 전용 Assertions

Rails는 minitest 프레임워크에 자체적으로 몇 가지 커스텀 assertion을 추가합니다:

Assertion 목적
[assert_difference(expressions, difference = 1, message = nil) {...}][] 제공된 블록을 실행한 결과로 표현식의 반환값의 수치적 차이를 테스트합니다.
[assert_no_difference(expressions, message = nil, &block)][] 제공된 블록을 실행하기 전과 후의 표현식을 평가한 수치 결과가 변경되지 않았음을 확인합니다.
[assert_changes(expressions, message = nil, from:, to:, &block)][] 제공된 블록을 실행한 후 표현식을 평가한 결과가 변경되었는지 테스트합니다.
[assert_no_changes(expressions, message = nil, &block)][] 제공된 블록을 실행한 후 표현식을 평가한 결과가 변경되지 않았는지 테스트합니다.
[assert_nothing_raised { block }][] 주어진 블록이 어떠한 예외도 발생시키지 않는지 확인합니다.
[assert_recognizes(expected_options, path, extras={}, message=nil)][] 주어진 경로의 라우팅이 올바르게 처리되었고 파싱된 옵션(expected_options 해시에 주어진)이 경로와 일치하는지 확인합니다. 기본적으로 Rails가 expected_options으로 주어진 라우트를 인식하는지 확인합니다.
[assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)][] 제공된 옵션이 제공된 경로를 생성하는데 사용될 수 있는지 확인합니다. 이는 assert_recognizes의 반대입니다. extras 파라미터는 쿼리 문자열에 들어갈 추가 요청 파라미터의 이름과 값을 요청에 알려주는데 사용됩니다. message 파라미터를 통해 assertion 실패시 커스텀 에러 메시지를 지정할 수 있습니다.
[assert_response(type, message = nil)][] 응답이 특정 상태 코드와 함께 오는지 확인합니다. 200-299를 나타내는 :success, 300-399를 나타내는 :redirect, 404를 나타내는 :missing, 또는 500-599 범위와 일치하는 :error를 지정할 수 있습니다. 명시적인 상태 번호나 그것의 심볼 등가물을 전달할 수도 있습니다. 자세한 정보는 전체 상태 코드 목록매핑 작동 방식을 참조하세요.
[assert_redirected_to(options = {}, message=nil)][] 응답이 주어진 옵션과 일치하는 URL로의 리다이렉트인지 확인합니다. assert_redirected_to root_path와 같은 named route와 assert_redirected_to @article와 같은 Active Record 객체도 전달할 수 있습니다.
[assert_queries_count(count = nil, include_schema: false, &block)][] &blockint 개수의 SQL 쿼리를 생성하는지 확인합니다.
[assert_no_queries(include_schema: false, &block)][] &block이 SQL 쿼리를 생성하지 않는지 확인합니다.
[assert_queries_match(pattern, count: nil, include_schema: false, &block)][] &block이 패턴과 일치하는 SQL 쿼리를 생성하는지 확인합니다.
[assert_no_queries_match(pattern, &block)][] &block이 패턴과 일치하는 SQL 쿼리를 생성하지 않는지 확인합니다.

이러한 assertion들의 사용법 중 일부를 다음 장에서 보게 될 것입니다.

2.6 Test Cases에 대한 간단한 설명

Minitest::Assertions에 정의된 assert_equal과 같은 모든 기본 assertions은 우리가 자체 test cases에서 사용하는 클래스에서도 사용할 수 있습니다. 실제로 Rails는 다음과 같은 상속 가능한 클래스들을 제공합니다:

이러한 각 클래스들은 Minitest::Assertions를 포함하고 있어서, 우리의 테스트에서 모든 기본 assertions를 사용할 수 있습니다.

Minitest에 대한 더 자세한 정보는 관련 문서를 참조하세요.

2.7 Transactions

기본적으로 Rails는 테스트를 데이터베이스 transaction으로 자동 감싸고, 테스트가 끝난 후에는 롤백됩니다. 이는 테스트들을 서로 독립적으로 만들어주며, 데이터베이스의 변경사항은 단일 테스트 내에서만 보이게 됩니다.

class MyTest < ActiveSupport::TestCase
  test "새로 생성된 user는 기본적으로 active 상태이다" do
    # test가 암시적으로 database transaction으로 감싸져 있기 때문에,
    # 여기서 생성된 user는 다른 test에서 볼 수 없습니다.
    assert User.create.active?
  end
end

ActiveRecord::Base.current_transaction 메서드는 여전히 의도한대로 동작합니다:

class MyTest < ActiveSupport::TestCase
  test "current_transaction" do
    # 테스트 주위의 암시적 transaction은 
    # current_transaction의 애플리케이션 레벨 의미에 영향을 주지 않습니다.
    assert User.current_transaction.blank?
  end
end

여러 개의 writing database가 있는 경우, 테스트는 각각의 트랜잭션으로 감싸지며 모두 롤백됩니다.

2.7.1 Test Transaction 제외하기

개별 테스트 케이스에서 제외할 수 있습니다:

class MyTest < ActiveSupport::TestCase
  # 이 테스트 케이스의 테스트들을 암묵적 database transaction으로 감싸지 않습니다.
  self.use_transactional_tests = false
end

2.8 Rails Test Runner

우리는 bin/rails test 명령어를 사용하여 모든 테스트를 한 번에 실행할 수 있습니다.

또는 테스트 케이스가 포함된 파일명을 bin/rails test 명령어에 전달하여 단일 테스트 파일을 실행할 수 있습니다.

$ bin/rails test test/models/article_test.rb
Run options: --seed 1559

# 실행 중:

..

0.027034초 만에 완료, 73.9810회 실행/초, 110.9715회 검증/초.

2회 실행, 3회 검증, 0회 실패, 0회 오류, 0회 건너뜀

이것은 test case의 모든 test method들을 실행할 것입니다.

-n 또는 --name flag와 test의 method name을 제공하여 test case의 특정 test method를 실행할 수도 있습니다.

$ bin/rails test test/models/article_test.rb -n test_the_truth
Run options: -n test_the_truth --seed 43583

# 실행 중:

.

0.009064초 내에 테스트 완료, 110.3266 테스트/초, 110.3266 assertion/초.

1개의 테스트, 1개의 assertion, 0개의 실패, 0개의 에러, 0개의 건너뜀

라인 번호를 지정하여 특정 라인에서 테스트를 실행할 수도 있습니다.

$ bin/rails test test/models/article_test.rb:6 # 특정 테스트와 라인 실행

라인 범위를 지정하여 테스트 범위를 실행할 수도 있습니다.

$ bin/rails test test/models/article_test.rb:6-20 # 6-20라인의 테스트를 실행

디렉토리 경로를 지정하여 디렉토리 내의 모든 테스트를 실행할 수도 있습니다.

$ bin/rails test test/controllers # 특정 디렉토리의 모든 test를 실행

테스트 runner는 빠른 실패, 테스트 실행 종료 시점의 출력 지연 등 많은 다른 기능도 제공합니다. 다음과 같이 test runner의 문서를 확인하세요:

$ bin/rails test -h
사용법:
  bin/rails test [PATHS...]

system test를 제외한 테스트 실행

예시:
    파일명 뒤에 라인 번호를 추가하여 단일 테스트를 실행할 수 있습니다:

        bin/rails test test/models/user_test.rb:27

    파일명 뒤에 라인 범위를 추가하여 여러 테스트를 실행할 수 있습니다:

        bin/rails test test/models/user_test.rb:10-20

    여러 파일과 디렉토리를 동시에 실행할 수 있습니다:

        bin/rails test test/controllers test/integration/login_test.rb

    기본적으로 테스트 실패와 에러는 실행 중에 인라인으로 보고됩니다.

minitest 옵션:
    -h, --help                       이 도움말을 표시합니다.
        --no-plugins                 minitest 플러그인 자동 로딩을 우회합니다(또는 $MT_NO_PLUGINS 설정).
    -s, --seed SEED                  랜덤 시드를 설정합니다. env를 통해서도 가능. 예: SEED=n rake
    -v, --verbose                    상세 출력. 파일 처리 진행 상황을 보여줍니다.
    -q, --quiet                      조용한 출력. 파일 처리 진행 상황을 보여주지 않습니다.
        --show-skips                 실행 마지막에 건너뛴 항목을 표시합니다.
    -n, --name PATTERN               /regexp/ 또는 문자열로 실행을 필터링합니다.
        --exclude PATTERN            실행에서 /regexp/ 또는 문자열을 제외합니다.
    -S, --skip CODES                 특정 유형의 결과 보고를 건너뜁니다(예: E).

알려진 확장: rails, pride 
    -w, --warnings                   Ruby 경고를 활성화하여 실행
    -e, --environment ENV            ENV 환경에서 테스트 실행
    -b, --backtrace                  전체 backtrace 표시
    -d, --defer-output               테스트 실행 후 테스트 실패와 에러 출력
    -f, --fail-fast                  첫 번째 실패나 에러 시 테스트 실행 중단
    -c, --[no-]color                 출력에 색상 활성화
        --profile [COUNT]            테스트 프로파일링을 활성화하고 가장 느린 테스트 케이스 목록 표시(기본값: 10)
    -p, --pride                      Pride. 테스팅 자부심을 보여주세요!

2.9 Continuous Integration (CI)에서 테스트 실행하기

CI 환경에서 모든 테스트를 실행하기 위해서는 다음 하나의 명령어만 필요합니다:

$ bin/rails test

System Test를 사용하는 경우, 속도가 느릴 수 있기 때문에 bin/rails test는 이를 실행하지 않습니다. System Test도 함께 실행하려면 bin/rails test:system을 실행하는 CI 단계를 추가하거나, 첫 번째 단계를 system test를 포함한 모든 테스트를 실행하는 bin/rails test:all로 변경하세요.

3 Parallel Testing

Parallel testing을 사용하면 테스트 스위트를 병렬화할 수 있습니다. 프로세스 포킹이 기본 방식이지만, 스레딩도 지원됩니다. 테스트를 병렬로 실행하면 전체 테스트 스위트를 실행하는 데 걸리는 시간이 줄어듭니다.

3.1 Parallel Testing with Processes

기본 병렬화 방법은 Ruby의 DRb 시스템을 사용하여 프로세스를 fork하는 것입니다. 프로세스는 제공된 worker 수에 따라 fork됩니다. 기본 수는 현재 사용중인 컴퓨터의 실제 코어 수이지만, parallelize 메서드에 전달된 숫자로 변경할 수 있습니다.

병렬화를 활성화하려면 test_helper.rb에 다음을 추가하세요:

class ActiveSupport::TestCase
  parallelize(workers: 2)
end

전달된 worker 수는 프로세스가 fork될 횟수입니다. 로컬 테스트 스위트를 CI와는 다르게 병렬화하고 싶을 수 있으므로, 테스트 실행에서 사용할 worker 수를 쉽게 변경할 수 있도록 환경 변수가 제공됩니다:

$ PARALLEL_WORKERS=15 bin/rails test

테스트를 병렬화할 때, Active Record는 각 프로세스에 대한 데이터베이스를 생성하고 데이터베이스에 schema를 로드하는 것을 자동으로 처리합니다. 데이터베이스에는 worker에 해당하는 번호가 접미사로 붙습니다. 예를 들어, 2개의 worker가 있다면 테스트는 각각 test-database-0test-database-1을 생성합니다.

전달된 worker의 수가 1 이하인 경우 프로세스가 fork되지 않고 테스트는 병렬화되지 않으며 원래의 test-database 데이터베이스를 사용합니다.

두 개의 hook이 제공되는데, 하나는 프로세스가 fork될 때 실행되고, 다른 하나는 fork된 프로세스가 종료되기 전에 실행됩니다. 앱이 여러 데이터베이스를 사용하거나 worker 수에 따라 달라지는 다른 작업을 수행하는 경우 유용할 수 있습니다.

parallelize_setup 메서드는 프로세스가 fork된 직후에 호출됩니다. parallelize_teardown 메서드는 프로세스가 종료되기 직전에 호출됩니다.

class ActiveSupport::TestCase
  parallelize_setup do |worker|
    # 데이터베이스 설정
  end

  parallelize_teardown do |worker|
    # 데이터베이스 정리
  end

  parallelize(workers: :number_of_processors)
end

이 메소드들은 thread를 사용한 parallel testing에서는 필요하지 않거나 사용할 수 없습니다.

3.2 스레드를 사용한 병렬 테스트

스레드를 선호하거나 JRuby를 사용하는 경우, 스레드 기반 병렬화 옵션을 사용할 수 있습니다. 스레드 병렬화는 Minitest의 Parallel::Executor를 기반으로 합니다.

병렬화 방식을 fork 대신 스레드를 사용하도록 변경하려면 test_helper.rb에 다음을 추가하세요:

class ActiveSupport::TestCase
  parallelize(workers: :number_of_processors, with: :threads)
end

번역: parallelization은 test 파일들을 여러 프로세스나 스레드로 나누어 실행함으로써 test suite의 실행 속도를 높여줍니다.

JRuby나 TruffleRuby에서 생성된 Rails 애플리케이션은 자동으로 with: :threads 옵션을 포함합니다.

parallelize에 전달된 worker 수는 테스트가 사용할 스레드 수를 결정합니다. CI 환경과 로컬 테스트 환경에서 테스트 병렬화를 다르게 설정하고 싶을 수 있습니다. 이를 위해 테스트 실행 시 worker 수를 쉽게 변경할 수 있는 환경 변수가 제공됩니다:

$ PARALLEL_WORKERS=15 bin/rails test

3.3 병렬 트랜잭션 테스트하기

스레드에서 병렬 데이터베이스 트랜잭션을 실행하는 코드를 테스트하려고 할 때, 이미 암시적 테스트 트랜잭션 하에 중첩되어 있기 때문에 서로를 차단할 수 있습니다.

이를 해결하기 위해 self.use_transactional_tests = false를 설정하여 테스트 케이스 클래스에서 트랜잭션을 비활성화할 수 있습니다:

class WorkerTest < ActiveSupport::TestCase
  self.use_transactional_tests = false

  test "병렬 트랜잭션" do
    # 트랜잭션을 생성하는 몇 개의 스레드를 시작
  end
end

transactional tests가 비활성화된 경우, 테스트 완료 후 변경 사항이 자동으로 롤백되지 않으므로 테스트가 생성한 모든 데이터를 정리해야 합니다.

3.4 테스트 병렬화 임계값

테스트를 병렬로 실행하면 데이터베이스 설정과 fixture 로딩에서 오버헤드가 발생합니다. 이러한 이유로 Rails는 50개 미만의 테스트가 포함된 실행은 병렬화하지 않습니다.

test.rb에서 이 임계값을 설정할 수 있습니다:

config.active_support.test_parallelization_threshold = 100

병렬 테스트 실행을 위한 최소 테스트 파일 수를 설정합니다. 이 값보다 적은 테스트 파일이 있다면 테스트가 순차적으로 실행됩니다. 기본값은 50입니다.

그리고 테스트 케이스 레벨에서 parallelization을 설정할 때도 마찬가지입니다:

class ActiveSupport::TestCase
  parallelize threshold: 100
end

한계값이 100인 병렬 테스트의 경우, 100개 이하의 테스트를 가진 test 클래스는 비용이 크고 병렬로 실행되지 않습니다.

테스트 데이터베이스

거의 모든 Rails 애플리케이션은 데이터베이스와 많은 상호작용을 하며, 결과적으로 테스트에서도 상호작용할 데이터베이스가 필요합니다. 효율적인 테스트를 작성하려면 이 데이터베이스를 설정하고 샘플 데이터로 채우는 방법을 이해해야 합니다.

기본적으로 모든 Rails 애플리케이션은 development, test, production 세 가지 환경을 가지고 있습니다. 각 환경의 데이터베이스는 config/database.yml에서 구성됩니다.

전용 test 데이터베이스를 사용하면 테스트 데이터를 격리된 상태로 설정하고 상호작용할 수 있습니다. 이를 통해 development나 production 데이터베이스의 데이터를 걱정하지 않고 테스트 데이터를 자신있게 조작할 수 있습니다.

3.5 테스트 데이터베이스 스키마 유지하기

테스트를 실행하기 위해서는, 테스트 데이터베이스가 현재 구조를 가지고 있어야 합니다. 테스트 helper는 테스트 데이터베이스에 pending migration이 있는지 확인합니다. 테스트 helper는 db/schema.rb 또는 db/structure.sql을 테스트 데이터베이스에 로드하려고 시도할 것입니다. 만약 pending migration이 있다면, 에러가 발생할 것입니다. 일반적으로 이는 schema가 완전히 migrate되지 않았음을 나타냅니다. 개발 데이터베이스에 대해 migration을 실행하면(bin/rails db:migrate) schema를 최신 상태로 만들 수 있습니다.

기존 migration이 수정된 경우, 테스트 데이터베이스를 재구축해야 합니다. 이는 bin/rails test:db를 실행하여 수행할 수 있습니다.

3.6 The Low-Down on Fixtures

For good tests, you'll need to give some thought to setting up test data. In Rails, you can handle this by defining and customizing fixtures. You can find comprehensive documentation in the Fixtures API documentation.

3.6.1 What are Fixtures?

Fixtures is a fancy word for sample data. Fixtures allow you to populate your testing database with predefined data before your tests run. Fixtures are database independent and written in YAML. There is one file per model.

Fixtures are not designed to create every object that your tests need, and are best managed when only used for default data that can be applied to the common case.

You'll find fixtures under your test/fixtures directory. When you run bin/rails generate model to create a new model, Rails automatically creates fixture stubs in this directory.

3.6.2 YAML

YAML-formatted fixtures are a human-friendly way to describe your sample data. These types of fixtures have the .yml file extension (as in users.yml).

Here's a sample YAML fixture file:

# lo & behold! 나는 YAML 주석입니다!
david:
  name: David Heinemeier Hansson
  birthday: 1979-10-15
  profession: 시스템 개발

steve:
  name: Steve Ross Kellock
  birthday: 1974-09-27
  profession: 키보드를 가진 사람

각 fixture는 이름과 함께 들여쓰기된 콜론으로 구분된 키/값 쌍의 목록으로 구성됩니다. 레코드는 일반적으로 빈 줄로 구분됩니다. fixture 파일의 첫 번째 열에 # 문자를 사용하여 주석을 추가할 수 있습니다.

associations를 사용하는 경우, 두 개의 서로 다른 fixture 간에 참조 노드를 정의할 수 있습니다. 다음은 belongs_to/has_many association의 예시입니다:

# test/fixtures/categories.yml
about:
  name: About
# test/fixtures/articles.yml
first:
  title: Rails에 오신 것을 환영합니다!
  category: about
# test/fixtures/action_text/rich_texts.yml
first_content:
  record: first (Article)
  name: content
  body: <div>안녕하세요, <strong>fixture로부터</strong></div>

fixtures/articles.ymlfirst Article의 category 키는 about 값을 가지고 있고, fixtures/action_text/rich_texts.ymlfirst_content 항목의 record 키는 first (Article) 값을 가지고 있습니다. 이는 전자의 경우 Active Record가 fixtures/categories.yml에서 찾은 Category about을 로드하고, 후자의 경우 Action Text가 fixtures/articles.yml에서 찾은 Article first를 로드하도록 지시합니다.

연관관계에서 서로를 이름으로 참조하기 위해서는, 연관된 fixture에 id: 속성을 지정하는 대신 fixture 이름을 사용할 수 있습니다. Rails는 실행 간에 일관성을 유지하도록 primary key를 자동으로 할당합니다. 이러한 연관 동작에 대한 자세한 정보는 Fixtures API 문서를 참조하세요.

3.6.3 File Attachment Fixtures

다른 Active Record 기반 모델과 마찬가지로, Active Storage attachment 레코드는 ActiveRecord::Base 인스턴스를 상속받으므로 fixture로 채울 수 있습니다.

thumbnail attachment로 이미지가 연결된 Article 모델과 fixture 데이터 YAML을 살펴보겠습니다:

class Article < ApplicationRecord
  has_one_attached :thumbnail
end
# test/fixtures/articles.yml
first:
  title: 게시글

[image/png][]로 인코딩된 파일이 test/fixtures/files/first.png에 있다고 가정할 때, 다음 YAML fixture 항목들은 관련된 ActiveStorage::BlobActiveStorage::Attachment 레코드들을 생성할 것입니다:

# test/fixtures/active_storage/blobs.yml
first_thumbnail_blob: <%= ActiveStorage::FixtureSet.blob filename: "first.png" %>
# test/fixtures/active_storage/attachments.yml
first_thumbnail_attachment:
  name: thumbnail
  record: first (Article)  
  blob: first_thumbnail_blob

3.6.4 ERB로 작업하기

ERB를 사용하면 템플릿 내에 Ruby 코드를 포함할 수 있습니다. Rails가 fixture를 로드할 때 YAML fixture 형식은 ERB로 사전 처리됩니다. 이를 통해 Ruby를 사용하여 샘플 데이터를 생성할 수 있습니다. 예를 들어, 다음 코드는 천 명의 사용자를 생성합니다:

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

3.6.5 실전에서의 Fixture

Rails는 기본적으로 test/fixtures 디렉터리의 모든 fixture를 자동으로 로드합니다. 로딩은 다음 3단계로 이루어집니다:

  1. fixture에 해당하는 테이블의 기존 데이터를 모두 삭제
  2. fixture 데이터를 테이블에 로드
  3. 직접 접근이 필요한 경우를 위해 fixture 데이터를 메서드로 덤프

데이터베이스의 기존 데이터를 제거하기 위해, Rails는 referential integrity trigger(외래 키와 체크 제약 조건 등)를 비활성화하려 시도합니다. 테스트 실행 시 귀찮은 권한 에러가 발생한다면, 데이터베이스 사용자가 테스트 환경에서 이러한 trigger를 비활성화할 수 있는 권한을 가지고 있는지 확인하세요. (PostgreSQL에서는 superuser만이 모든 trigger를 비활성화할 수 있습니다. PostgreSQL 권한에 대해 여기에서 더 자세히 읽어보세요).

3.6.6 Fixture는 Active Record Object입니다

Fixture는 Active Record의 인스턴스입니다. 위의 3번 항목에서 언급했듯이, fixture는 테스트 케이스의 로컬 스코프에서 메서드로 자동 사용할 수 있기 때문에 직접 접근이 가능합니다. 예를 들어:

# david라는 이름의 fixture에 대한 User 객체를 반환합니다
users(:david)

# david의 id라는 속성을 반환합니다
users(:david).id

# User 클래스에서 사용 가능한 메서드에도 접근할 수 있습니다
david = users(:david)
david.call(david.partner)

여러 fixture를 한 번에 가져오려면 fixture 이름 목록을 전달할 수 있습니다. 예를 들면:

# david와 steve fixture를 포함한 array를 리턴할 것입니다
users(:david, :steve)

4 Model 테스트

Model 테스트는 애플리케이션의 다양한 model을 테스트하는 데 사용됩니다.

Rails model 테스트는 test/models 디렉토리에 저장됩니다. Rails는 model 테스트 스켈레톤을 생성하는 generator를 제공합니다.

$ bin/rails generate test_unit:model article title:string body:text
create  test/models/article_test.rb
create  test/fixtures/articles.yml

Model test는 ActionMailer::TestCase와 같은 자체 superclass를 가지고 있지 않습니다. 대신 ActiveSupport::TestCase를 상속받습니다.

5 System Testing

System test를 사용하면 실제 브라우저나 headless 브라우저에서 테스트를 실행하여 애플리케이션과 사용자 상호작용을 테스트할 수 있습니다. System test는 내부적으로 Capybara를 사용합니다.

Rails system test를 생성하기 위해서는 애플리케이션의 test/system 디렉토리를 사용합니다. Rails는 system test 스켈레톤을 생성하는 generator를 제공합니다.

$ bin/rails generate system_test users
      invoke test_unit
      create test/system/users_test.rb

새로 생성된 system test는 다음과 같습니다:

require "application_system_test_case"

class UsersTest < ApplicationSystemTestCase
  # test "index 페이지 방문하기" do 
  #   visit users_url 
  #
  #   assert_selector "h1", text: "Users"
  # end
end

기본적으로 system test는 Selenium driver를 사용하여 Chrome 브라우저에서 1400x1400의 화면 크기로 실행됩니다. 다음 섹션에서는 이러한 기본 설정을 변경하는 방법을 설명합니다.

기본적으로 Rails는 테스트 중에 발생하는 예외를 처리하고 HTML 오류 페이지로 응답하려고 시도합니다. 이 동작은 config.action_dispatch.show_exceptions 설정을 통해 제어할 수 있습니다.

5.1 기본 설정 변경하기

Rails는 system test의 기본 설정을 변경하는 것을 매우 간단하게 만듭니다. 모든 setup이 추상화되어 있어서 테스트 작성에만 집중할 수 있습니다.

새로운 애플리케이션이나 scaffold를 생성할 때, application_system_test_case.rb 파일이 test 디렉토리에 생성됩니다. 이곳이 system test의 모든 설정이 있어야 하는 곳입니다.

기본 설정을 변경하고 싶다면 system test가 "driven by"하는 것을 변경할 수 있습니다. Selenium에서 Cuprite로 driver를 변경하고 싶다고 가정해봅시다. 먼저 cuprite gem을 Gemfile에 추가하세요. 그런 다음 application_system_test_case.rb 파일에서 다음과 같이 하세요:

require "test_helper"
require "capybara/cuprite"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :cuprite
end

위의 코드 파일은 system test를 위한 코드입니다. Capybara를 위한 Cuprite 드라이버를 사용하는 설정을 포함하고 있습니다.

driven_by의 드라이버 이름은 필수 인자입니다. driven_by에 전달할 수 있는 선택적 인자는 브라우저를 지정하는 :using(이는 Selenium에서만 사용됨), 스크린샷용 화면 크기를 변경하는 :screen_size, 그리고 드라이버가 지원하는 옵션을 설정하는 데 사용할 수 있는 :options가 있습니다.

require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :firefox 
end

Firefox 브라우저를 사용하는 Selenium으로 구동되는 시스템 테스트를 수행하기 위한 기본 클래스입니다.

만약 headless 브라우저를 사용하고 싶다면, :using 인자에 headless_chrome 또는 headless_firefox를 추가하여 Headless Chrome이나 Headless Firefox를 사용할 수 있습니다.

require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :headless_chrome
end

원격 브라우저(예: Docker의 Headless Chrome)를 사용하려면, remote url을 추가하고 options를 통해 browser를 remote로 설정해야 합니다.

require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  url = ENV.fetch("SELENIUM_REMOTE_URL", nil)  
  options = if url
    { browser: :remote, url: url }
  else
    { browser: :chrome }
  end
  driven_by :selenium, using: :headless_chrome, options: options
end

위와 같이 설정하면 테스트 코드가 CI 서버에서는 remote Selenium 서버를 사용하고, 개발환경에서는 로컬 Chrome을 사용하게 됩니다.

이제 remote browser에 대한 연결이 생성되어야 합니다.

$ SELENIUM_REMOTE_URL=http://localhost:4444/wd/hub bin/rails test:system

만약 테스트 중인 애플리케이션이 Docker 컨테이너와 같은 원격 환경에서도 실행되는 경우, Capybara는 원격 서버를 호출하는 방법에 대한 추가 설정이 필요합니다.

require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  def setup
    Capybara.server_host = "0.0.0.0" # 모든 인터페이스에 바인딩
    Capybara.app_host = "http://#{IPSocket.getaddress(Socket.gethostname)}" if ENV["SELENIUM_REMOTE_URL"].present?
    super
  end
  # ...
end

이제 Docker 컨테이너나 CI에서 실행되는지 여부와 관계없이 remote browser와 서버에 대한 연결을 얻을 수 있습니다.

Capybara 설정이 Rails에서 제공하는 것보다 더 많은 설정을 필요로 하는 경우, 이러한 추가 설정은 application_system_test_case.rb 파일에 추가할 수 있습니다.

추가 설정에 대해서는 Capybara의 문서를 참조하세요.

5.2 Screenshot Helper

ScreenshotHelper는 테스트의 스크린샷을 캡처하도록 설계된 헬퍼입니다. 이는 테스트가 실패한 시점의 브라우저를 확인하거나, 디버깅을 위해 나중에 스크린샷을 확인하는 데 도움이 될 수 있습니다.

두 가지 메서드가 제공됩니다: take_screenshottake_failed_screenshot입니다. take_failed_screenshot는 Rails 내부의 before_teardown에 자동으로 포함됩니다.

take_screenshot 헬퍼 메서드는 브라우저의 스크린샷을 찍기 위해 테스트 어디에나 포함될 수 있습니다.

5.3 System Test 구현하기

이제 우리의 blog 애플리케이션에 system test를 추가해보겠습니다. index 페이지를 방문하고 새로운 blog article을 생성하는 과정을 통해 system test를 작성하는 방법을 설명하겠습니다.

scaffold generator를 사용했다면, system test 스켈레톤이 자동으로 생성되었을 것입니다. scaffold generator를 사용하지 않았다면, 먼저 system test 스켈레톤을 생성하는 것부터 시작하세요.

$ bin/rails generate system_test articles

테스트 파일 placeholder를 생성했을 것입니다. 이전 명령어의 출력에서 다음과 같은 내용을 볼 수 있습니다:

      invoke  test_unit
      create    test/system/articles_test.rb

이제 파일을 열어서 우리의 첫 assertion을 작성해봅시다:

require "application_system_test_case"

class ArticlesTest < ApplicationSystemTestCase
  test "인덱스 보기" do
    visit articles_path
    assert_selector "h1", text: "Articles"  
  end
end

테스트는 articles index 페이지에 h1이 있다는 것을 확인하고 통과해야 합니다.

system test를 실행하세요.

$ bin/rails test:system

기본적으로 bin/rails test를 실행하면 system test는 실행되지 않습니다. system test를 실제로 실행하려면 bin/rails test:system을 실행해야 합니다. 모든 테스트(system test 포함)를 실행하려면 bin/rails test:all을 실행할 수도 있습니다.

5.3.1 Articles System Test 생성하기

이제 블로그에서 새 article을 생성하는 플로우를 테스트해보겠습니다.

test "Article을 생성해야 함" do
  visit articles_path

  click_on "New Article"

  fill_in "Title", with: "Creating an Article"
  fill_in "Body", with: "Created this article successfully!"

  click_on "Create Article"

  assert_text "Creating an Article"
end

첫 번째 단계는 visit articles_path를 호출하는 것입니다. 이것은 테스트를 articles index 페이지로 이동시킬 것입니다.

그 다음 click_on "New Article"은 index 페이지에서 "New Article" 버튼을 찾을 것입니다. 이것은 브라우저를 /articles/new로 리다이렉트할 것입니다.

그런 다음 테스트는 지정된 텍스트로 article의 title과 body를 채울 것입니다. 필드들이 채워지면, "Create Article"이 클릭되어 새로운 article을 데이터베이스에 생성하기 위한 POST 요청을 보낼 것입니다.

우리는 articles index 페이지로 다시 리다이렉트되고, 거기서 새로운 article의 제목 텍스트가 articles index 페이지에 있는지 assert합니다.

5.3.2 여러 화면 크기에 대한 테스트

데스크톱 테스트 외에도 모바일 크기에 대해 테스트하고 싶다면, ActionDispatch::SystemTestCase를 상속받는 또 다른 클래스를 생성하여 테스트 스위트에서 사용할 수 있습니다. 이 예제에서는 /test 디렉토리에 다음 구성으로 mobile_system_test_case.rb 파일이 생성됩니다.

require "test_helper"

class MobileSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :chrome, screen_size: [375, 667]
end

iphone 8 크기와 같은 크기로 mobile system test를 설정하고 싶다면, base class로부터 상속받은 모든 test에서 이러한 화면 크기를 사용할 수 있습니다.

test/system 안에 MobileSystemTestCase를 상속받는 테스트를 생성하여 이 configuration을 사용할 수 있습니다. 이제 여러 다른 configuration을 사용하여 앱을 테스트할 수 있습니다.

require "mobile_system_test_case"

class PostsTest < MobileSystemTestCase
  test "index 방문하기" do
    visit posts_url
    assert_selector "h1", text: "Posts"
  end
end

5.3.3 Taking It Further

시스템 테스팅의 아름다움은 컨트롤러, 모델, 뷰와의 사용자 상호작용을 테스트한다는 점에서 integration testing과 유사하지만, 시스템 테스팅은 훨씬 더 견고하며 실제 사용자가 사용하는 것처럼 애플리케이션을 테스트한다는 것입니다. 앞으로 댓글 작성, 게시글 삭제, 임시 게시글 발행 등 사용자가 애플리케이션에서 할 수 있는 모든 것을 테스트할 수 있습니다.

6 Integration Testing

Integration test는 애플리케이션의 다양한 부분들이 어떻게 상호작용하는지 테스트하는 데 사용됩니다. 일반적으로 애플리케이션 내의 중요한 워크플로우를 테스트하는 데 사용됩니다.

Rails integration test를 만들기 위해서는 애플리케이션의 test/integration 디렉토리를 사용합니다. Rails는 integration test 스켈레톤을 생성하는 generator를 제공합니다.

$ bin/rails generate integration_test user_flows
      exists  test/integration/
      create  test/integration/user_flows_test.rb

새로 생성된 integration test는 다음과 같습니다:

require "test_helper"

class UserFlowsTest < ActionDispatch::IntegrationTest
  # test "진실" do
  #   assert true
  # end
end

여기서 테스트는 ActionDispatch::IntegrationTest를 상속받습니다. 이를 통해 integration test에서 사용할 수 있는 추가적인 helper들이 제공됩니다.

기본적으로 Rails는 테스트 중에 발생하는 exception들을 rescue하고 HTML 에러 페이지로 응답하려 시도합니다. 이 동작은 config.action_dispatch.show_exceptions 설정을 통해 제어할 수 있습니다.

6.1 Integration Test에서 사용 가능한 Helper들

표준 testing helper 외에도, ActionDispatch::IntegrationTest를 상속하면 integration test를 작성할 때 사용할 수 있는 추가적인 helper들이 제공됩니다. 선택할 수 있는 세 가지 카테고리의 helper들을 간단히 살펴보겠습니다.

integration test runner를 다루기 위해서는 ActionDispatch::Integration::Runner를 참조하세요.

request를 수행할 때는 ActionDispatch::Integration::RequestHelpers를 사용할 수 있습니다.

파일을 업로드해야 하는 경우에는 ActionDispatch::TestProcess::FixtureFile를 참조하세요.

session이나 integration test의 상태를 수정해야 하는 경우에는 ActionDispatch::Integration::Session을 참조하세요.

6.2 Integration Test 구현하기

우리의 블로그 애플리케이션에 integration test를 추가해보겠습니다. 새로운 블로그 글을 작성하는 기본적인 워크플로우부터 시작하여, 모든 것이 제대로 작동하는지 확인해보겠습니다.

먼저 integration test 스켈레톤을 생성하는 것부터 시작하겠습니다:

$ bin/rails generate integration_test blog_flow

테스트 파일의 placeholder가 생성되어야 합니다. 이전 명령어의 출력 결과는 다음과 같습니다:

      invoke  test_unit
      create    test/integration/blog_flow_test.rb

이제 파일을 열고 우리의 첫 번째 assertion을 작성해볼까요:

require "test_helper"

class BlogFlowTest < ActionDispatch::IntegrationTest
  test "웰컴 페이지를 볼 수 있다" do
    get "/"
    assert_select "h1", "Welcome#index" 
  end
end

assert_select를 살펴보고 Testing Views 섹션에서 request의 결과로 생성된 HTML을 쿼리해보겠습니다. 이는 주요 HTML 요소와 그 내용의 존재 여부를 확인하여 request에 대한 응답을 테스트하는 데 사용됩니다.

root path에 접속하면 view에 welcome/index.html.erb가 렌더링되어야 합니다. 따라서 이 assertion은 통과해야 합니다.

6.2.1 Articles Integration 생성하기

블로그에 새 article을 생성하고 그 결과로 생성된 article을 확인하는 기능을 테스트해보는 것은 어떨까요?

test "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을 호출합니다. 이 응답은 성공적이어야 합니다.

그 후 Articles controller의 :create action에 post request를 보냅니다:

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

이 코드는 "/articles"로 POST 요청을 보내고, article의 title과 body를 포함하는 params를 함께 전송합니다. 그리고 redirect 응답이 있는지 확인하고 redirect를 따라갑니다.

요청 이후의 두 줄은 새로운 article을 생성할 때 설정한 리다이렉트를 처리하기 위한 것입니다.

리다이렉트 이후에 후속 요청을 할 계획이 있다면 follow_redirect!를 호출하는 것을 잊지 마세요.

마지막으로 우리는 응답이 성공적이었고 새로운 article이 페이지에서 읽을 수 있다는 것을 검증할 수 있습니다.

6.2.2 더 나아가기

우리는 블로그를 방문하고 새로운 article을 생성하는 매우 작은 워크플로우를 성공적으로 테스트할 수 있었습니다. 이를 더 발전시키고 싶다면 댓글 작성, article 삭제, 또는 댓글 수정에 대한 테스트를 추가할 수 있습니다. Integration 테스트는 우리 애플리케이션의 모든 종류의 사용 사례를 실험해볼 수 있는 좋은 장소입니다.

7 Controller를 위한 Functional 테스트

Rails에서 controller의 다양한 action을 테스트하는 것은 functional 테스트를 작성하는 한 형태입니다. controller는 애플리케이션에 들어오는 웹 요청을 처리하고 결국 렌더링된 view로 응답한다는 것을 기억하세요. functional 테스트를 작성할 때, 여러분은 action이 요청을 어떻게 처리하는지와 예상되는 결과 또는 응답(일부 경우에는 HTML view)을 테스트하게 됩니다.

7.1 Functional Tests에 포함해야 할 것

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

  • 웹 요청이 성공적이었는가?
  • 사용자가 올바른 페이지로 리다이렉트되었는가?
  • 사용자 인증이 성공적으로 이루어졌는가?
  • view에서 적절한 메시지가 사용자에게 표시되었는가?
  • 응답에서 올바른 정보가 표시되었는가?

functional tests를 실제로 확인하는 가장 쉬운 방법은 scaffold generator를 사용하여 controller를 생성하는 것입니다:

$ bin/rails generate scaffold_controller article title:string body:text
...
create  app/controllers/articles_controller.rb
...
invoke  test_unit
create    test/controllers/articles_controller_test.rb
...

이것은 Article 리소스에 대한 controller 코드와 테스트를 생성할 것입니다. test/controllers 디렉토리에 있는 articles_controller_test.rb 파일을 살펴볼 수 있습니다.

controller가 이미 있고 7개의 기본 action 각각에 대한 테스트 scaffold 코드만 생성하고 싶다면, 다음 명령어를 사용할 수 있습니다:

$ bin/rails generate test_unit:scaffold article
...
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 "index를 가져와야 함" do
    get articles_url
    assert_response :success
  end
end

test_should_get_index 테스트에서 Rails는 index라는 액션에 대한 요청을 시뮬레이션하여 요청이 성공적이었는지 확인하고 올바른 응답 본문이 생성되었는지도 확인합니다.

get 메서드는 웹 요청을 시작하고 결과를 @response에 채웁니다. 최대 6개의 인자를 받을 수 있습니다:

  • 요청하는 컨트롤러 액션의 URI. 문자열이나 라우트 헬퍼(예: articles_url) 형태가 될 수 있습니다.
  • params: 액션에 전달할 요청 매개변수의 해시를 포함하는 옵션 (예: 쿼리 문자열 매개변수나 article 변수).
  • headers: 요청과 함께 전달될 헤더를 설정합니다.
  • env: 필요에 따라 요청 환경을 커스터마이즈합니다.
  • xhr: 요청이 Ajax 요청인지 여부. true로 설정하여 요청을 Ajax로 표시할 수 있습니다.
  • as: 다른 content type으로 요청을 인코딩합니다.

이러한 키워드 인자들은 모두 선택사항입니다.

예시: 첫 번째 Article:show 액션을 호출하면서 HTTP_REFERER 헤더를 전달:

get article_url(Article.first), headers: { "HTTP_REFERER" => "http://example.com/home" }

다른 예: Ajax 요청으로 마지막 Article:update action을 호출하면서 paramstitle의 새로운 텍스트를 전달:

patch article_url(Article.last), params: { article: { title: "updated" } }, xhr: true

xhr 옵션을 true로 설정하여 Article의 마지막 레코드에 대해 Ajax 요청을 생성합니다

:titleparams로 전달하여 새로운 article을 생성하기 위해 :create action을 호출하는 또 다른 예시입니다(JSON request로):

post articles_path, params: { article: { title: "Ahoy!" } }, as: :json
```를 사용하여 JSON 형식으로 "Ahoy!"라는 제목의 article을 생성하는 POST 요청을 보냅니다.

NOTE: 만약 `articles_controller_test.rb`에서 `test_should_create_article` 테스트를 실행하면, 새로 추가된 model level validation으로 인해 실패할 것이며 이는 당연한 결과입니다.

`articles_controller_test.rb`의 `test_should_create_article` 테스트를 수정하여 모든 테스트가 통과하도록 만들어 봅시다:

```ruby
test "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

이제 모든 테스트를 실행하면 모든 테스트가 통과할 것입니다.

만약 Basic Authentication 섹션의 단계들을 따랐다면, 모든 테스트를 통과시키기 위해서는 각 요청 헤더에 인증을 추가해야 합니다:

post articles_url, params: { article: { body: "Rails는 멋져요!", title: "Hello Rails" } }, headers: { Authorization: ActionController::HttpAuthentication::Basic.encode_credentials("dhh", "secret") }

기본적으로 Rails는 테스트 중에 발생하는 exception을 처리하고 HTML error page로 응답하려고 시도합니다. 이 동작은 config.action_dispatch.show_exceptions 설정으로 제어할 수 있습니다.

7.2 Functional Tests에서 사용 가능한 Request 유형

HTTP 프로토콜에 익숙하다면, get이 request 유형 중 하나라는 것을 알 것입니다. Rails functional tests에서는 6가지 request 유형을 지원합니다:

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

모든 request 유형에는 사용할 수 있는 동등한 메서드가 있습니다. 일반적인 C.R.U.D. 애플리케이션에서는 get, post, put, 그리고 delete를 더 자주 사용하게 될 것입니다.

Functional tests는 지정된 request 유형이 액션에서 허용되는지 여부를 확인하지 않으며, 결과에 더 중점을 둡니다. Request tests는 테스트를 더 목적성 있게 만들기 위한 이 사용 사례를 위해 존재합니다.

7.3 XHR (Ajax) Request 테스팅

Ajax request를 테스트하려면 get, post, patch, put, 그리고 delete 메서드에 xhr: true 옵션을 명시하면 됩니다. 예시:

test "ajax request" do
  article = articles(:one)
  get article_url(article), xhr: true

  assert_equal "hello world", @response.body
  assert_equal "text/javascript", @response.media_type
end

테스트에서 ajax 요청을 생성하려면 xhr: true를 요청에 전달할 수 있습니다.

7.4 세 개의 Hash 영웅들!

요청이 만들어지고 처리되면, 사용할 수 있는 3개의 Hash 객체가 준비됩니다:

  • cookies - 설정된 모든 쿠키
  • flash - flash에 존재하는 모든 객체
  • session - session 변수에 존재하는 모든 객체

일반적인 Hash 객체와 마찬가지로, 문자열 키를 참조하여 값에 접근할 수 있습니다. 심볼 이름으로도 참조할 수 있습니다. 예를 들면:

flash["gordon"]               # 또는 flash[:gordon]
session["shmession"]          # 또는 session[:shmession] 
cookies["are_good_for_u"]     # 또는 cookies[:are_good_for_u]

7.5 사용 가능한 Instance Variables

요청이 생성된 이후에는 functional test에서 세 가지 instance variables에 접근할 수 있습니다:

  • @controller - 요청을 처리하는 controller
  • @request - Request 객체
  • @response - Response 객체
class ArticlesControllerTest < ActionDispatch::IntegrationTest
  test "index를 얻어야 합니다" do
    get articles_url

    assert_equal "index", @controller.action_name
    assert_equal "application/x-www-form-urlencoded", @request.media_type
    assert_match "Articles", @response.body  
  end
end

7.6 Headers와 CGI 변수 설정하기

HTTP headersCGI variables는 headers로 전달될 수 있습니다:

# HTTP 헤더 설정하기
get articles_url, headers: { "Content-Type": "text/plain" } # 커스텀 헤더로 요청을 시뮬레이션

# CGI 변수 설정하기  
get articles_url, headers: { "HTTP_REFERER": "http://example.com/home" } # 커스텀 환경 변수로 요청을 시뮬레이션

7.7 flash 통지 테스트하기

앞에서 기억하시겠지만, Three Hashketeers 중 하나가 flash였습니다.

블로그 애플리케이션에서 누군가 새로운 Article을 성공적으로 생성할 때마다 flash 메시지를 추가하고 싶습니다.

우선 test_should_create_article 테스트에 다음 assertion을 추가해보겠습니다:

test "article 생성해야 함" do
  assert_difference("Article.count") do
    post articles_url, params: { article: { title: "Some title" } }
  end

  assert_redirected_to article_path(Article.last)
  assert_equal "Article was successfully created.", flash[:notice]
end

지금 테스트를 실행해보면 실패하는 것을 볼 수 있습니다:

$ bin/rails test test/controllers/articles_controller_test.rb -n test_should_create_article
Run options: -n test_should_create_article --seed 32266

# 실행 중:

F

0.114870초 만에 완료, 8.7055 실행/초, 34.8220 검증/초.

  1) 실패:
ArticlesControllerTest#test_should_create_article [/test/controllers/articles_controller_test.rb:16]:
--- 예상
+++ 실제
@@ -1 +1 @@
-"Article was successfully created."
+nil

1 실행, 4 검증, 1 실패, 0 오류, 0 건너뜀

이제 컨트롤러에 flash 메시지를 구현해봅시다. 우리의 :create action은 이제 다음과 같이 보일 것입니다:

def create
  @article = Article.new(article_params)

  if @article.save
    flash[:notice] = "Article이 성공적으로 생성되었습니다."
    redirect_to @article
  else
    render "new"
  end
end

이제 테스트를 실행하면 통과하는 것을 볼 수 있습니다:

$ bin/rails test test/controllers/articles_controller_test.rb -n test_should_create_article
Run options: -n test_should_create_article --seed 18981

# 실행중:

.

0.081972초 만에 완료, 12.1993 runs/s, 48.7972 assertions/s.

1 실행, 4 assertion, 0 실패, 0 에러, 0 건너뜀

7.8 종합하기

이제 Articles 컨트롤러는 :index:new, :create 액션들을 테스트합니다. 기존 데이터를 다루는 것은 어떨까요?

:show 액션에 대한 테스트를 작성해봅시다:

test "article을 표시해야 함" do
  article = articles(:one)
  get article_url(article)
  assert_response :success
end

앞서 fixtures에 대한 논의에서 언급했듯이, articles() 메서드를 통해 우리는 Articles fixtures에 접근할 수 있습니다.

기존 Article을 삭제하는 건 어떨까요?

test "article을 삭제해야 함" do
  article = articles(:one)
  assert_difference("Article.count", -1) do
    delete article_url(article)
  end

  assert_redirected_to articles_path
end

기존 Article을 업데이트하는 테스트도 추가할 수 있습니다.

test "게시글이 업데이트되어야 합니다" do
  article = articles(:one)

  patch article_url(article), params: { article: { title: "updated" } }

  assert_redirected_to article_path(article)
  # association을 리로드하여 업데이트된 데이터를 가져오고 제목이 업데이트되었는지 확인합니다.
  article.reload 
  assert_equal "updated", article.title
end

우리는 이 세 가지 테스트에서 중복이 발생하기 시작하는 것을 볼 수 있습니다. 이들은 모두 동일한 Article fixture 데이터에 접근합니다. ActiveSupport::Callbacks가 제공하는 setupteardown 메서드를 사용하여 이것을 D.R.Y.하게 만들 수 있습니다.

이제 우리의 테스트는 다음과 같이 보여야 합니다. 간단히 하기 위해 다른 테스트들은 지금은 생략하겠습니다.

require "test_helper"

class ArticlesControllerTest < ActionDispatch::IntegrationTest
  # 각각의 테스트마다 실행됨
  setup do
    @article = articles(:one)
  end

  # 각각의 테스트가 끝난 후 실행됨  
  teardown do
    # controller가 cache를 사용중일때는 이후에 초기화하는 것이 좋은 방법일 수 있음
    Rails.cache.clear
  end

  test "should show article" do
    # setup의 @article 인스턴스 변수 재사용
    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)
    # 업데이트된 데이터를 가져오기 위해 association을 reload하고 title이 업데이트되었는지 확인
    @article.reload
    assert_equal "updated", @article.title
  end
end

Rails의 다른 콜백들과 마찬가지로, setupteardown 메서드도 블록, lambda, 또는 메서드 이름을 심볼로 전달하여 사용할 수 있습니다.

7.9 Test Helpers

코드 중복을 피하기 위해, 자신만의 test helper를 추가할 수 있습니다. 아래 sign in 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

위 예시는 모든 integration test에 sign in helper를 추가하는 방법을 보여줍니다. test_helper.rb 파일을 통해 모든 integration test에서 해당 helper method를 사용할 수 있게 됩니다.

require "test_helper"

class ProfileControllerTest < ActionDispatch::IntegrationTest
  test "프로필이 표시되어야 함" do
    # helper는 이제 모든 controller test case에서 재사용 가능합니다
    sign_in_as users(:david)

    get profile_url
    assert_response :success
  end
end

7.9.1 별도의 파일 사용하기

helper들이 test_helper.rb를 복잡하게 만든다고 생각된다면, 이들을 별도의 파일로 분리할 수 있습니다. test/lib 또는 test/test_helpers가 helper들을 저장하기에 좋은 장소입니다.

# test/test_helpers/multiple_assertions.rb
module MultipleAssertions
  def assert_multiple_of_forty_two(number)
    assert (number % 42 == 0), "#{number}가 42의 배수가 될 것으로 예상됨"
  end
end

이러한 helper들은 필요할 때 명시적으로 require하고 필요에 따라 include할 수 있습니다.

require "test_helper"
require "test_helpers/multiple_assertions"

class NumberTest < ActiveSupport::TestCase
  include MultipleAssertions

  test "420은 42의 배수입니다" do
    assert_multiple_of_forty_two 420
  end
end

또는 관련된 부모 클래스에 직접 포함될 수 있습니다

# test/test_helper.rb
require "test_helpers/sign_in_helper"

class ActionDispatch::IntegrationTest
  include SignInHelper
end

7.9.2 Helper를 Eagerly Requiring 하기

test 파일에서 암시적으로 Helper에 액세스할 수 있도록 test_helper.rb에서 Helper를 eagerly require하는 것이 편리할 수 있습니다. 이는 다음과 같이 globbing을 사용하여 수행할 수 있습니다.

# test/test_helper.rb
Dir[Rails.root.join("test", "test_helpers", "**", "*.rb")].each { |file| require file }

이제 test_helpers 디렉터리에 저장된 모든 helper 파일들을 자동으로 로드하게 됩니다. 이는 Rails가 기본적으로 제공하는 기능과는 별도로, test_helper.rb에 helper 메서드를 추가해서 테스트 파일에서 바로 사용할 수 있게 해줍니다.

이것은 개별 테스트에서 필요한 파일만 수동으로 요구하는 것과 달리 부팅 시간이 증가하는 단점이 있습니다.

8 라우트 테스트하기

Rails 애플리케이션의 다른 모든 것처럼 라우트도 테스트할 수 있습니다. 라우트 테스트는 test/controllers/에 있거나 컨트롤러 테스트의 일부입니다.

애플리케이션에 복잡한 라우트가 있는 경우, Rails는 이를 테스트하기 위한 여러 유용한 헬퍼를 제공합니다.

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

9 뷰 테스트하기

주요 HTML 요소의 존재와 그 내용을 검증하여 요청에 대한 응답을 테스트하는 것은 애플리케이션의 뷰를 테스트하는 일반적인 방법입니다. 라우트 테스트와 마찬가지로 뷰 테스트는 test/controllers/에 있거나 컨트롤러 테스트의 일부입니다. assert_select 메서드를 사용하면 간단하면서도 강력한 구문을 사용하여 응답의 HTML 요소를 쿼리할 수 있습니다.

assert_select에는 두 가지 형식이 있습니다:

assert_select(selector, [equality], [message]) selector를 통해 선택된 요소들에 대해 equality 조건이 충족되는지 확인합니다. selector는 CSS selector 표현식(String)이거나 대체 값이 있는 표현식일 수 있습니다.

assert_select(element, selector, [equality], [message]) element(instance of Nokogiri::XML::Node 또는 Nokogiri::XML::NodeSet)와 그 하위 요소들로부터 시작하여 selector를 통해 선택된 모든 요소들에 대해 equality 조건이 충족되는지 확인합니다.

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

assert_select "title", "Rails 테스팅 가이드에 오신 것을 환영합니다"

더 깊은 조사를 위해 assert_select 블록을 중첩해서 사용할 수 있습니다.

다음 예시에서 li.menu_item의 내부 assert_select는 외부 블록에서 선택된 요소들의 집합 내에서 실행됩니다:

assert_select "ul.navigation" do # `ul.navigation`를 선택하고
  assert_select "li.menu_item" # 그 안에서 `li.menu_item`을 선택합니다 
end

선택된 요소들의 컬렉션은 각 요소별로 assert_select를 개별적으로 호출할 수 있도록 반복될 수 있습니다.

예를 들어 응답에 4개의 중첩된 리스트 요소를 각각 포함하는 2개의 순서가 있는 리스트가 포함되어 있다면, 다음 테스트는 모두 통과할 것입니다.

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

위 예제는 모든 ordered list(ol)가 정확히 4개의 list item(li)을 포함하고 있는지 확인합니다.

assert_select "ol" do
  assert_select "li", 8
end

위 예제는 모든 ordered list에서 총 8개의 list item이 있는지 확인합니다.

이 assertion은 매우 강력합니다. 더 고급 사용법은 해당 documentation을 참조하세요.

9.1 추가 View 기반 Assertions

View 테스팅에서 주로 사용되는 추가 assertion들이 있습니다:

Assertion 용도
assert_select_email 이메일 본문에 대한 assertion을 할 수 있습니다.
assert_select_encoded 인코딩된 HTML에 대한 assertion을 할 수 있습니다. 각 요소의 내용을 디코딩하고 디코딩된 모든 요소로 블록을 호출하여 이를 수행합니다.
css_select(selector) 또는 css_select(element, selector) selector에 의해 선택된 모든 요소의 배열을 반환합니다. 두 번째 방식은 먼저 기본 element를 찾은 다음 그 자식들 중에서 selector 표현식과 일치하는 것을 찾습니다. 일치하는 것이 없으면 두 방식 모두 빈 배열을 반환합니다.

다음은 assert_select_email 사용 예시입니다:

assert_select_email do
  assert_select "small", "'수신거부'를 원하시면 '구독취소' 링크를 클릭해주세요."
end

10 Testing View Partials

Partial template - 보통 "partial"이라고 불리는 - 은 렌더링 프로세스를 더 관리하기 쉬운 조각으로 나누는 또 다른 장치입니다. Partial을 사용하면 템플릿에서 코드 조각을 별도의 파일로 추출하여 템플릿 전체에서 재사용할 수 있습니다.

View 테스트는 partial이 예상대로 콘텐츠를 렌더링하는지 테스트할 수 있는 기회를 제공합니다. View partial 테스트는 test/views/에 위치하며 ActionView::TestCase를 상속받습니다.

Partial을 렌더링하려면 템플릿에서처럼 render를 호출하세요. 콘텐츠는 테스트 로컬 #rendered 메서드를 통해 사용할 수 있습니다:

class ArticlePartialTest < ActionView::TestCase
  test "자신을 가리키는 링크를 렌더링합니다" do
    article = Article.create! title: "Hello, world"

    render "articles/article", article: article

    assert_includes rendered, article.title
  end
end

ActionView::TestCase를 상속받는 테스트들은 assert_selectrails-dom-testing에서 제공하는 다른 추가적인 view 기반 assertion들에도 접근할 수 있습니다.

test "자기 자신에 대한 링크를 렌더링한다" do
  article = Article.create! title: "Hello, world"

  render "articles/article", article: article

  assert_select "a[href=?]", article_url(article), text: article.title
end

rails-dom-testing과 통합하기 위해, ActionView::TestCase를 상속받는 테스트는 렌더링된 내용을 Nokogiri::XML::Node 인스턴스로 반환하는 document_root_element 메서드를 선언합니다:

test "자기 자신을 가리키는 링크를 렌더링한다" do
  article = Article.create! title: "Hello, world"

  render "articles/article", article: article
  anchor = document_root_element.at("a")

  assert_equal article.name, anchor.text
  assert_equal article_url(article), anchor["href"]
end

만약 애플리케이션이 Ruby >= 3.0 이상을 사용하고, Nokogiri >= 1.14.0 이상과 Minitest >= >5.18.0 이상에 의존하는 경우, document_root_elementRuby Pattern Matching을 지원합니다:

test "자신을 가리키는 링크를 렌더링한다" do
  article = Article.create! title: "Hello, world"

  render "articles/article", article: article
  anchor = document_root_element.at("a")
  url = article_url(article)

  assert_pattern do
    anchor => { content: "Hello, world", attributes: [{ name: "href", value: url }] }
  end
end

Functional and System Testing 테스트에서 사용하는 동일한 Capybara-powered Assertions에 접근하고 싶다면, ActionView::TestCase를 상속하고 document_root_elementpage 메서드로 변환하는 기본 클래스를 정의할 수 있습니다:

# test/view_partial_test_case.rb

require "test_helper" 
require "capybara/minitest"

class ViewPartialTestCase < ActionView::TestCase
  include Capybara::Minitest::Assertions

  def page
    Capybara.string(rendered)
  end
end

# test/views/article_partial_test.rb

require "view_partial_test_case"

class ArticlePartialTest < ViewPartialTestCase
  test "자신에 대한 링크를 렌더링한다" do
    article = Article.create! title: "Hello, world"

    render "articles/article", article: article

    assert_link article.title, href: article_url(article)
  end
end

Action View 버전 7.1부터, #rendered 헬퍼 메서드는 view partial의 렌더링된 내용을 파싱할 수 있는 객체를 반환합니다.

#rendered 메서드가 반환하는 String 내용을 객체로 변환하려면, .register_parser를 호출하여 파서를 정의하세요. .register_parser :rss를 호출하면 #rendered.rss 헬퍼 메서드가 정의됩니다. 예를 들어, 렌더링된 RSS content#rendered.rss로 객체로 파싱하려면, RSS::Parser.parse에 대한 호출을 등록하세요:

register_parser :rss, -> rendered { RSS::Parser.parse(rendered) }

test "RSS를 렌더링한다" do
  article = Article.create!(title: "Hello, world")

  render formats: :rss, partial: article

  assert_equal "Hello, world", rendered.rss.items.last.title
end

기본적으로 ActionView::TestCase는 다음을 위한 parser를 정의합니다:

test "HTML을 렌더링한다" do
  article = Article.create!(title: "Hello, world")

  render partial: "articles/article", locals: { article: article }

  assert_pattern { rendered.html.at("main h1") => { content: "Hello, world" } }
end

test "JSON을 렌더링한다" do
  article = Article.create!(title: "Hello, world")

  render formats: :json, partial: "articles/article", locals: { article: article }

  assert_pattern { rendered.json => { title: "Hello, world" } }
end

11 Testing Helpers

헬퍼는 뷰에서 사용할 수 있는 메서드를 정의할 수 있는 간단한 모듈입니다.

헬퍼를 테스트하기 위해서는 헬퍼 메서드의 출력이 기대하는 것과 일치하는지 확인하기만 하면 됩니다. 헬퍼 관련 테스트는 test/helpers 디렉토리에 있습니다.

다음과 같은 헬퍼가 있다고 가정해봅시다:

module UsersHelper
  # user의 성과 이름을 연결한 링크를 생성합니다
  def link_to_user(user) 
    link_to "#{user.first_name} #{user.last_name}", user
  end
end

이 메서드의 출력을 다음과 같이 테스트할 수 있습니다:

class UsersHelperTest < ActionView::TestCase
  test "사용자의 전체 이름을 반환해야 합니다" do
    user = users(:david)

    assert_dom_equal %{<a href="/user/#{user.id}">David Heinemeier Hansson</a>}, link_to_user(user) 
  end
end

게다가 테스트 클래스가 ActionView::TestCase를 상속받기 때문에, link_topluralize 같은 Rails의 helper 메서드들에 접근할 수 있습니다.

12 Testing Your Mailers

mailer 클래스를 철저히 테스트하기 위해서는 몇 가지 특별한 도구가 필요합니다.

12.1 우체부를 검사하기

메일러 클래스는 - Rails 어플리케이션의 다른 모든 부분처럼 - 기대한대로 작동하는지 확인하기 위해 테스트되어야 합니다.

메일러 클래스를 테스트하는 목적은 다음을 보장하기 위함입니다:

  • 이메일이 처리되고 있는지 (생성 및 발송)
  • 이메일 내용이 올바른지 (제목, 발신자, 본문 등)
  • 올바른 이메일이 올바른 시점에 발송되고 있는지

12.1.1 모든 측면에서

메일러를 테스트하는 데는 unit test와 functional test라는 두 가지 측면이 있습니다. unit test에서는 엄격하게 제어된 입력값으로 메일러를 독립적으로 실행하고 출력을 알려진 값(fixture)과 비교합니다. functional test에서는 메일러가 생성하는 세부적인 내용을 테스트하기보다는, 컨트롤러와 모델이 메일러를 올바르게 사용하고 있는지 테스트합니다. 올바른 이메일이 올바른 시점에 발송되었는지를 입증하는 테스트를 수행합니다.

12.2 Unit Testing

메일러가 예상대로 동작하는지 테스트하기 위해, unit test를 사용하여 메일러의 실제 결과와 생성되어야 할 미리 작성된 예제를 비교할 수 있습니다.

12.2.1 Fixtures의 재등장

메일러의 unit testing을 위해, fixtures는 출력이 어떻게 보여야 하는지에 대한 예제를 제공하는 데 사용됩니다. 이것들은 예제 이메일이며 다른 fixtures처럼 Active Record 데이터가 아니기 때문에, 다른 fixtures와 별도로 자체 하위 디렉토리에 보관됩니다. test/fixtures 내의 디렉토리 이름은 메일러의 이름과 직접적으로 대응됩니다. 따라서, UserMailer라는 이름의 메일러의 경우, fixtures는 test/fixtures/user_mailer 디렉토리에 위치해야 합니다.

메일러를 생성할 때, generator는 메일러 액션에 대한 stub fixtures를 생성하지 않습니다. 위에서 설명한 대로 직접 해당 파일들을 생성해야 합니다.

12.2.2 기본 테스트 케이스

다음은 친구에게 초대장을 보내는 데 사용되는 invite 액션을 가진 UserMailer라는 메일러를 테스트하는 unit test입니다. 이것은 invite 액션을 위해 generator가 생성한 기본 테스트를 수정한 버전입니다.

require "test_helper"

class UserMailerTest < ActionMailer::TestCase
  test "invite" do
    # 이메일을 생성하고 추가 assert를 위해 저장
    email = UserMailer.create_invite("me@example.com",
                                     "friend@example.com", Time.now)

    # 이메일을 보내고 큐에 들어갔는지 테스트
    assert_emails 1 do
      email.deliver_now
    end

    # 보낸 이메일의 본문에 기대한 내용이 포함되어 있는지 테스트
    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

테스트에서 email을 생성하고 반환된 객체를 email 변수에 저장합니다. 그리고 이메일이 전송되었는지 확인하고(첫 번째 assert), 두 번째 assert에서는 이메일이 실제로 우리가 기대하는 내용을 포함하고 있는지 확인합니다. read_fixture 헬퍼는 이 파일의 내용을 읽어오는 데 사용됩니다.

email.body.to_s는 (HTML 또는 텍스트) 부분이 하나만 있을 때 사용됩니다. mailer가 둘 다 제공하는 경우, email.text_part.body.to_s 또는 email.html_part.body.to_s를 사용하여 특정 부분에 대해 fixture를 테스트할 수 있습니다.

다음은 invite fixture의 내용입니다:

안녕하세요 friend@example.com님,

초대를 받으셨습니다.

감사합니다!

mailer에 대한 테스트 작성에 대해 좀 더 이해하는 것이 좋은 시점입니다. config/environments/test.rbActionMailer::Base.delivery_method = :test 코드는 delivery method를 test mode로 설정하여 실제로 이메일이 전송되지 않게 합니다(테스트 중에 사용자들에게 스팸이 전송되는 것을 방지하는데 유용합니다). 대신 이메일은 배열(ActionMailer::Base.deliveries)에 추가됩니다.

ActionMailer::Base.deliveries 배열은 ActionMailer::TestCaseActionDispatch::IntegrationTest 테스트에서만 자동으로 초기화됩니다. 이러한 테스트 케이스 외부에서 깨끗한 상태로 만들고 싶다면, 다음과 같이 수동으로 초기화할 수 있습니다: ActionMailer::Base.deliveries.clear

12.2.3 대기열에 추가된 이메일 테스트하기

assert_enqueued_email_with assertion을 사용하여 이메일이 모든 예상된 mailer 메서드 인자 및/또는 파라미터화된 mailer 파라미터와 함께 대기열에 추가되었는지 확인할 수 있습니다. 이를 통해 deliver_later 메서드로 대기열에 추가된 모든 이메일을 매칭할 수 있습니다.

기본 테스트 케이스와 마찬가지로, 이메일을 생성하고 반환된 객체를 email 변수에 저장합니다. 다음 예제들은 인자 및/또는 파라미터를 전달하는 다양한 방법을 포함합니다.

이 예제는 이메일이 올바른 인자와 함께 대기열에 추가되었는지 확인합니다:

require "test_helper"

class UserMailerTest < ActionMailer::TestCase
  test "invite" do
    # 이메일을 생성하고 추가 검증을 위해 저장
    email = UserMailer.create_invite("me@example.com", "friend@example.com")

    # 이메일이 올바른 인자와 함께 대기열에 들어갔는지 테스트
    assert_enqueued_email_with UserMailer, :create_invite, args: ["me@example.com", "friend@example.com"] do
      email.deliver_later
    end
  end
end

이 예제는 args로 인자들의 해시를 전달하여 mailer가 올바른 mailer method 명명된 인자들과 함께 enqueue되었음을 assert할 것입니다:

require "test_helper"

class UserMailerTest < ActionMailer::TestCase
  test "invite" do
    # 이메일을 생성하고 추가 검증을 위해 저장합니다
    email = UserMailer.create_invite(from: "me@example.com", to: "friend@example.com")

    # 이메일이 올바른 명명된 인자와 함께 enqueue 되었는지 테스트합니다
    assert_enqueued_email_with UserMailer, :create_invite, args: [{ from: "me@example.com",
                                                                    to: "friend@example.com" }] do
      email.deliver_later
    end
  end
end

이 예시는 매개변수화된 mailer가 올바른 파라미터와 인자들과 함께 enqueue되었는지를 검증합니다. Mailer 파라미터는 params로 전달되고 mailer 메서드 인자들은 args로 전달됩니다:

require "test_helper"

class UserMailerTest < ActionMailer::TestCase
  test "invite" do
    # 이메일을 생성하고 추가 assertion을 위해 저장
    email = UserMailer.with(all: "good").create_invite("me@example.com", "friend@example.com")

    # 이메일이 올바른 mailer 파라미터와 인수로 큐에 추가되었는지 테스트
    assert_enqueued_email_with UserMailer, :create_invite, params: { all: "good" },
                                                           args: ["me@example.com", "friend@example.com"] do
      email.deliver_later
    end
  end
end

이 예시는 매개변수화된 mailer가 올바른 매개변수와 함께 enqueue 되었는지 테스트하는 대체 방법을 보여줍니다:

require "test_helper"

class UserMailerTest < ActionMailer::TestCase
  test "invite" do
    # 이메일을 생성하고 추가 검증을 위해 저장
    email = UserMailer.with(to: "friend@example.com").create_invite

    # 이메일이 올바른 mailer 매개변수와 함께 큐에 들어갔는지 테스트
    assert_enqueued_email_with UserMailer.with(to: "friend@example.com"), :create_invite do
      email.deliver_later
    end
  end
end

12.3 Functional 및 System Testing

Unit testing을 통해 이메일의 속성을 테스트할 수 있지만 functional 및 system testing을 통해서는 사용자 상호작용이 이메일 전송을 적절하게 트리거하는지를 테스트할 수 있습니다. 예를 들어, 친구 초대 작업이 올바르게 이메일을 보내고 있는지 확인할 수 있습니다:

# Integration Test
require "test_helper"

class UsersControllerTest < ActionDispatch::IntegrationTest
  test "invite friend" do
    # ActionMailer::Base.deliveries의 차이를 assert 함
    assert_emails 1 do
      post invite_friend_url, params: { email: "friend@example.com" }
    end
  end
end
# System Test
require "test_helper"

class UsersTest < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :headless_chrome

  test "친구 초대하기" do
    visit invite_users_url
    fill_in "Email", with: "friend@example.com"
    assert_emails 1 do
      click_on "Invite" 
    end
  end
end

assert_emails 메서드는 특정 deliver 메서드에 종속되지 않으며 deliver_now 또는 deliver_later 메서드로 전달된 이메일과 함께 작동합니다. 이메일이 enqueue 되었는지 명시적으로 확인하려면 assert_enqueued_email_with(위의 예제) 또는 assert_enqueued_emails 메서드를 사용할 수 있습니다. 자세한 내용은 여기 문서에서 확인할 수 있습니다.

13 Testing Jobs

Job은 격리된 상태(job의 동작에 초점을 맞춤)와 컨텍스트 내(호출 코드의 동작에 초점을 맞춤)에서 테스트할 수 있습니다.

13.1 Job 단독 테스팅

job을 생성하면 관련된 테스트 파일이 test/jobs 디렉터리에 함께 생성됩니다.

다음은 billing job에 대한 테스트 예시입니다:

require "test_helper"

class BillingJobTest < ActiveJob::TestCase
  test "계정이 청구됨" do
    perform_enqueued_jobs do
      BillingJob.perform_later(account, product) 
    end
    assert account.reload.charged_for?(product)
  end
end

테스트의 기본 queue adapter는 perform_enqueued_jobs가 호출될 때까지 job을 실행하지 않습니다. 또한 테스트가 서로 간섭하지 않도록 각 테스트가 실행되기 전에 모든 job을 제거합니다.

테스트는 perform_now 대신 perform_enqueued_jobsperform_later를 사용하므로, retry가 설정되어 있을 경우 retry 실패가 재큐잉되어 무시되지 않고 테스트에서 포착됩니다.

13.2 Context에서의 Job 테스팅

컨트롤러 액션에 의한 Job의 정상적인 enqueue 여부를 테스트하는 것이 좋은 관행입니다. ActiveJob::TestHelper 모듈은 assert_enqueued_with와 같이 이를 도와주는 여러 메서드를 제공합니다.

다음은 account 모델 메서드를 테스트하는 예시입니다:

require "test_helper"

class AccountTest < ActiveSupport::TestCase
  include ActiveJob::TestHelper

  test "#charge_for가 billing job을 enqueue 한다" do
    assert_enqueued_with(job: BillingJob) do
      account.charge_for(product)
    end

    assert_not account.reload.charged_for?(product)

    perform_enqueued_jobs

    assert account.reload.charged_for?(product)
  end
end

13.3 예외가 발생하는지 테스트

특정한 경우에 job이 예외를 발생시키는지 테스트하는 것은 까다로울 수 있습니다. 특히 retry가 구성되어 있는 경우에는 더욱 그렇습니다. perform_enqueued_jobs 헬퍼는 job이 예외를 발생시키는 경우 테스트를 실패시킵니다. 따라서 예외가 발생했을 때 테스트가 성공하도록 하려면 job의 perform 메서드를 직접 호출해야 합니다.

require "test_helper"

class BillingJobTest < ActiveJob::TestCase
  test "잔액이 부족한 계좌는 청구하지 않음" do 
    assert_raises(InsufficientFundsError) do
      BillingJob.new(empty_account, product).perform
    end
    assert_not account.reload.charged_for?(product)
  end
end

Action Cable을 일반적으로 권장하지 않는 방법입니다. 이는 argument serialization과 같은 프레임워크의 일부를 우회하기 때문입니다.

14 Testing Action Cable

Action Cable은 애플리케이션 내에서 여러 계층에서 사용되므로, channel과 connection 클래스 자체를 테스트해야 할 뿐만 아니라 다른 엔티티들이 올바른 메시지를 broadcast하는지도 테스트해야 합니다.

14.1 Connection Test Case

기본적으로 Action Cable이 포함된 새로운 Rails 어플리케이션을 생성하면, base connection class(ApplicationCable::Connection)에 대한 테스트도 test/channels/application_cable 디렉토리에 함께 생성됩니다.

Connection 테스트는 connection의 identifier가 적절하게 할당되었는지 또는 부적절한 connection 요청이 거부되는지 확인하는 것을 목표로 합니다. 다음은 예시입니다:

class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
  test "params로 연결" do
    # `connect` 메서드를 호출하여 연결 열기를 시뮬레이션 
    connect params: { user_id: 42 }

    # 테스트에서 `connection` 을 통해 Connection 객체에 접근할 수 있습니다
    assert_equal connection.user_id, "42"
  end

  test "params가 없으면 연결 거부" do
    # `assert_reject_connection` matcher를 사용하여
    # 연결이 거부되는지 확인
    assert_reject_connection { connect }
  end
end

integration test에서 하는 것과 동일한 방법으로 request cookie를 지정할 수 있습니다:

test "쿠키로 연결" do
  cookies.signed[:user_id] = "42"

  connect

  assert_equal connection.user_id, "42"
end

ActionCable::Connection::TestCase의 API 문서에서 더 자세한 정보를 확인하세요.

14.2 Channel Test Case

기본적으로 channel을 생성하면 test/channels 디렉토리에 관련 테스트도 함께 생성됩니다. 다음은 chat channel의 테스트 예시입니다:

require "test_helper"

class ChatChannelTest < ActionCable::Channel::TestCase
  test "채널을 구독하고 방을 스트리밍함" do
    # `subscribe`를 호출하여 구독 생성을 시뮬레이션합니다 
    subscribe room: "15"

    # 테스트에서 `subscription`을 통해 Channel 객체에 접근할 수 있습니다
    assert subscription.confirmed?
    assert_has_stream "chat_15"
  end
end

이 테스트는 꽤 단순하며 connection이 특정 stream을 subscribe 하는지만 assert합니다.

connection의 식별자를 지정할 수도 있습니다. 다음은 web notifications channel의 테스트 예시입니다:

require "test_helper"

class WebNotificationsChannelTest < ActionCable::Channel::TestCase
  test "사용자를 위한 구독 및 스트림" do
    stub_connection current_user: users(:john)

    subscribe

    assert_has_stream_for users(:john)
  end
end

ActionCable::Channel::TestCase의 API 문서에서 더 자세한 정보를 확인하세요.

14.3 커스텀 Assertion과 다른 컴포넌트 내에서 Broadcast 테스팅

Action Cable은 테스트의 장황함을 줄일 수 있는 여러 커스텀 assertion들과 함께 제공됩니다. 사용 가능한 assertion들의 전체 목록은 ActionCable::TestHelper의 API 문서를 참조하세요.

올바른 메시지가 다른 컴포넌트(예: 컨트롤러) 내에서 broadcast되었는지 확인하는 것은 좋은 사례입니다. 이는 Action Cable이 제공하는 커스텀 assertion들이 매우 유용한 경우입니다. 예를 들어, 모델 내에서:

require "test_helper"

class ProductTest < ActionCable::TestCase
  test "결제 후 상태 브로드캐스트" do
    assert_broadcast_on("products:#{product.id}", type: "charged") do
      product.charge(account)
    end
  end
end

Channel.broadcast_to로 생성된 broadcasting을 테스트하려면 기본 stream 이름을 생성하기 위해 Channel.broadcasting_for를 사용해야 합니다:

# app/jobs/chat_relay_job.rb
class ChatRelayJob < ApplicationJob
  def perform(room, message)
    ChatChannel.broadcast_to room, text: message
  end
end
# test/jobs/chat_relay_job_test.rb
require "test_helper"

class ChatRelayJobTest < ActiveJob::TestCase
  include ActionCable::TestHelper

  test "방에 메시지 브로드캐스트하기" do
    room = rooms(:all)

    assert_broadcast_on(ChatChannel.broadcasting_for(room), text: "Hi!") do
      ChatRelayJob.perform_now(room, "Hi!")
    end
  end
end

15 Testing Eager Loading

일반적으로 애플리케이션은 속도를 높이기 위해 developmenttest 환경에서는 eager load를 하지 않습니다. 하지만 production 환경에서는 eager load를 합니다.

프로젝트의 어떤 파일이 어떤 이유로든 로드될 수 없다면, production 환경에 배포하기 전에 이를 감지하는 것이 좋지 않을까요?

15.1 Continuous Integration

프로젝트에 CI가 구축되어 있다면, CI에서 eager loading을 하는 것은 애플리케이션의 eager loading을 보장하는 쉬운 방법입니다.

CI는 일반적으로 테스트 스위트가 CI에서 실행 중임을 나타내는 환경 변수를 설정합니다. 예를 들어, CI와 같은 변수를 사용할 수 있습니다:

# config/environments/test.rb
config.eager_load = ENV["CI"].present?

test 환경에서는 CI(Continuous Integration) 환경에서만 eager loading을 활성화하고, 그렇지 않을 때는 비활성화합니다.

Rails 7부터는, 새로 생성된 애플리케이션이 기본적으로 이 방식으로 구성됩니다.

15.2 Bare Test Suites

프로젝트에 continuous integration이 없는 경우에도 Rails.application.eager_load!를 호출하여 test suite에서 eager load를 수행할 수 있습니다:

15.2.1 Minitest

require "test_helper"

class ZeitwerkComplianceTest < ActiveSupport::TestCase
  test "모든 파일을 오류 없이 eager loading 합니다" do
    assert_nothing_raised { Rails.application.eager_load! }
  end
end

15.2.2 RSpec

Rails에서는 기본적으로 Minitest로 테스팅을 합니다. Minitest를 대체할 수 있는 가장 인기 있는 대안으로는 RSpec이 있습니다.

RSpec을 사용하려면:

1) Gemfile에 gem을 추가하고:

group :development, :test do
  gem 'rspec-rails'
end

2) bundle install 실행

3) 아래를 실행해서 rspec을 초기화합니다:

$ bin/rails generate rspec:install

4) test 디렉터리에 있던 파일들을 spec 디렉터리로 옮깁니다.

5) Minitest 파일 확장자 _test.rb_spec.rb로 변경합니다.

6) Minitest 구문을 RSpec 구문으로 변경합니다.

Minitest: ruby class ArticleTest < ActiveSupport::TestCase def test_the_truth assert true end end

RSpec: ruby describe Article do it "is true" do expect(true).to be_truthy end end

require "rails_helper"

RSpec.describe "Zeitwerk 준수" do
  it "모든 파일을 오류 없이 eager load 한다" do
    expect { Rails.application.eager_load! }.not_to raise_error
  end
end

16 추가 테스팅 리소스

16.1 시간 의존적 코드 테스팅

Rails는 시간에 민감한 코드가 예상대로 동작하는지 검증할 수 있는 내장 helper 메소드를 제공합니다.

다음 예시는 travel_to helper를 사용합니다:

# 사용자가 등록 1개월 후에 선물을 받을 자격이 주어진다고 가정합니다.
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
  # `travel_to` 블록 내부에서 `Date.current`가 스텁 처리됩니다
  assert_equal Date.new(2004, 10, 24), user.activation_date
  assert user.applicable_for_gifting?
end

# 변경사항은 `travel_to` 블록 내부에서만 볼 수 있습니다.
assert_equal Date.new(2004, 10, 24), user.activation_date

사용 가능한 time helper들에 대한 더 자세한 정보는 ActiveSupport::Testing::TimeHelpers API 문서를 참고해주세요.



맨 위로