본문 바로가기

개발

테스트 코드가 어색한가요? 테스트 코드와 친해지기

반응형

테스트 코드가 어색하신가요?

개발자라면 누구나 한 번쯤 테스트 코드나 TDD에 대한 이야기를 들어봤을 것입니다. 그리고, 대부분 이 중요성에 대해서 알을 거라고 생각합니다. 하지만 실제로 테스트 코드를 작성하고 팀에 이를 도입하는 것은 쉽지 않습니다. 저 또한 스타트업 개발자로 근무하면서 사내에 테스트 코드를 도입하고자 했으나 많은 어려움에 부딪혔습니다. 어찌저찌 팀에서 테스트 코드를 작성하기 시작했지만, 마음 한 켠에는 답답함이 남아 있었습니다. '테스트 코드를 이렇게 짜는게 맞는건가...?' 이러한 답답함을 극복하기 위해 많은 강의나 콘텐츠들을 스터디하면서 적용해 나갔고, 점점 팀에는 테스트 코드 작성의 노하우가 쌓이기 시작했습니다. 그리고 현재는 테스트 코드 작성이 어색하지 않은 조직이 되어가고 있습니다.

지금 와서 돌이켜보면, 그 때 '이러한 조언을 받았으면 조금 더 수월하게 테스트 작성을 할 수 있었을텐데...' 하는 것들이 있습니다. 저희가 했던 고민들을 하고 계실 분들에게 조금이나마 도움이 되었으면 하는 마음으로 테스트 코드와 친해지기 위한 네가지 단계를 정리해보았습니다.

테스트 코드와 친해지기 위한 네가지 단계

1단계, 요구사항을 테스트 하자.

테스트 코드 작성에서 가장 중요한 첫걸음은 '요구사항을 테스트 하는 것'입니다. 굉장히 당연한 얘기인 것처럼 들리겠지만, 생각보다 많은 개발자들이 요구사항을 명확히 정의하지 않고 개발을 시작합니다. 요구사항을 제대로 인지하지 못한다면 내가 어떠한 것들을 테스트 해야할 지 결정하기 어렵고, 이는 테스트 코드 작성을 어렵게 만듭니다. 따라서, 개발하기 전 요구사항들을 명확하게 정의하는 것이 중요합니다. 실제 테스트 코드 작성이 어렵다면 요구 사항에 대한 테스트 케이스의 인터페이스만이라도 먼저 작성해보도록 합시다.

def test_add_product_success():
    """
    상품을 장바구니에 성공적으로 추가한다.
    """
    # TODO: 테스트 코드 구현 필요
    pass

def test_add_duplicate_product_failure():
    """
    장바구니에 이미 존재하는 상품을 다시 추가할 수 없다.
    """
    # TODO: 테스트 코드 구현 필요
    pass

def test_add_out_of_stock_product_failure():
    """
    재고가 없는 상품을 장바구니에 추가할 수 없다.
    """
    # TODO: 테스트 코드 구현 필요
    ...

# 기타 테이스 케이스...

이렇게 미리 내가 작성할 코드에 대한 요구사항을 테스트 케이스의 인터페이스로 작성해두면, 개발과정에서 요구사항에 대해 더욱 명확하게 생각할 수 있고, 실제 테스트 코드를 구현하지 않더라도 코드 리뷰과정에서 동료개발자들이 코드를 훨씬 이해하기 쉽게 만들어줍니다.

2단계, 모든 코드를 다 테스트 해야한다는 부담을 내려놓자.

처음 테스트 코드 작성을 하게 되면 모든 것을 다 테스트 해야만 할 것만 같은 부담감을 느끼게 되고, 이는 테스트 코드 작성을 주저하게 만듭니다. 하지만 모든 코드를 다 테스트 해야한다는 부담감을 조금 내려놓아도 괜찮습니다. 테스트 커버리지가 조금 부족하더라도 이를 조금씩 개선해가면 됩니다. 비즈니스에서 중요한 부분부터 조금씩 테스트 커버리지를 높여나간다면 점점 코드의 신뢰도가 높아질 것입니다.

또한, 테스트 작성이 너무 어렵거나 테스트를 작성했을 때 변경에 취약한 부분들이 있습니다. (주로 API나 UI 컴포넌트들이 그렇습니다.) 이럴 때는 해당 계층이 의존하고 있는 모듈들에 대한 테스트를 먼저 작성함으로써 코드의 신뢰도를 개선할 수 있습니다. 예를 들면, 프론트엔드에서 UI 요소들에 대한 요구사항은 보통 외부 세계와 소통하는 경우가 많아 테스트하기 쉽지 않고, 디자인 요소에 많이 의존하고 있기 때문에 수정될 확률이 높습니다. 이 때는 비즈니스 로직과 같은 모듈을 UI요소와 분리하여 코드를 구성하고, 이에 대한 테스트 코드를 작성함으로써 코드의 신뢰도를 높일 수 있습니다.

