유용한 도구

자동 테스트는 근본적으로 문제가 있는 경우 오류를 발생시키거나 오류를 일으키는 코드입니다. 대부분의 라이브러리 또는 테스트 프레임워크는 테스트를 더 쉽게 작성할 수 있게 해주는 다양한 프리미티브를 제공합니다.

이전 섹션에서 언급했듯이 이러한 프리미티브에는 거의 항상 독립적인 테스트 (테스트 사례라고 함)를 정의하고 어설션을 제공하는 방법이 포함됩니다. 어설션은 결과 확인과 잘못된 경우 오류 발생을 결합하는 방법이며 모든 테스트 프리미티브의 기본 프리미티브로 간주될 수 있습니다.

이 페이지에서는 이러한 프리미티브에 대한 일반적인 접근 방식을 설명합니다. 선택한 프레임워크에는 이와 비슷한 내용이 있을 수 있지만 정확한 참조는 아닙니다.

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

import { fibonacci, catalan } from '../src/math.js';
import { assert, test, suite } from 'a-made-up-testing-library';

suite('math tests', () => {
  test('fibonacci function', () => {
    // check expected fibonacci numbers against our known actual values
    // with an explanation if the values don't match
    assert.equal(fibonacci(0), 0, 'Invalid 0th fibonacci result');
    assert.equal(fibonacci(13), 233, 'Invalid 13th fibonacci result');
  });
  test('relationship between sequences', () => {
    // catalan numbers are greater than fibonacci numbers (but not equal)
    assert.isAbove(catalan(4), fibonacci(4));
  });
  test('bugfix: check bug #4141', () => {
    assert.isFinite(fibonacci(0)); // fibonacci(0) was returning NaN
  })
});

이 예에서는 '수학 테스트'라는 테스트 그룹 (도구 모음이라고도 함)을 만들고 각각 일부 어설션을 실행하는 세 가지 독립적인 테스트 사례를 정의합니다. 이러한 테스트 사례는 일반적으로 테스트 실행기의 필터 플래그 등을 통해 개별적으로 처리하거나 실행할 수 있습니다.

프리미티브로서의 어설션 도우미

Vitest를 포함한 대부분의 테스트 프레임워크에는 assert 객체에 관한 어설션 도우미 모음이 포함되어 있습니다. 이 컬렉션을 사용하면 일부 expectation와 비교하여 반환 값 또는 기타 상태를 빠르게 확인할 수 있습니다. 이러한 기대는 '잘 알려진' 값인 경우가 많습니다. 이전 예에서는 13번째 피보나치 숫자가 233이어야 하므로 assert.equal를 사용하여 직접 확인할 수 있습니다.

값이 특정 형식을 취하거나 다른 값보다 크거나 다른 속성을 가지고 있을 것으로 예상할 수도 있습니다. 이 과정에서는 가능한 모든 어설션 도우미를 다루지는 않지만 테스트 프레임워크에서는 항상 최소한 다음과 같은 기본 검사를 제공합니다.

  • '확인'이라고도 하는 '진실' 검사는 조건이 true인지 확인하며, 성공 또는 올바른지 확인하는 if 작성 방법과 일치합니다. 이는 assert(...) 또는 assert.ok(...)로 제공되는 경향이 있으며 단일 값과 선택적 주석을 취합니다.

  • 수학 테스트 예와 같이 객체의 반환 값 또는 상태가 알려진 정상 값과 같을 것으로 예상되는 동등성 검사 이는 원시 동등 (예: 숫자 및 문자열) 또는 참조 동등(동일한 객체)을 위한 것입니다. 내부적으로 이는 == 또는 === 비교를 통한 '진실' 검사에 불과합니다.

    • JavaScript는 느슨한 동등 (==)과 엄격한 동등 (===)을 구분합니다. 대부분의 테스트 라이브러리는 각각 assert.equal 메서드와 assert.strictEqual 메서드를 제공합니다.
  • 심층 동등성 검사: 동등성 검사를 확장하여 객체, 배열 및 기타 복잡한 데이터 유형의 콘텐츠 및 객체를 순회하여 비교하기 위한 내부 로직을 확인합니다. 이러한 요소가 중요한 이유는 JavaScript에는 두 객체 또는 배열의 콘텐츠를 비교할 수 있는 기본 제공 방법이 없기 때문입니다. 예를 들어 [1,2,3] == [1,2,3]는 항상 false입니다. 테스트 프레임워크에는 deepEqual 또는 deepStrictEqual 도우미가 포함되는 경우가 많습니다.

'진실' 검사만 비교하는 것이 아니라 두 값을 비교하는 어설션 도우미는 일반적으로 2~3개의 인수를 사용합니다.

  • 테스트 중인 코드에서 생성되거나 검증할 상태를 설명하는 실제 값입니다.
  • 예상 값으로, 일반적으로 하드 코딩됩니다 (예: 리터럴 숫자 또는 문자열).
  • 예상되는 문제 또는 실패했을 수 있는 사항을 설명하는 주석(선택사항). 이 줄이 실패할 경우 포함됩니다.

