Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

3단계(AAA) 패턴으로 더 나은 테스트 작성하기

(『Practicing Rails』에서 발췌한 내용입니다. 뉴스레터에 가입하시면 첫 번째 챕터를 무료로 받아볼 수 있습니다!)

새 앱을 개발하고 있는데, Rails가 자동으로 테스트 파일을 생성해 주었다고 상상해 보세요:

test/models/bug_test.rb
require 'test_helper'

class BugTest < ActiveSupport::TestCase
  # test "the truth" do
  #   assert true
  # end
end

주석을 해제하고, 적절한 이름을 붙이면 테스트를 작성할 준비가 끝난 것처럼 보입니다. 하지만 그다음은 어떻게 해야 할까요? 무엇부터 작성해야 할까요? 테스트 코드는 어떤 모습을 가져야 할까요?

간단한 패턴 하나만 따라 해도, 빈 껍데기에 불과한 코드가 명확하고 잘 구조화된 테스트 케이스로 바뀝니다.

3단계 테스트 패턴이란?

테스트 케이스는 세 가지 단계로 구성되어야 합니다:

  1. 먼저, 필요한 것들을 준비합니다 ("Arrange", 준비)
  2. 그다음, 무언가를 실행합니다 ("Act", 실행)
  3. 마지막으로, 기대한 결과가 실제로 일어났는지 확인합니다 ("Assert", 검증)

예를 들어 Ruby의 배열 메서드를 테스트한다고 가정해 보겠습니다. 이 패턴을 따르면 테스트는 다음과 같은 형태가 됩니다:

test "Array#sort will sort an array of numbers" do
  # arrange
  unsorted_array = [7, 4, 2, 3]
  
  # act
  sorted_array = unsorted_array.sort

  # assert
  assert_equal [2, 3, 4, 7], sorted_array
end

아주 간단합니다. 중요한 것은 테스트의 모든 부분이 각자 제자리를 가진다는 점, 그리고 각 단계가 거의 스스로 작성 방법을 알려준다는 사실입니다.

물론 Arrange 단계가 필요 없거나, Act와 Assert 단계가 하나로 합쳐지는 경우도 있습니다. 그럼에도 테스트를 작성할 때 세 단계를 모두 의식하면 큰 도움이 됩니다.

Assert 단계에서 조심해야 할 함정

Assert 단계에는 한 가지 주의할 점이 있습니다. Act 단계에서 사용한 것과 동일한 로직을 Assert 단계에서 다시 사용해서는 안 됩니다. 같은 답에 도달하는 서로 다른 두 경로를 확보해야 합니다. 그렇지 않으면 호출하는 코드에 버그가 있어도 알아차리지 못합니다. Assert 단계에서 결국 같은 코드를 한 번 더 호출하는 것에 지나지 않기 때문입니다.

예를 들어, 수학 계산을 테스트한다면:

test "average returns the average of a set of numbers" do
  # arrange
  numbers = [1, 2, 3, 4]
  
  # act
  average = numbers.average

  # assert
  
  # 나쁜 예
  assert_equal [1, 2, 3, 4].average, average

  # 좋은 예
  assert_equal 2.5, average
end

Assert 단계에서 [1, 2, 3, 4].average를 다시 호출하는 것은 나쁜 방법입니다. average가 사실상 어떤 값이든 반환하더라도 이 검증은 그대로 통과해 버리기 때문입니다.

이 예제에서는 문제점이 비교적 명확합니다. 하지만 코드가 복잡해지더라도 같은 코드를 두 번 실행하고 있는 것은 아닌지 반드시 확인하세요. 그렇지 않으면 메서드가 호출되었는지만 검증할 뿐, 기대한 대로 동작하는지는 전혀 확인하지 못하게 됩니다.

일반적으로 두 번째 경로를 만드는 가장 쉬운 방법은 직접 손으로 답을 계산해서 하드코딩하는 것입니다. 다소 취약해 보일 수 있지만, 테스트가 깨져도 눈치채지 못하는 상황보다는 훨씬 낫습니다.

왜 3단계일까?

테스트를 이 세 단계로 나누면, 답해야 할 질문이 훨씬 단순해집니다. "이 테스트를 어떻게 작성해야 하지?"라는 막연한 고민 대신, 각 단계별로 집중할 수 있습니다. "이 테스트를 어떻게 준비하지?", "무엇을 테스트하는 거지?", "결과는 어떤 모습이어야 하지?"와 같이 말입니다.

이런 질문들 역시 쉽지 않을 수 있지만, 테스트 전체를 한꺼번에 고민하는 것보다는 훨씬 수월합니다. 게다가 운이 좋다면 관련된 테스트들끼리 단계를 공유할 수도 있어, 다음 테스트를 작성하는 일이 훨씬 덜 부담스러워집니다.