개발자
1. 로그인 폼에 대한 테스트를 작성하다 생긴 의문이 있습니다. 현재 테스트에 대해 공부하며 로그인 폼에 대한 테스트 코드를 작성하고 있었습니다. 그러다보니 로그인 폼에 포함되는 이메일 인풋 에서 진행했던 테스트와 유사한 테스트 코드를 반복해서 작성하고 있는걸 발견했습니다. 예를들어 이메일 입력에 유효하지 않은 이메일 입력이 들어오면 인풋 아래에 에러 텍스트가 보여야 한다 라는 테스트를 작성 했습니다. 그런데 로그인 폼에서 이런 로직을 포함하는 통합적인 로직을 테스트 하고 있었습니다. 이런 경우 로그인 폼에서는 이메일 인풋에서 작성한 테스트는 패스하고 좀 더 통합적인 테스트를 작성해야 할까요? 2. TDD를 도입한다면 어떤 테스트 코드부터 작성 되어야 하나요? 예를 들어 위 처럼 로그인 폼에 대한 통합 테스트가 있고 각 인풋 그리고 버튼 에 대한 유닛 테스트가 있다고 생각했을 때 어떤 테스트가 먼저 작성 되어야 하는지 궁금합니다. 통합 테스트를 먼저 작성하게 되면 이 통합적인 기능이 완료 되기 전까지는 계속해서 테스트가 실패 할 텐데 그래도 괜찮은가? 하는 궁금증이 있습니다!
답변 1
1. 질문의 내용 중 "이런 로직을 포함하는 통합적인 로직을 테스트"라고 하는 부분이 잘 이해가 안됩니다. 예를 들어 아래와 같은 간단한 form이 있다고 가정해보겠습니다. ------------------------------ <로그인 폼> <- 로그인 폼은 isFormValid() 함수도 포함하고 있음. <아이디(이메일) 인풋> <암호 인풋> <로그인 버튼> </로그인 폼> ------------------------------ <아이디(이메일) 인풋> -> 얘가 잘 작동하는지 테스트 <암호 인풋> -> 얘가 잘 작동하는지 테스트 여기까진 뭐 토론꺼리가 없습니다만, <로그인 버튼> -> 얘를 클릭했을 때 <아이디(이메일) 인풋>과 <암호 인풋>까지 한꺼번에 통합해서 테스트를 하겠다는 이야기로 들리는데, 왜 그렇게 해야하는지가 이해 안됩니다. 저의 경험에 비춰보면 <로그인 버튼>에 대한 테스트는 "관련된 인풋 값들에 에러가 있을 때" "관련된 인풋 값들에 에러가 없을 때" 정도로 tc를 구성하고, id, pw 인풋은 같이 render 할 필요 없이 걍 있는셈치고(또는 관련된 인풋들의 종류나 갯수는 상관이 없이) <로그인 버튼>만 고립시켜서 테스트가 가능하도록 설계하는게 바람직할 것 같거든요. 위에 제시한 <로그인 폼> 예시 같은 구조라면 <로그인 버튼>을 클릭했을 때 isFormValid()가 호출되고 그 결과에 따라 다음 동작이 결정되는 구조일거고, 그렇다면 isFormValid()를 mocking 해서 상황에 맞게 "항상 true를 반환"하거나 "항상 false를 반환"하게 설정해놓고 그에 맞게 <로그인 버튼>의 동작을 테스트해야 할 거에요. 당연히 isFormValid()에 대한 tc도 따로 작성하고, 만약 전체를 다 담고 있는 <로그인 폼>에 또 다른 기능들이 포함되어 있다면 이에 대한 테스트들도 다 이런 비슷한 느낌으루다가 고립/독립시켜서 수행이 되어야 할 것 같습니다. 이러한 관점에서 보면 질문에 언급된 내용중 "이메일 인풋에서 작성한 테스트는 패스하고"까지는 잘 이해가 되지만 그 뒤에 이어지는 "좀 더 통합적인 테스트"라는 것이 구체적으로 어떤 것을 이야기 하는지 이해가 안 되는 겁니다. 그리고 만약 이렇게 독립/고립시켜서 테스트가 어려운 형태라면 저라면 테스트가 쉬운 형태로 리펙토링 할 것 같습니다. 2. 테스트 작성 순서는 상관이 없을 것 같습니다. 질문 내용처럼 "기능이 완료 되기 전까지는 계속해서 테스트가 실패 할 텐데" 라는 상황은 너무나 당연한 과정입니다. 그 tc fail을 영원히 방치할 생각이 아니라는 전제 하에 말이죠. (당연히 그렇겠죠?) 구현 중인 기능에 제거하기 힘든 외부 의존성이 있다거나 같이 엮여서 돌아가는 연관기능 중에 mocking 하기가 곤란한게 있다거나 하는건 진짜 비일비재 합니다. 그렇기 때문에 이런 저런 이유로 기능이 완성될 때까지 계속 fail하는 tc가 있다면 그건 그렇게 완성될 때까지 진행하는게 맞는 겁니다. 근데 이런 코딩 과정과 별개로 중간 중간 커밋하면서 진행하고 싶은데 팀에 "tc에 fail 있으면 머지할 수 없셈!!" 이런 컨벤션이 있다거나 하다면, tc fail이 없는 내용들만 모아서 따로따로 커밋하고, 최종적으로 기능이 다 완성됐을 때 계속 fail이 발생하던 녀석들을 모아서 막판에 커밋한다거나 또는 리뷰어에게 양해를 구해고 fail 발생하는 tc만 나중에 따로 커밋하는 식으로 할 수 밖에 없습니다. 참고로 저의 경험상 팀의 리뷰/테스팅 컨벤션에 이정도 유연성도 없고 말이 안 통한다면 그건 컨벤션에 문제가 있다고 봅니다.
최기환
작성자
항해 플러스 플러스 프론트엔드 1기 • 2024년 05월 01일
답변 감사합니다! 제가 테스트 코드 작성에 익숙치 않아서 좋은 테스트 코드를 작성하는 방법을 열심히 찾아가고 있습니다! 아직 부족해서 헷갈리게 해드렸네요...ㅎ 테스트를 작성하다보면 이 컴포넌트가 하는 기능에 대한 모든 경우의 테스트 코드를 작성해야 한다 라는 강박이 스스로에게 있는거 같았습니다. 그래서 로그인 폼에서 사용되는 인풋 컴포넌트에 대한 유닛 테스트를 이미 작성했는데 로그인 폼의 기능에 대한 통합 테스트 코드를 작성할 때 이미 검증했던 인풋 컴포넌트 기능의 테스트를 포함해서 작성하고 있더라고요! 그래서 통합 테스트는 어떻게 작성해야 하지? 하는 의문이 생겨서 질문 드렸습니다! 통합테스트가 여러 유닛테스트가 상호작용하는 기능에 대한 테스트 라고 들은거 같습니다! 통합 테스트를 작성하려고 하는 기능에 초점을 둬야 하는지 아니면 이를 구성하는 코드의 검증에 초점을 둬야하는지 잘 모르겠습니다!
지금 가입하면 모든 질문의 답변을 볼 수 있어요!
현직자들의 명쾌한 답변을 얻을 수 있어요.
이미 회원이신가요?
커리어리 AI 봇
GPT가 달아주는 답변입니다.
2024년 05월 01일
안녕하세요 최기환님, 여러분의 질문에 답변 드리겠습니다. 1. 로그인 폼 테스트와 이메일 입력 테스트 간의 중복은 어느 정도 필요할 수 있습니다. 우선적으로, 각각의 컴포넌트는 독립적으로 작동해야 하므로 그들 자체의 유효성 검사 로직을 가지고 있어야 합니다. 따라서 이메일 입력 컴포넌트를 위한 유닛 테스트에서는 '유효하지 않은 이메일이 입력되면 에러 메시지가 나타나는가?'에 초점을 맞추어야 합니다. 반면에 로그인 폼에서는, 전반적인 상호작용(예: 모든 필수 입력란이 채워져 있는 경우에만 '로그인' 버튼이 활성화되는가?)에 대한 시나리오를 사용하는 통합 테스트를 작성해야 합니다. 2. TDD (Test-Driven Development)의 기본 원칙에 따르면 먼저 실패하는 테스트를 작성한 후 해당 테스트를 통과하게 하는 코드를 작성합니다. 그런 다음 코드를 리팩토링합니다. 일반적으로 개발자들은 유닛 테스트부터 시작해서 해당 기능이 모든 유닛 테스트를 통과하면, 그 다음에는 통합 테스트로 넘어갑니다. 이러한 방식은 코드의 각 부분이 올바르게 작동하는지 확인하고, 전체 시스템이 예상대로 작동하는지 검증할 수 있게 합니다. 따라서 큰 사진을 보는 통합 테스트를 먼저 작성하면 초기에 많은 실패를 경험할 수 있지만, 이것이 TDD를 도입하기로 결정한 순간부터 받아들여야 하는 실제입니다. 기억하셔야 할 중요한 점은 TDD는 단순히 실패하는 테스트를 성공으로 만드는 것을 넘어서 고품질의 소프트웨어 구조와 유지보수 가능한 코드를 원칙적으로 도모한다는 것입니다. 따라서 목표 기능을 달성하기 위해 필요한 모든 측면(유닛, 컴포넌트, 통합)의 테스트 케이스를 고려하는 것이 중요합니다.
지금 가입하면 모든 질문의 답변을 볼 수 있어요!