본문 바로가기
테스트코드 & 정적분석

테스트 메소드 (함수) 이름은 비즈니스 내용을 사용하기

by 향로 (기억보단 기록을) 2023. 8. 5.
반응형

테스트 메소드 (함수) 이름은 비즈니스 내용을 담아야한다.
이는 테스트의 의도를 명시적으로 표현하기 때문에 중요한데, 테스트 코드를 작성하는 것에 집중한 나머지 이름에 대해서는 크게 신경쓰지 않고 넘어가는 경우가 많다.

예를 들어 다음과 같은 기존의 테스트 코드가 있다고 가정해보자.

// bad
describe('ArticleService', () => {
    it('create article pending status', async () => {
        const limitOverUserId = await createLimitOverUser();

        const article = await sut.create(limitOverUserId, '테스트글');

        expect(article.status).toBe(ArticleStatus.PENDING);
    });
});

이 테스트의 문제는 무엇일까?

  • 테스트 함수명으로 무엇을 검증하는지 알 수가 없어 테스트 코드 전체를 봐야만 내용을 이해할 수 있다.
  • (리팩토링, 기획 변경 등으로) 테스트가 실패할 경우 원래 의도한 동작이 무엇인지 명확하지 않아 테스트를 수정하기가 어렵다.

그럼 테스트 메소드(함수)의 이름은 어떻게 만드는 것이 좋을까?

나는 아래 내용을 담아서 코드를 작성한다.

  • 테스트 대상
  • 어떤 상황에서
  • 예상되는 결과

3가지 내용을 테스트 메소드 (함수) 이름에 항상 한글로 작성한다.

쉬운 예로, "사용자가잔액이부족하면결제가거부됨"과 같은 메소드 이름은 해당 테스트가 어떤 상황을 검증하고 있는지 명확하게 보여준다.

물론 BDD 스타일이 지원되는 테스트 프레임워크에서는 다음과 같이 좀 더 섬세하게 작성할 수 있다.

describe(테스트 대상의 클래스)
 - describe(테스트 대상 - 클래스의 메소드)
   - it (어떤 상황에서 테스트 대상이 수행되면 예상되는 결과가 무엇인지)
  • 공식적으로 context를 지원하지 않는 테스트 프레임워크들이 많아서 describe 를 중첩으로 사용하는 편이다.

위 내용을 담아서 다시 테스트 코드를 작성한다면 다음과 같다.
(본문의 내용은 전혀 달라진게 없다.)

// good
describe('ArticleService', () => {
    describe('create', () => {
        it('하루 글쓰기 횟수가 초과한 사용자가 작성할 경우, 작성된 글은 PENDING 상태가 된다.', async () => {
            const limitOverUserId = await createLimitOverUser();

            const article = await sut.create(limitOverUserId, '테스트글');

            expect(article.status).toBe(ArticleStatus.PENDING);
        });
    });
});

테스트 메소드 이름을 좀 더 비즈니스 내용에 담게 되면 구체적으로 다음과 같은 장점을 얻게 된다.

  • 가독성 증가
    • 코드를 읽는 사람들이 해당 테스트가 어떤 비즈니스 케이스를 확인하고 있는지 더 쉽게 이해할 수 있다.
  • 문서화로 사용 가능
    • 해당 테스트 코드는 실제 비즈니스 로직이 어떻게 작동해야 하는지에 대한 간결한 문서 역할을 할 수 있다.
  • 유지보수 용이
    • 나중에 요구사항이 변경되거나 코드가 수정될 때 해당 테스트가 어떤 부분을 검증하고 있는지 파악하기 쉽다.
  • 에러 추적 용이
    • 테스트가 실패할 경우, 테스트 메소드의 이름이 비즈니스 내용을 반영하고 있으면 어떤 비즈니스 요구사항이 만족되지 않았는지를 빠르게 파악할 수 있다.

이뿐만 아니라, 비즈니스 내용으로 테스트 이름을 짓는 습관이 있으면 TDD가 훨씬 쉬워진다.
예를 들어, 나는 기획서를 받으면 구현체를 받기 전에 다음과 같이 기획서 내용을 그대로 이름만 있는 테스트 코드들을 먼저 작성한다.

describe('PointService', () => {
    describe('use', () => {
        it('보유 포인트가 주문 금액보다 크면 포인트가 주문금액만큼 차감된다.', async () => {});

        it('보유 포인트가 주문 금액보다 부족하면 InsufficientPointsException이 반환된다.', async () => {});
        
        ...
    });
});

아무런 구현체가 없는 코드이지만, 기획서에 있는 비즈니스 내용만 있는 테스트 코드를 작성하고 나면 비즈니스 내용이 누락될 일이 거의 없다.

구현에 집중하느라 매번 빼먹는 예외조건, 특정 케이스 등이 있다면 굉장히 유용하다.

물론, 테스트 메소드명이 너무 길어지면 일부 개발자들은 이를 복잡하다고 느낄 수 있으며, 이름만으로 모든 비즈니스 로직을 반영하기 어려울 수도 있기 때문에, 적절한 주석과 함께 사용하는 것도 적절하게 고려하면 좋다.

반응형