3단계, 통합테스트로 시작해도 괜찮습니다.

처음 테스트 코드를 작성하기 시작할 때, 막연히 통합테스트는 좋지 않은 것이라고 생각했습니다. 통합테스트를 줄이고 단위테스트를 늘려야 하며, 이를 위해 모킹과 같은 테스트 더블 사용을 권장하는 주장들을 어렵지 않게 찾아 볼 수 있습니다. 해당 주장들은 대부분 마틴 파울러(리팩터링의 저자)의 Test Pyramid를 근거로 들며, 유닛테스트를 더 많이 작성해야 한다고 얘기합니다.

테스트 피라미드

하지만, 조금만 더 들어가보면 마틴파울러도 단위테스트를 절대적으로 더 많이 작성해야 한다고 주장하는 것은 아닙니다. 마틴 파울러는 해당 포스트를 포함하여 다른 포스트에서 다음과 같이 언급합니다.

“나는 외부 리소스를 위해 테스트 더블 (mock, stub 등)을 사용하는 것을 절대적인 규칙이라고 생각하지 않는다. 외부 리소스에 접근하는 것이 충분히 안정적이고 빠르다면, 유닛 테스트에서 사용하지 않을 이유가 없다.”- [UnitTest]

“고수준 테스트가 충분히 빠르고, 신뢰할 수 있고, 변경하기 쉽다면 저수준 테스트가 필요하지 않다고 생각한다." - [TestPyramid]

여러분께서 아직 테스트코드와 충분히 친해지지 않았다고 생각하신다면 통합테스트로 시작해도 괜찮습니다. 통합 테스트는 모킹과 같은 테스트 더블을 활용한 테스트보다 더 노은 신뢰도를 가져갈 수 있다는 장점이 있습니다. 대부분 통합테스트의 단점으로 속도적인 부분을 언급하는데, 실제 테스트케이스가 몇 천, 몇 만개가 아니라면 이 부분은 처음에는 크게 문제가 되지 않습니다. 속도적인 이슈가 발생하면 이 문제를 개선해나가면 됩니다. 오히려 초기에 테스트 더블을 활용한 테스트를 고집하게 되면, 지나친 추상화로 코드의 복잡도가 높아지는 부작용이 발생하게 됩니다.

이 부분에 관해서는, 현재 패스트캠퍼스에서 '현실 세상의 TDD'를 강의하고 계신 이규원님께서 잘 작성하신 정말로 테스트 대역이 필요한가그게 통합 테스트라고? 정말?이라는 글을 읽어 보시면 좀 더 깊은 관점에서 생각해볼 수 있으니 권해드립니다.

4단계, 반드시 실패하는 테스트 코드를 작성하자.

테스트 코드는 실패해야 빛을 발합니다. 테스트 코드를 작성하다 보면 '내가 작성하는 테스트가 필요한 테스트 인가'하는 생각이 들 때가 있습니다. 해당 테스트 코드가 정말로 필요한지를 알기 쉬운 가장 좋은 방법은 해당 테스트가 실패할 수 있는 테스트인지를 따져 보는 것입니다. 내가 요구사항을 제대로 구현하지 않았을 때 해당 테스트가 실패한다면 해당 테스트 코드는 충분한 의미를 다하고 있는 것입니다. (그래서 TDD에서 실패하는 테스트를 확인하는 레드 단계가 굉장히 중요합니다.)

반대로, 내가 작성한 테스트가 구현의 변경에도 실패하지 않는 테스트라면 해당 테스트는 필요가 없는 테스트이거나, 테스트 코드를 잘 못 작성했을 확률이 높습니다. 따라서, 반드시 테스트의 실패를 확인하는 습관을 들이는 것이 좋습니다.

우선 테스트 코드에 손을 내밀자.

이상으로 테스트 코드와 친해지기 위한 4가지 단계를 정리해보았습니다. 그런데, 이 4가지 단계보다 더 중요한 것이 있습니다. 가장 중요한 것은 테스트 코드 작성(도입)을 바로 시작하는 것입니다. 테스트 코드 작성 경험이 부족하다면 어려운 것이 당연합니다. 하지만, 시작하지 않으면 영원히 안전망없이 외줄타기를 하게 되고, 코드의 수정(리팩터링)주기와 기능 추가 주기는 길어집니다. 그만큼 개발자의 성장은 정체되고, 팀의 생산성이 떨어지기 시작합니다.

조금 부족하더라도, 먼저 테스트 코드에 손을 내밀어 보는 것은 어떨까요? 우리 테스트 코드와 친해져봅시다! 🤗

반응형