ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • FE(프론트엔드)에서 어떤 test를 해야 할까? (TDD는 또 어떤 걸까?)
    CS 2024. 9. 27. 02:34

    개발에 있어서 test라는 걸 생각해봤을 때 내가 아는 건 블랙박스 테스트, 화이트박스 테스트와 같은 내용들이다.

    정보처리기능사 준비를 하면서 공부를 했던 내용이었는데, 어느 정도 느낌만 기억이 나고 자세한 내용까지는 기억이 나지 않았다.

     

    그런데 딱 프론트엔드에서 진행해야할 테스트는 뭐고, 어떤 식으로 진행해야 하는가를 생각해보면 잘 생각은 나지 않아서 다시 조사해보고자 한다.

     

    ✅ 내가 알고 있거나 생각하는 테스트 ✅

     

    프론트엔드에서 테스트를 진행한다

     

     

    이걸 생각했을 때, 딱 떠오르는 테스트는 E2E 테스트이다.

    물론, 단위 테스트나 UI 테스트 같은 것도 들어보기도 했지만 직접 해본 적은 없다.

     

    E2E는 사용자가 어떤 식으로 서비스를 접근하고 사용하게 될지를 시나리오로 작성하고, 시나리오에 맞춰서 테스트를 진행하는 방법

     

    이 방법의 경우에는 시나리오가 다양하게 작성될 수 있기 때문에 고려해야 하는 점들이 너무 많고, 진행까지 많은 시간이 소요되기 때문에 항상 이 방법으로 모든 테스트를 거칠 수 없다.

     

    또한, 어느 정도 기능 개발이 된 상태에서 진행을 해야해서 개발 도중 테스트를 하기에는 어려움이 있는 테스트 방법이라고 생각한다.

     

    결국 프론트엔드에서 테스트를 진행하는 이유는 애플리케이션의 품질에 대한 보증인 코드에 대한 자신감과 근거를 얻기 위함이고, 사용자가 직접 사용을 했을 때 오류를 접하지 않도록 하기 위해 이 테스트라는 것을 계속 진행하는 것이다.

     

    이 테스트를 하는 목적을 정리해보자면 다음과 같다.

     

    📌 테스트의 목적 📌

    결국 주요 목적은 두 가지라고 생각한다.

    1. 서비스에서 발생 가능한 오류 및 결함들을 예방하여, 요구사항 대로 동작이 잘 되는 가를 확인하기 위함
    2. 개발 과정에서 발생하는 변경 사항으로 인해 새로운 문제가 발생하지 않는가를 확인하기 위함

     

    사실 개발만 진행하고 테스트를 해보지 않으면, 기능 요구사항에 맞춰 코드가 잘 작성이 되었는지, 불편함은 없는지 확인할 수 있는 방법이 없다. 제대로 동작하는지 확인도 하지 않고 서비스를 하게 되면 내가 생각하지 못한 오류가 발생하여 사용자에게 불편함을 줄 수 있고, 나 또한 오류가 어디서 발생할지 모르는 불안감이 생기지 않겠는가?

     

    게다가 서비스를 개발하게 되면 처음에 기획하고 작성된 요구사항들로만 서비스가 구성되는가? 절대 그럴 일이 없다.

     

    사용자의 요구사항에 따라 기능이 추가될 수 있고 삭제될 수도 있다. 그리고 계속되는 업데이트 속에서 새로운 코드들이 추가가 될 것인데, 그때마다 내가 작성한 코드가 모두 완벽하게 돌아갈 것이라는 보장도 없는 법이다.

     

    이런 상황들에서 작성한 코드에 대한 믿음과 신뢰를 가질 수 있는 방법이 테스트다.

     

    테스트를 잘 설계하여 진행하면 코드에 대한 자신감을 가질 수 있고, 새로운 기능 추가나 기존 버그 수정, 리팩토링에 대한 부담을 줄일 수 있다. 그리고 테스트를 진행하면서 발생할 수 있는 잠재적인 문제들 또한 예방을 할 수 있다.

     

    위에서 언급한 내용에서 추가적인 사항인데 기술부채를 생각하면 테스트는 필수적이라고 할 수 있다.

     

    ❓ 기술부채 ❓

    기술부채라는 단어를 지나가면서 한번 쯤은 들어봤을 거라고 생각한다.

    이걸 정확히 어떤 말인지 정리를 해보자면 다음과 같다.

    기술적인 문제를 뒤로 미루고, 비즈니스 문제를 먼저 해결하는 상황을 의미한다.

     

    이 기술부채는 주로 개발 속도를 높이거나 빠른 기능 출시를 목표로 할 때 많이 발생하게 된다.

     

    예를 들면, 코드 구조를 최적화하지 않고 임시방편으로 해결하거나, 코드 리뷰 없이 기능을 바로 배포하는 경우 등이 가장 전형적인 사례이다. 이러한 결정은 비즈니스 문제는 일시적으로 해결이 가능하다.

     

    하지만, 이게 누적이 되면 어떻게 될까?

    결국에는 코드의 복잡성은 증가하고, 유지보수가 어려워지기에, 코드를 새로 작성하는 게 나을 만큼 큰 손실을 초래할 수 있고, 이렇게 되면 결국 비즈니스 측면에서도 상당한 위험을 가져올 수 있다.

    이런 상황에서 테스트는 기술부채를 관리하고, 장기적인 유지보수성을 보장하는 데 큰 역할을 한다.

    테스트는 코드를 검증함으로써, 변경이 있을 때 기존 기능이 손상되지 않도록 보호하는 역할을 한다.

     

    특히, 자동화된 테스트는 지속적인 코드 수정 및 배포 과정에서 품질을 유지하는 데 필수적이며, 테스트가 충분히 마련된 환경에서는 코드 리팩토링이나 새로운 기능 추가를 할 때 우려되는 사항들을 덜어줌으로 작업을 할 때 자신감을 가질 수 있다.

     

    단, 제대로 테스트가 작성되지 않으면 코드 변경이 기존 기능을 망가뜨릴 가능성이 커지고, 기술부채 또한 더욱 악화시킬 수 있기에 초기부터 테스트 자동화 전략을 마련하여 기술부채를 줄이고자 하는 것이 좋다.

     


     

    다시 테스트로 돌아가서 생각해보면 테스트 방법은 엄청 다양하고 많다.

    블랙박스 테스트, 그레이박스 테스트, 화이트박스 테스트, 단위, UI, 시스템, 통합, 회귀, E2E, 알파, 인수, 기능, 성능, 베타 등등 .....

     

    이걸 모두 프론트엔드에서 진행하기에는 너무 버겁지 않을까?

    그럼 프론트에서 진행해야할 테스트는 무엇일까?

     

    프론트엔드에서 자주 언급되는 테스트는 다음과 같다.

     

    💡 프론트엔드 테스트의 종류 💡

    1️⃣ 정적(Static) 테스트

    가장 하단에 위치한 정적 테스트는 코드를 실행하기 전에 하는 테스트를 의미한다.

     

    예를 들어, 구문 오류나 나쁜 코드 스타일 등을 검증하는 것이다.

     

    개발자가 코드를 작성할 때, 한 번의 실수도 없이 코드를 모두 성공적으로 작성할 수 있는 것이 아니다.

    그렇기에 코드를 실행하기 전에 이런 개발자의 실수를 잡아내는 테스트이다.

     

    보통 ESlint를 사용해서 사용하지 변수를 찾는다거나 typescript로 변수 타입을 검사하는 등의 방법으로 테스트를 진행할 수 있다.

     

    2️⃣ 단위(Unit) 테스트

    애플리케이션의 개별 기능이나 컴포넌트를 떼어내, 독립적인 환경에서 테스트를 하는 것이다.

     

    각 단위는 독립적으로 실행되며, 외부 의존성이 최소화된 상태에서 검증된다.

     

    이 테스트의 주요 목적은 특정 함수나 컴포넌트가 의도한 대로 작동하는지를 확인하기 위함이다.

    이를 위해 의존성을 제거하거나 대체하려고 mocking 기법을 사용한다.

    💡 [참고] mocking 💡
    : 테스트 완경에서 실제 의존성을 대체하거나 시뮬레이션하는 기법이다.

    코드가 실제 외부 시스템이나 서비스(API, DB 등)에 의존할 때, 이러한 실제 의존성을 테스트용 가짜(mock) 객체나 함수를 사용하여 대체함으로써, 독립적인 테스트 환경을 구성하는 방법이다.

    이를 통해, 의존성 제거가 가능하고, 테스트 속도 향상 및 특정 상황 테스트까지 가능하다는 이점이 있어 사용하는 기법이다.

     

    이 단위 테스트는 매우 빠르고, 작성하는 데 비교적 비용이 적게 든다.

    또한, 코드베이스 내에서 가장 작은 단위인 함수나 컴포넌트를 테스트하기에, 문제 발생 시 해당 문제를 빠르게 파악하는 것이 가능하고 수정도 빠르게 진행할 수 있다.

     

    보통 이 단위 테스트를 진행할 때 Jest와 같은 도구가 사용되며, 테스트 커버리지를 높이기 위해 다양한 테스트 케이스를 정의할 수 있다.

     

    3️⃣ 통합(Integration) 테스트

    애플리케이션의 여러 부분이 서로 어떻게 상호작용하는지 검증하는 테스트이다.

     

    단위 테스트가 개별 구성 요소에 집중한다면, 통합 테스트는 이름 그대로 구성 요소들이 함께 작동할 때 발생할 수 있는 문제를 찾아내는 것을 목표로 한다.

     

    주로 컴포넌트 간의 상호작용이나 API 호출과 같은 외부 시스템과의 통합을 테스트하는 데 사용된다.

     

    이 통합 테스트는 단일 페이지 단위로 실행될 수도 있고, 더 큰 범위에서 애플리케이션의 주요 흐름을 검증할 수도 있다.

    단위 테스트와 동일하게 Jest를 사용할 수도 있고 React Testing Library(RTL)과 같은 도구를 통해 수행할 수 있다.

     

    이를 통해서 UI가 예상대로 동작을 하는지, 이벤트 발생 시에 API 요청이 제대로 이루어지는지, 여러 컴포넌트가 상호작용하며 의도한 결과를 도출하는지 등을 테스트할 수 있다.

     

    단위 테스트보다는 실행 시간이 길고 설정이 복잡할 수 있지만, 여러 컴포넌트가 결합된 실제 애플리케이션의 동작을 더 정확하게 반영할 수 있다는 장점이 있고, 단위 테스트에서 놓칠 수 있는 문제들을 발견할 수 있으며, 애플리케이션의 전반적인 안정성을 높이는 데 중요한 역할을 한다.

     

    4️⃣ E2E(End-to-End) 테스트

    위에서 처음 언급했었던 테스트이지만 다시 한 번 정리를 해보자면,

    애플리케이션의 프론트엔드와 백엔드가 모두 통합된 상태에서 이루어지는 테스트이다.

     

    단위 테스트나 통합 테스트가 각각의 기능이나 상호작용에 집중하게 된다면, 이는 전체적인 흐름을 검증하는 것이다.

     

    위에서 언급했던 것처럼, 사용자가 브라우저를 통해 사용하는 것과 같은 조건에서 모든 것을 테스트하는 것이다.

     

    백엔드와 프론트엔드, API 서버, DB, 인증 시스템 등 모든 것을 포함할 수 있는데, 만약 백엔드 시스템이 준비가 되지 않았거나 접근을 할 수 없는 경우에는 이를 모킹해서 UI와 사용자 상호작용만을 테스트할 수도 있다.

     

    주로 Cypress와 Selenium 같은 도구를 사용하는데, 브라우저에 직접 애플리케이션을 자동으로 실행하고 상호작용을 시뮬레이션할 수 있다.

     

    이 테스트의 경우 실제 사용 환경과 가장 유사한 방식으로 테스트를 진행하므로, 사용자가 직면하게될 문제들을 사전에 발견하고 방지할 수 있다는 장점이 있지만, 작성 및 유지보수에 시간이 많이 소요되고, 실행 속도도 상대적으로 느리다.

     


     

    위 테스트를 다음과 같은 트로피로 나타낼 수 있다.

    테스팅 트로피

     

    이 트로피를 테스팅 트로피라고 하는데, 트로피 모양을 보면 통합 테스트(Integration)의 부분이 가장 넓은 게 보인다.

    왜 통합 테스트의 비중이 더 큰가 싶을 수 있는데, 이는 위에서 설명했던 각 테스트의 특징과 과정들을 보면 알 수 있다.

     

    지금까지 위에서 테스트를 하면 좋은 점들을 계속해서 언급했는데, 그렇다고 모든 테스트를 계속적으로 많이 진행할수록 좋은가? 그건 절대 아니라고 본다. 테스트를 진행하는 것은 좋지만, 테스트를 진행하는 것은 시간 투자이기도 하기에 쓸데없는 것에 많은 시간을 투자하게 되는 경우도 발생하기 때문이다.

     

    위 트로피의 의미는 속도와 비용이 가장 균형에 맞는 테스트인 통합 테스트를 위주로 진행하라는 의미이다.

     

    사실 쪼개서 기능 별로 테스트를 하는 단위 테스트가 더 중요하지 않을까? 싶을 수 있다.

    하지만, 단위 테스트를 잘한다고 해도 통합 테스트에서 동작이 안된다면 단위 테스트에서의 이득이 사라지게 된다.
    또한, 위 이유 말고도 제한된 리소스로 테스팅 효율을 높이기 위해서는 통합 테스트를 작성하는 것이 더 좋다.

    그렇기에 이러한 통합 테스트의 중요성을 강조하기 위한 그림이다.

     


     

    📌 프론트엔드 테스팅 대상 📌

    1️⃣ API 서버 통신

    • 실제 API 서버 이용: 통합 테스트 또는 E2E 테스트를 통해서 실제 API 서버와의 통신을 검증할 수 있다.
    • 테스트 서버 구축: 테스트 전용 서버를 설정하여 검증할 수 있으며, 이는 API 변경에 대한 유연성을 제공한다.
    • 클라이언트 모킹: 클라이언트 측에서 API 호출을 모킹하여 원하는 응답을 쉽게 받을 수 있어 다양한 상황을 테스트할 수 있다. 이를 통해 개발 초기 단계에서도 안정적인 테스트를 수행할 수 있다.
    • Jest 사용: Jest는 리액트 팀에서 제공하는 테스트 도구로, 모킹과 테스트에 매우 유용하다.

     

    2️⃣ 사용자 이벤트

    • 입력 이벤트 처리: 사용자의 입력을 올바르게 처리하는지 검증한다.
    • 테스트 유틸리티 활용: 다양한 테스트 유틸리티를 활용하여 이벤트를 시뮬레이션할 수 있다. 예를 들어, userEvent를 사용하여 노드.js 환경에서 사용자 이벤트를 테스트할 수 있다.
    • E2E에서의 이벤트 발생: E2E 테스트에서는 실제로 이벤트가 발생하도록 시뮬레이션하여, 최종 사용자 경험을 검증한다.
    • Cypress 사용: 브라우저 환경에서는 E2E 테스트 도구인 Cypress를 통해 실제 브라우저에서 사용자 이벤트를 테스트할 수 있다. Cypress는 브라우저 상의 요소를 쉽게 제어할 수 있는 메소드를 제공한다.

     

    3️⃣ 시각적 요소

    • 입력 및 출력 값 테스트: 사용자의 입력과 그에 따른 화면의 변화를 테스트해야 한다.
    • 스냅샷 테스트: 현재 HTML 구조를 저장하여 의도한 대로 렌더링되는지를 확인한다.
    • 시각적 회귀 테스트: HTML과 CSS를 포함하여, 컴포넌트가 실제로 브라우저에서 렌더링되는 모습이 의도한 대로 나타나는지를 검증한다. Storybook을 사용하여 시각적 회귀 테스트를 수행할 수 있다.

     

    테스팅 환경 또한 두 가지로 구분할 수 있는데, 어떤 특징을 가지고 어떤 걸 사용하는 게 좋을지 보기 쉽게 정리해 보았다.

    📌 테스팅 환경 📌

    1. 브라우저 환경
      • 네트워크 IO 및 렌더링 엔진 활용: 실제 브라우저 환경에서 테스트함으로써 다양한 기능을 검증할 수 있다.
      • 호환성 테스트: 다양한 운영체제와 브라우저에서 테스트를 수행하여 호환성을 확인할 수 있다.
      • 속도: 초기 구동 속도가 느리며, 설치가 필요하다. Headless 브라우저를 사용하는 것이 일반적이다.
    2. Node.js 환경
      • 간단한 설치 및 실행: 속도가 빠르고 설치가 용이하다.
      • 모듈 단위 테스트: 모듈 단위로 테스트를 수행할 수 있다.
      • jsdom 사용: DOM 및 BOM API가 없으므로 jsdom을 사용하나, 브라우저 동작을 100% 구현하지는 못한다.

     

    ⚠️ 고려 사항 ⚠️

    • 크로스 브라우징 테스트: 브라우저 환경을 사용해야 한다. 브라우저의 실제 동작(렌더링, 네트워크 IO 등)에 대한 테스트가 필요한 경우에도 브라우저 환경을 활용한다.
    • 어플리케이션의 종류와 규모: 어플리케이션의 복잡성, 규모 및 팀 구성에 따라 적절한 테스트 환경을 선택하고 전략을 수립해야 한다.

     

    이렇게 테스트의 중요성이 강조되는 요즘, 언급되는 개발 접근 방식을 소개해보고자 한다.

     

    ❓TDD(테스트 주도 개발)❓

    소프트웨어 개발 접근 방식 중 하나로, 코드를 작성하기 전에 먼저 테스트를 작성하는 방법론이다.

     

    TDD의 주요 목적은 소프트웨어의 품질을 높이고, 개발 프로세스를 더 효율적으로 만드는 것이다.

    그럼 이 TDD는 어떻게 진행해야 할까?

     

    🤔 TDD의 절차 🤔

    1️⃣ 테스트 작성

    • 개발자는 기능 요구 사항에 따라 실패할 것이 분명한 테스트 케이스를 작성한다. 이 테스트는 작성할 기능이 실제로 구현되지 않았기 때문에 실패한다.
    • 예를 들어, 특정 함수가 특정 입력에 대해 예상된 출력을 반환해야 한다고 가정할 때, 이 함수의 테스트를 먼저 작성한다.

     

    2️⃣ 코드 구현

    • 실패한 테스트를 통과시키기 위해 필요한 최소한의 코드를 작성한다.
    • 작성한 코드가 테스트를 통과하는지 확인한다. 이 단계에서는 코드 품질이나 최적화는 고려하지 않으며, 오직 테스트를 통과하는 것에 집중한다.

     

    3️⃣ 리팩토링

    • 테스트가 통과한 후, 코드를 리팩토링하여 가독성을 높이고 유지 보수성을 개선한다.
    • 이 단계에서도 작성한 테스트가 모두 통과하는지 확인한다. 리팩토링 과정에서 기능에 변화가 없도록 주의해야 한다.

     

    이 과정을 새로운 기능을 추가하거나 기존 기능을 수정할 때마다 반복하게 되는 것이다.

     

    TDD의 장점 ✅

    코드 품질 향상

    • TDD는 코드가 요구 사항에 부합하는지를 지속적으로 검증하므로 코드 품질이 향상된다.

    버그 감소

    • 초기 단계에서 테스트를 작성하므로, 나중에 발생할 수 있는 버그를 조기에 발견하고 수정할 수 있다.

    설계 개선

    • 테스트를 기반으로 코드를 작성하다 보니, 자연스럽게 더 나은 설계와 구조를 갖춘 코드가 작성된다.

    문서화 효과

    • 작성된 테스트는 코드의 사용법을 문서화하는 역할을 한다. 새로운 팀원이 코드를 이해하는 데 도움이 된다.

    안정성

    • 리팩토링이나 새로운 기능 추가 시 기존 테스트가 통과하는지 확인함으로써, 기존 기능의 안정성을 유지할 수 있다.

     

    사실 테스팅이 중요한 만큼 그럼 모든 프로젝트에서 TDD 방법론을 도입하는 것이 좋을 거 같다는 생각이 들 거 같긴한데, 어떤 것이든 무조건 좋다는 없다는 것처럼 TDD도 그렇다. 아래 단점과 함께 그걸 확인할 수 있다.

    🚫 TDD의 단점 🚫

    초기 시간 투자

    • 테스트 작성이 초기 개발 단계에서 추가적인 시간 투자를 요구하기 때문에, 빠르게 개발해야 하는 경우에는 비효율적일 수 있다.

    복잡한 테스트 관리

    • 테스트가 많아질수록 관리와 유지 보수가 어려워질 수 있다. 테스트 케이스를 잘 조직하고 관리하는 것이 중요하다.

    테스트 스킵 가능성

    • 일부 개발자들은 테스트를 작성하는 것을 귀찮아하거나, 필요성을 느끼지 못할 수 있어 TDD의 원칙이 무시될 수 있다.

     

    이런 것들만 보면 TDD라는 것은 결국 확장성 중심보다는 맞춤형 애플리케이션에 적합하다고 생각을 하는데, 전체적인 인터페이스와 구조들이 어떻게 유기적으로 동작하는지 인터페이스를 사용하는 관점에서 테스트를 작성해본다면 또 유용하게 사용할 수 있을 거 같기도 하다.

     

    마지막으로 이 TDD 도구들까지만 정리해보자.

    ⚒️ TDD 도구 ⚒️

    • Jest: 자바스크립트 환경에서 널리 사용되는 테스트 프레임워크로, 리액트 애플리케이션에서도 많이 활용된다.
    • Mocha: 유연한 자바스크립트 테스트 프레임워크로, 다양한 라이브러리와 함께 사용할 수 있다.
    • JUnit: 자바 애플리케이션의 테스트를 위해 사용되는 대표적인 프레임워크이다.
    • RSpec: 루비 언어에서 많이 사용되는 테스트 프레임워크로, BDD(행위 주도 개발) 스타일의 테스트를 지원한다.

     

    처음에는 별 거 없다고 조금은 생각했던 게 테스트였다. 하지만, 디테일하게 들어갈수록 더 어려워지는 거 같다는 느낌을 받았다.

     

    바로 좋은 테스트를 작성하는 것은 어렵고 수많은 연습도 필요하고 고려할 부분에서 여러 고민도 해봐야 해서 처음부터 테스트를 완벽하고 좋게 작성할 수는 없을 거라고 생각한다. 하지만 계속 연습을 많이 해본다면 테스팅도 개발 진행 속도가 향상되는 것만큼 늘 수 있지 않을까 생각한다.

     

    개발 효율성은 떨어지더라도 TDD를 적용해서 꾸준히 연습을 진행해본다면 효과적이게 작용하지 않을까 생각한다.

     

     

     

     

    'CS' 카테고리의 다른 글

    WebView?  (3) 2024.10.03
    SWC(Speedy Web Compiler)?  (1) 2024.10.02
    웹 워커(Web Worker)  (1) 2024.09.24
    Virtual DOM(가상 DOM)  (0) 2024.09.19
    [TS] Typescript를 왜 쓸까?  (2) 2024.09.18
Designed by Tistory.