어설션을 결합하여 다양한 검사를 구성하는 것도 매우 일반적인 방법입니다. 시스템 상태를 자체적으로 올바르게 확인하는 경우는 드물기 때문입니다. 예를 들면 다음과 같습니다.

  test('JWT parse', () => {
    const json = decodeJwt('eyJieSI6InNhbXRob3Ii…');

    assert.ok(json.payload.admin, 'user should be admin');
    assert.deepEqual(json.payload.groups, ['role:Admin', 'role:Submitter']);
    assert.equal(json.header.alg, 'RS265')
    assert.isAbove(json.payload.exp, +new Date(), 'expiry must be in future')
  });

Vitest는 내부적으로 Chai 어설션 라이브러리를 사용하여 어설션 도우미를 제공합니다. 참조를 통해 코드에 적합한 어설션과 도우미를 확인하는 것이 유용할 수 있습니다.

Fluent 및 BDD 어설션

일부 개발자는 동작 기반 개발 (BDD) 또는 유창 스타일 어설션이라고 하는 어설션 스타일을 선호합니다. 기대치 확인의 진입점이 expect()라는 메서드이므로 이를 '예상' 도우미라고도 합니다.

도우미는 assert.ok 또는 assert.strictDeepEquals와 같은 간단한 메서드 호출로 작성된 어설션과 같은 방식으로 작동하지만 일부 개발자는 이 함수를 더 쉽게 읽을 수 있습니다. BDD 어설션은 다음과 같을 수 있습니다.

// A failure here would generate "Expect result to be an array that does include 42"
const result = await possibleMeaningsOfLife();
expect(result).to.be.an('array').that.does.include(42);

// or a simpler form
expect(result).toBe('array').toContainEqual(42);

// the same in assert might be
assert.typeOf(result, 'array', 'Expected the result to be an array');
assert.include(result, 42, 'Expected the result to include 42');

이러한 어설션 스타일은 메서드 체이닝이라는 기법으로 인해 작동합니다. 이 기법에서는 expect에서 반환된 객체를 추가 메서드 호출과 함께 지속적으로 체이닝할 수 있습니다. 이전 예의 to.bethat.does를 비롯한 호출의 일부에는 함수가 없으며 호출을 더 쉽게 읽고 테스트에 실패한 경우 자동 코멘트를 생성할 수 있도록 하기 위해서만 포함됩니다. 특히 expect는 일반적으로 선택적 주석을 지원하지 않습니다. 체이닝에서 실패를 명확하게 설명해야 하기 때문입니다.

많은 테스트 프레임워크가 Fluent/BDD와 일반 어설션을 모두 지원합니다. 예를 들어 Vitest는 차이의 접근 방식을 모두 내보내고 BDD에 대해 약간 더 간결한 자체 접근 방식을 사용합니다. 반면 Jest는 기본적으로 예상 메서드만 포함합니다.

여러 파일에서 테스트 그룹화

테스트를 작성할 때 모든 테스트가 하나의 파일에 있는 것이 아니라 이미 암시적인 그룹화를 제공하는 경향이 있습니다. 여러 파일에 테스트를 작성하는 것이 일반적입니다. 실제로 테스트 실행기는 일반적으로 파일이 테스트용이라는 사실만 인지합니다. 예를 들어 vitest는 프로젝트에서 '.test.jsx' 또는 '.spec.ts'와 같은 확장자로 끝나는 모든 파일('.test' 및 '.spec'과 여러 유효한 확장자 포함)을 포함합니다.

구성요소 테스트는 다음과 같은 디렉터리 구조와 같이 테스트 중인 구성요소의 피어 파일에 위치하는 경향이 있습니다.

UserList.tsx 및 UserList.test.tsx를 포함한 디렉터리의 파일 목록입니다.
구성요소 파일 및 관련 테스트 파일

마찬가지로 단위 테스트는 테스트 중인 코드 옆에 배치되는 경향이 있습니다. 엔드 투 엔드 테스트는 각각 자체 파일에 있을 수 있으며 통합 테스트는 고유한 자체 폴더에 배치할 수도 있습니다. 이러한 구조는 복잡한 테스트 사례가 성장하여 테스트용으로만 필요한 지원 라이브러리와 같은 자체 비 테스트 지원 파일이 필요한 경우 유용할 수 있습니다.

파일 내에서 테스트 그룹화

이전 예에서와 같이 test()로 설정한 테스트를 그룹화하는 suite() 호출 내에 테스트를 배치하는 것이 일반적입니다. 도구 모음은 일반적으로 테스트 자체는 아니지만 전달된 메서드를 호출하여 관련 테스트 또는 목표를 그룹화하여 구조를 제공하는 데 도움이 됩니다. test()의 경우 전달된 메서드는 테스트 자체의 작업을 설명합니다.

어설션과 마찬가지로 Fluent/BDD도 그룹화 테스트와 상당히 표준적으로 동일합니다. 몇 가지 일반적인 예는 다음 코드에서 비교됩니다.

