소프트웨어 테스트의 세계에 첫발을 내딛는 많은 개발자들이, 때로는 어디서부터 시작해야 할지 막막함을 느낄 수 있습니다. 테스트의 가장 큰 가치는 언제 테스트가 실패해야 하는지 아는 것입니다. 하지만 그 실패의 순간을 어떻게 정의하고, 어디서 선을 그어야 할까요? 이 질문에 대한 답은 바로 ‘테스트 경계’에 있습니다.
테스트 경계란 무엇인가요?
테스트 경계는 쉽게 말해, 테스트에서 다루는 시스템의 범위를 결정하는 것입니다. 이 경계를 설정하면 테스트에 포함될 코드와 제외할 코드가 명확해지죠. 이렇게 함으로써 우리는 불필요한 요소로 인해 테스트가 실패하는 것을 방지하고, 진짜로 중요한 부분에 집중할 수 있습니다.
예를 들어, 네트워크 요청을 처리하는 함수를 테스트한다고 가정해봅시다. 이 함수는 외부 서버에서 데이터를 가져오고, 이를 정규화하여 반환하는 작업을 합니다. 이때 네트워크 요청의 성공 여부는 테스트의 관심사가 아닙니다. 왜냐하면 서버의 상태는 우리 코드의 통제 밖에 있기 때문이죠. 그래서 우리는 서버 요청을 모의(Mock)하여, 네트워크 문제가 테스트 결과에 영향을 미치지 않도록 합니다. 이것이 바로 테스트 경계를 설정하는 대표적인 방법 중 하나입니다.
테스트 경계 설정하기
여기 fetchUser 함수를 예로 들어보겠습니다. 이 함수는 사용자 ID를 받아 해당 사용자의 정보를 가져오는 역할을 합니다.
import { toCamelCase } from './to-camel-case.js';
export async function fetchUser(id) {
const response = await fetch(`/user/${id}`);
const user = await response.json();
return toCamelCase(user);
}
위 코드에서, fetchUser 함수는 세 가지 주요 단계를 수행합니다:
- 사용자의 정보를 가져오기 위해 서버에 요청합니다.
- 응답 본문을 JSON으로 변환합니다.
- JSON 객체의 키를 카멜 케이스로 변환합니다.
이제 이 함수를 테스트하는 코드를 작성해보겠습니다.
import { fetchUser } from './fetch-user.js';
test('fetches the user by id and normalizes the keys', async () => {
// Mock fetch to prevent actual network request
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ first_name: 'John', last_name: 'Doe' })
})
);
const user = await fetchUser('abc-123');
// Check if fetchUser returns the user object with camelCased keys
expect(user).toEqual({ firstName: 'John', lastName: 'Doe' });
// Ensure fetch was called with correct endpoint
expect(fetch).toHaveBeenCalledWith('/user/abc-123');
});
이 테스트는 다음과 같은 경계를 설정했습니다:
- 네트워크 요청을 모의(Mock)하여 실제 서버 요청을 제거하고, 오로지 함수의 로직만 테스트합니다.
- toCamelCase 함수는 실제로 호출되어, 응답 객체의 키를 올바르게 변환하는지 확인합니다.
이처럼, 테스트 경계를 설정함으로써 우리는 함수의 핵심 동작에만 집중할 수 있습니다. 이런 방식으로 테스트를 구성하면 예상치 못한 외부 요소로 인한 오류를 줄이고, 테스트의 신뢰성을 높일 수 있습니다.
왜 테스트 경계가 중요한가요?
테스트 경계를 올바르게 설정하는 것은 단순히 테스트가 실패하지 않게 만드는 것 이상의 의미가 있습니다. 경계를 설정함으로써, 우리는 테스트의 신뢰성을 높이고, 코드의 의도에 맞게 테스트를 최적화할 수 있습니다. 잘못된 경계 설정은 불안정한 테스트를 만들고, 이는 오히려 삭제하는 것이 나을 수도 있습니다.
예를 들어, fetchUser라는 함수를 테스트한다고 생각해봅시다. 이 함수는 사용자 정보를 가져와, 이를 특정 형식으로 변환하여 반환하는 역할을 합니다. 이때 중요한 것은 네트워크 요청이나 데이터 변환의 결과가 아니라, 함수가 예상된 동작을 수행하는지 여부입니다. 따라서 네트워크 요청이나 데이터 변환 로직을 모의하여, 오로지 함수의 동작에만 집중할 수 있습니다.
결론: 경계를 사랑하세요
테스트 경계를 설정하는 것은, 단순히 테스트의 실패를 방지하는 것이 아니라, 코드의 진짜 의도와 중요성을 파악하게 해줍니다. 테스트 경계를 통해 불필요한 부분을 제거하고, 진짜 중요한 부분에 집중할 수 있게 되기를 바랍니다.
참고 자료: Epic Web Dev, “What Is A Test Boundary?”