// traditional/TDD
suite('math tests', () => {
  test('handle zero values', () => {
    assert.equal(fibonacci(0), 0);
  });
});

// Fluent/BDD
describe('math tests', () => {
  it('should handle zero values', () => {
    expect(fibonacci(0)).toBe(0);
  });
})

어설션을 작성하는 데 expectassert를 사용하는 것의 차이가 큰 반면, 대부분의 프레임워크에서 suitedescribetestit처럼 유사하게 동작합니다.

도구 모음과 테스트를 정렬하는 방식이 미묘하게 다른 도구도 있습니다. 예를 들어 Node.js의 내장된 테스트 실행기는 test() 중첩 호출을 지원하여 암시적으로 테스트 계층 구조를 만듭니다. 그러나 Vitest는 suite()를 사용한 이러한 종류의 중첩만 허용하며 다른 test() 내에서 정의된 test()는 실행하지 않습니다.

어설션과 마찬가지로 기술 스택이 제공하는 그룹화 메서드의 정확한 조합은 그다지 중요하지 않습니다. 이 과정에서는 이러한 개념을 개략적으로 다루지만 선택한 도구에 어떻게 적용되는지 파악해야 합니다.

수명 주기 메서드

파일 내 최상위 수준에서 암시적으로 테스트를 그룹화하는 한 가지 이유는 모든 테스트에서 또는 테스트 그룹에 한 번 실행되는 설정 및 해체 메서드를 제공하기 위해서입니다. 대부분의 프레임워크는 네 가지 메서드를 제공합니다.

모든 `test()` 또는 `it()`마다 모음에 대해 한 번
테스트 실행 전 `beforeeach()` `beforeAll()`
테스트 실행 후 `aftereach()` `afterAll()`

예를 들어 각 테스트 전에 가상 사용자 데이터베이스를 미리 채우고 나중에 삭제할 수 있습니다.

suite('user test', () => {
  beforeEach(() => {
    insertFakeUser('bob@example.com', 'hunter2');
  });
  afterEach(() => {
    clearAllUsers();
  });

  test('bob can login', async () => { … });
  test('alice can message bob', async () => { … });
});

이는 테스트를 단순화하는 데 유용할 수 있습니다. 공통 설정 및 해체 코드를 모든 테스트에 복제하지 않고 공유할 수 있습니다. 또한 설정 및 해제 코드 자체에서 오류가 발생한다면 이는 테스트 자체가 실패하지 않는 구조적 문제를 나타낼 수 있습니다.

일반 도움말

이러한 프리미티브에 관해 생각할 때 기억해야 할 몇 가지 팁을 소개합니다.

프리미티브는

이 페이지와 다음 몇 페이지의 도구와 프리미티브는 Vitest, Jest, Mocha, 웹 테스트 실행기, 기타 특정 프레임워크와 정확히 일치하지 않습니다. 여기서는 Vitest를 일반 가이드로 사용했지만 이를 원하는 프레임워크에 매핑해야 합니다.

필요에 따라 어설션 혼합 및 매칭

테스트는 기본적으로 오류를 발생시킬 수 있는 코드입니다. 모든 실행기는 고유한 테스트 사례를 설명하는 원시적인 test()를 제공합니다.

그러나 이 실행기가 assert(), expect(), 어설션 도우미도 제공하는 경우 이 부분은 편의성에 관한 것이므로 필요한 경우 건너뛸 수 있습니다. 다른 어설션 라이브러리나 구식 if 문을 비롯하여 오류를 발생시킬 수 있는 모든 코드를 실행할 수 있습니다.

생명을 구할 수 있는 IDE 설정

VSCode와 같은 IDE가 선택한 테스트 도구의 자동 완성 및 문서에 액세스할 수 있도록 하면 생산성을 높일 수 있습니다. 예를 들어 Chai 어설션 라이브러리assert에 100개 이상의 메서드가 있으며 올바른 메서드 문서를 인라인으로 표시하면 편리합니다.

이는 테스트 메서드로 전역 네임스페이스를 채우는 일부 테스트 프레임워크의 경우 특히 중요할 수 있습니다. 이는 미세한 차이이지만, 테스트 라이브러리가 전역 네임스페이스에 자동으로 추가된 경우 가져오기 없이 테스트 라이브러리를 사용할 수 있는 경우가 많습니다.

// some.test.js
test('using test as a global', () => { … });

도우미가 자동으로 지원되는 경우에도 도우미를 가져오는 것이 좋습니다. 그러면 IDE에서 이러한 메서드를 명확하게 조회할 수 있기 때문입니다. (React를 빌드할 때 이 문제가 발생했을 수 있습니다. 일부 코드베이스에는 매직 React 전역이 있지만 일부는 그렇지 않아서 React를 사용하여 모든 파일에서 가져와야 하기 때문입니다.)

// some.test.js
import { test } from 'vitest';
test('using test as an import', () => { … });