Notice
Recent Posts
Recent Comments
Link
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

은학의 코딩 일기장

[React] TestCode & TDD 본문

React

[React] TestCode & TDD

<Eunhak> 2023. 4. 21. 16:05

테스트코드란?

테스트코드란 내가 작성한 메서드가 실제로 재대로 동작하는지 테스트 하는 코드이다.

기능을 개발한 후 잘 구현되었는지 테스트 코드를 작성하고 리팩토링을 통해 향상시킬 수 있다.

의도된 대로 정확히 작동하는지 검증하는 절차이다.

테스트 코드…왜 써야되죠?

테스트 코드를 작성하는 이유는 크게 세 가지로 요약할 수 있습니다.

  1. 소프트웨어의 품질 향상: 테스트 코드를 작성하면 소프트웨어가 예상대로 동작하는지 여부를 확인할 수 있습니다. 이를 통해 버그를 발견하고 수정할 수 있으며, 이는 소프트웨어의 품질을 향상시키는 데 큰 역할을 합니다.
  2. 코드 유지 보수 용이성: 테스트 코드를 작성하면 소프트웨어의 코드를 변경할 때마다 기존 코드의 기능이 올바르게 동작하는지 확인할 수 있습니다. 이는 코드를 유지 보수하기 쉽게 만들어줍니다.
  3. 개발 생산성 향상: 테스트 코드를 작성하면 개발자는 코드 작성시 발생할 수 있는 에러를 미리 발견할 수 있기 때문에 디버깅 시간을 절약할 수 있습니다. 또한, 코드 수정시에도 기존 코드가 올바르게 동작하는지 확인할 수 있기 때문에 수정에 소요되는 시간을 줄일 수 있습니다.

테스트 코드도 추가되기 때문에 관리해야 할 코드의 양이 늘어나는 것은 맞지만이것보다 테스트 코드를 유지함으로써 얻는 이점이 더 많다.

테스트 코드 작성의 절차

소스코드가 있고, 요구사항(함수, 특정한 기능, UI, 성능, API 스펙)에 따라서 다양한 테스트가 있다**.모든 테스트가 패스되면 끝**이다.Fail된 테스트는 Modify or fix 해야한다.

테스트의 종류

테스트는 테스트 대상 범위나 성격에 따라

UI test, Integration test, Unit test 세 가지로 구분한다.

 

 

Unit Test (단위 테스트)

소형 테스트에 속하는 테스트.

클래스 범주 내에서 작은 단위 (함수)의 기능에 대한 유효성을 검증하는 테스트이다.단위 테스트는 매우 간단하고 명확하며 빠르게 실행된다는 특징이있다.하나의 함수에 대한 하나 이상의 테스트가 존재할 수 있고, 각각의 조건에 대한 유효성을 검증한다.이렇게 작성된 테스트가 많을수록 해당 로직에 대한 신뢰도가 높아질 수 있다.또한, 작게 쪼개진 단위 테스트는 해당 로직이 어떤 역할을 하는지 쉽게 파악할 수 있다.

react 예시 )

  • 컴포넌트가 잘 렌더링된다.
  • 컴포넌트의 특정 함수를 실행하면 상태가 우리가 원하는 형태로 바뀐다
  • 리덕스의 액션 생성 함수가 액션 객체를 잘 만들어낸다
  • 리덕스의 리듀서에 상태와 액션객체를 넣어서 호출하면 새로운 상태를 잘 만들어준다.

Integration Test (통합 테스트)

중형 테스트에 속하는 테스트.

서로 다른 모듈 혹은 클래스 간 상호작용의 유효성을 검사하는 테스트이다.이러한 통합 테스트가 필요한 이유는 각각 단위 테스트가 검증되었다 하더라도, 모듈 간 인터페이스 및 데이터 흐름이 의도한 대로 작동하지 않는 경우도 있기 때문이다.

또한 각 모듈에 대한 설정 또는 테스트를 하기 위해 사전 조건이 필요한 경우도 있기 때문에 조금 더 넓은 범위에서 이루어지는 통합테스트가 필요하다.

단위 테스트보다 테스트 코드를 작성하기가 복잡하만 단위 테스트 보다 더 넓은 범위의 종속성까지 테스트함으로써 단위 테스트 보다 좀 더 유의미한 테스트가 되는 경우가 많다.

react 예시 )

  • 여러 컴포넌트들을 렌더링하고 서로 상호 작용을 잘 하고 있다
  • DOM 이벤트를 발생 시켰을 때 우리의 UI 에 원하는 변화가 잘 발생한다
  • 리덕스와 연동된 컨테이너 컴포넌트의 DOM 에 특정 이벤트를 발생시켰을 때 우리가 원하는 액션이 잘 디스패치 된다

UI Test

대형 테스트로 분류되는 테스트.

실제 사용자들이 사용하는 화면에 대한 테스트를 하여 서비스의 기능이 정상적으로 작동하는지 검증하는 테스트이다.UI 테스트는 실제 앱을 사용하는 사용자의 흐름에 대해 테스트 함으로써 UI 변경 사항으로 발생할 수 있는 문제를 사전에 차단함으로써 신뢰성을 높인다.

하지만 화면과 직접적으로 연관되어있는 테스트이다보니 실행 시간도 오래 걸리고 디자인이 변경될 때마다 테스트 코드의 수정이 필요하기 때문에 유지보수 비용이 크다.

react 예시 )

  • 사용자가 입력한 값이 제대로 처리된다. ⇒ 로그인 폼에 사용자 이름과 비밀번호를 입력하고 제출하면 제대로 처리되는지 확인한다.
  • UI 요소의 상태 변화가 예상한 대로 수행된다. ⇒ 모달창이 열리고 닫히는 것을 확인한다.
  • 여러 페이지 간에 이동하는 동작이 예상한 대로 작동한다. ⇒ 사용자가 페이지를 이동하면 다음 페이지가 예상한 대로 표시되는지 확인합니다.

 

 

테스트 피라미드

테스트 피라미드는 테스트하고자하는 단위와 목적에 따라 테스트를 구분하고 있다.

단위 테스트는 가장 작은 단위인 함수를 검증하고, 통합 테스트는 모듈 간 상호작용이 올바르게 동작하는지 검증하고, UI 테스트는 가장 마지막에 사용자 인터랙션이 오류없이 동작하는지 검증한다.

피라미드 형태로 표현된 이유는 테스트 코드를 작성하는 데 필요한 비용도 다르기 때문이다.

단위 테스트는 검증하려는 범위가 작기 때문에 작성이 수월하고,

검증 범위도 작기 때문에 무수히 많은 테스트가 작성될 수 있다.

통합 테스트는 좀 더 넓은 범위의 검증이기 때문에 고려할 부분이 많아서 더 많은 비용이 필요하다.

UI 테스트는 가장 많은 비용과 시간이 필요하지만 사용자 시나리오 기반으로 작성하기 때문에 테스트 코드의 양은 적다.

구글 공식 문서에서는 각 테스트 비율을 단위테스트는 70%, 통합테스트는 20%, UI테스트틑 10%로 구성하는 것이 좋다고한다.

테스트를 처음 도입하는 분이라면 비교적 작성의 비용이 적은 단위 테스트부터 시작하는 것을 추천

 

 

 


 

테스트코드를 작성할 때는 아래 두개의 원칙을 따르면서 하는게 좋음

일곱 테스트 원칙 (Seven Testing Principles)

소프트웨어 테스팅 분야에서 40여년 간 제안되고 발전해 온 일곱 개의 기본 원칙이다.

각 항목에 대한 자세한 내용은 Sevent Testing Principles 문서에서 확인 가능하다.

  • 스팅은 결함의 존재를 보여주는 것이다.
  • 완벽한 테스트는 불가능하다.
  • 테스트 구성은 가능한 빠른 시기에 시작한다.
  • 결함은 군집되어 있다.
  • 살충제 역설(Pesticide Paradox) — 비슷한 테스트가 반복되면 새로운 결함을 발견할 수 없다.
  • 테스팅은 정황에 의존적이다.
  • 오류 부재의 오해 — 사용되지 않는 시스템이나 사용자의 기대에 부응하지 않는 기능의 결함을 찾고 수정하는 것은 의미가 없다.

F.I.R.S.T 단위 테스트 원

단위 테스트는 가장 작은 단위의 테스트이며, 모든 테스트의 시작점이다. 단위 테스트만 구성되어도 굉장히 많은 문제를 해결할 수 있으며, 코드 품질이 훨씬 좋아질 수 있다. F.I.R.S.T 원칙은 다음 각 항목의 앞 글자를 딴 원칙이다.

  • Fast — 유닛 테스트는 빨라야 한다.빠르다의 속도는 상대적일 수 있지만 적어도 통합테스트를 돌리는데 있어서 부담감을 느끼지 않을 정도여야 한다고 생각합니다.
  • 각 테스트는 **빠르게 수행**되어야 합니다.
  • Isolated — 다른 테스트에 종속적인 테스트는 절대로 작성하지 않는다.A테스트의 결과가 B테스트의 결과의 영향을 미쳐서는 안됩니다
  • 각 테스트는 **독립적**으로 수행되어야 하며,
  • Repeatable — 테스트는 실행할 때마다 같은 결과를 만들어야 한다.1번 수행하든, 10번 수행하든 성공, 실패의 결과가 매번 동일해야 합니다.
  • 테스트는 수정이 발생하지 않는 한 매번 결과가 같아야 합니다.
  • Self-validating — 테스트는 스스로 결과물이 옳은지 그른지 판단할 수 있어야 한다. 특정 상태를 수동으로 미리 만들어야 동작하는 테스트는 작성하지 않는다.테스트가 스스로 성공과 실패를 가늠하지 않는다면 판단은 주관적이되며 지루한 수작업 평가가 필요하게 됩니다
  • 각 테스트의 결과는 '**성공 또는 실패**'여야 합니다
  • Timely — 유닛 테스트는 프로덕션 코드가 테스트를 성공하기 직전에 구성되어야 한다.코드를 구현한 다음에 테스트 코드를 만들면 실제 코드가 테스트하기 어렵다는 사실을 발견할지도 모릅니다
  • 이부분의 내용은 테스트주도 개발과 밀접한 관련이 있습니다
  • 단위테스트는 테스트 하려는 실제 코드를 구현하기 직전에 구현해야 합니다

 

 


TDD란?

Test Driven Development의 약자로 ‘테스트 주도 개발’이라고 한다.

반복 테스트를 이용한 소프트웨어 방법론으로 작은 단위의 테스트 케이스를 작성하고 이를 통과하는 코드를 추가하는 단계를 반복하여 구현한다.

즉, 개발자가 테스트 코드를 먼저 작성하면서 개발을 진행하는 방법론을 테스트 주도 개발(Test-Driven Development, TDD)이라고 부른다.

일반 개발 방식과 TDD 개발 방식의 비교

일반 개발 방식

보통 개발 방식은 ‘요구사항 분석 → 설계 → 개발 → 테스트 → 배포’ 형태의 개발 주기를 갖는다.

 

 

이러한 방식은 소프트웨어 개발을 느리게 하는 잠재적 위험이 존재한다.

그 이유는 아래와 같다.

소비자의 요구사항이 처음부터 명확하지 않을 수 있다. 따라서 처음부터 완벽한 설계는 어렵다. 자체 버그 검출 능력 저하 또는 소스코드의 품질이 저하될 수 있다.자체 테스트 비용이 증가할 수 있다.

이러한 문제점이 발생되는 이유는 어느 프로젝트든 초기 설계가 완벽하다고 말할 수 없기 때문이다.

고객의 요구사항 또는 디자인의 오류 등 많은 외부 또는 내부 조건에 의해 재설계하여 점진적으로 완벽한 설계로 나아간다.

재설계로 인해 개발자는 코드를 삽입, 수정, 삭제하는 과정에서 불필요한 코드가 남거나 중복처 될 가능성이 크다.

결론적으로 이러한 코드들은 재사용이 어렵고 관리가 어려워서 유지보수를 어렵게 만든다.

작은 부분의 기능 수정에도 모든 부분을 테스트해야 하므로 전체적인 버그를 검출하기 어려워진다. 따라서 자체 버그 검출 능력이 저하된다. 그 결과 어디서 버그가 발생할지 모르기 때문에 잘못된 코드도 고치지 않으려 하는 현상이 나타나게 된다.

이 현상은 소스코드의 품질 저하와 직결된다. 작은 수정에도 모든 기능을 다시 테스트해야하는 문제가 발생하여 자체 테스트 비용이 증가된다.

TDD 개발 방식

TDD와 일반적인 개발 방식의 가장 큰 차이점은 테스트 코드를 작성한 뒤에 실제 코드를 작성한다는 것이다.

디자인(설계) 단계에서 프로그래밍 목적을 반드시 미리 정의해야만 하고, 무엇을 테스트해야 할지 미리 정의(테스트 케이스 작성)해야만 한다.

테스트 코드를 작성하는 도중 발생하는 예외 사항(버그 및 수정사항)은 테스트 케이스에 추가하고 설계를 개선한다.

이후 테스트가 통과된 코드만을 코드 개발 단계에서 실제 코드로 작성한다.

 

 

이러한 반복적인 단계가 진행되면서 자연스럽게 코드의 버그가 줄어들고 소스코드는 간결해진다.

또한 테스트 케이스 작성으로 인해 자연스럽게 설계가 개선됨으로 재설계 시간이 절감된다.

 

 

 

TDD 개발주기

 

위 그림은 TDD의 개발주기를 표현한 것이다.

{ Red } 단계에서는 실패하는 테스트 코드를 먼저 작성한다.

{ Green } 단계에서는 테스트 코드를 성공시키기 위한 실제 코드를 작성한다.

{ Blue } 단계에서는 중복 코드 제거, 일반화 등의 리팩토링을 수행한다.

1. RED : 테스트 실패

  • 구체적인 하나의 요구사항을 검증하는 하나의 테스트를 추가한다.
  • 추가된 테스트가 실패하는지 확인한다.

실패하는 것이 확인 되어야, 테스트가 검증력을 가진다고 신뢰할 수 있다. 실패의 이유는 운영 코드가 아직 변경되지 않았기 때문이어야 한다. 테스트 코드의 문제이면 안 된다.

 

2. GREEN : 테스트 성공

  • 추가된 테스트를 포함하여, 모든 테스트가 성공하게끔 운영 코드를 변경한다.
  • 테스트의 성공은 모든 요구사항을 만족했음을 의미한다.
  • 테스트 성공을 위한 최소한의 코드 변경만 진행한다.

TDD를 반복하다 보면, 지루함을 느끼는 프로그래머가 많다. 하지만 TDD에서는 테스트 성공을 위한 최소한의 코드 그 이상을 변경하거나 추가하면 안 된다. 테스트 되지 않은 코드가 중간에 추가되면, 이후 리팩토링 등의 다른 프로세스에서 어떤 부작용을 가져올지 알 수 없기 때문이다.

3. REFACTOR : 리팩토링

  • 코드베이스를 정리한다.
  • 인터페이스 뒤에 숨어 있는 구현 설계를 개선한다.
  • 가독성, 적용성, 성능을 고려한다.

 

중요한 것은 실패하는 테스트 코드를 작성할 때까지 실제 코드를 작성하지 않는 것과, 실패하는 테스트를 통과할 정도의 최소 실제 코드를 작성해야하는 것이다. 이를 통해 실제 코드에 대해 기대되는 바를 보다 명확하게 정의 함으로써 불필요한 설계를 피할 수 있고, 정확한 요구 사항에 집중할 수 있다.

TDD 세부 프로세스

‘단위 테스트 작성 → 단위 테스트 실행 → 운영 코드 작성 → 단위 테스트 실행 → 설계 개선(리팩토링) → 단위 테스트 → … ‘ 실행 반복한다.

 

 

 

 

TDD 개발 방식의 장점

보다 튼튼한 객체 지향적인 코드 생산

TDD는 코드의 재사용 보장을 명시하므로 TDD를 통한 소프트웨어 개발 시 기능 별 철저한 모듈화가 이뤄진다.

이는 종속성과 의존성이 낮은 모듈로 조합된 소프트웨어 개발을 가능하게 하며 필요에 따라 모듈을 추가하거나 제거해도 소프트웨어 전체 구조에 영향을 미치치 않게 된다.

재설계 시간의 단축

테스트 코드를 먼저 작성하기 때문에 개발자가 지금 무엇을 해야하는지 분명히 정의하고 개발을 시작하게된다. 또한 테스트 시나리오를 작성하면서 다양한 예외사항에 대해 생각해 볼 수 있다. 이는 개발 진행 중 소프트웨어의 전반적인 설계가 변경되는 일을 방지할 수 있다.

디버깅 시간의 단축

이는 유닛 테스팅을 하는 이점이기도하다. 예를 들면 사용자의 데이터가 잘못 나온다면 DB의 문제인지, 비즈니스 레이어의 문제인지 UI의 문제인지 실제 모든 레이어들을 전부 디버깅 해야하지만, TDD의 경우 자동화 된 유닛 테스팅을 전제하므로 특정 버그를 손 쉰게 찾아낼 수 있다.

테스트 문서의 대체 가능

주로 SI 프로젝트 진행 과정에서 어떤 요소들이 테스트 되었는지 테스트 정의서를 만든다. 이것은 단순 통합 테스트문서에 지나지 않는다. 하지만 TDD를 하게 될 경우 테스팅을 자동화 시킴과 동시에 보다 정확한 테스트 근거를 산출 할 수 있다.

추가 구현의 용의함

개발이 완료된 소프트웨어에 어떤 기능을 추가할 때 가장 우려되는 점은 해당 기능이 기존 코드에 어떤 영향을 미칠지 알지 못한다는 것이다. 하지만 TDD의 경우 자동화된 유닛 테스팅을 전제하므로 테스트 기간을 획기적으로 단축시킬 수 있다.

이러한 TDD의 장점에도 불구하고 모두가 이 개발 프로세스를 따르는 것은 아니다. 그 이유는 무엇일까?

TDD 개발 방식의 단점

가장 큰 단점은 바로 생산성 저하이다.

개발 속도가 느려진다고 생각하는 사람이 많기 때문에 TDD에 대해 반신반의 한다.

왜냐하면 처음부터 2개의 코드를 짜야하고 중간중간 테스트를 하면서 고쳐나가야하기 때문이다.

TDD 방식의 개발 시간은 일반적인 개발 방식에 비해 대략 10~30% 정도로 늘어난다.

SI 프로젝트에서는 소프트웨어의 품질보다는 납기일 준수가 훨씬 중요하기 때문에 TDD 방식을 잘 사용하지 않는다.

그 외 TDD 정보

TDD의 궁극적인 목표는 작동하는 깔끔한 코드를 작성하는 것

TDD의 개발 단계에는 리팩토링이 있는데, 이 리팩토링 과정을 거치면서 중복된 코드들은 제거되고, 복잡한 코드들은 깔끔하게 정리하게 된다. 또한 테스트를 처음 작성할 때에는 귀찮고 개발을 느리게 한다는 느낌을 받을 수 있지만, 장기적으로 보면 반드시 개발 비용을 아껴줄 것이다.

특별한 경우가 아니라면 테스트 코드를 먼저 작성하는 것이 좋음

테스트 코드는 성공 케이스 뿐만 아니라 실패 케이스까지 작성해야 하기 때문에 작성해야 할 테스트의 개수는 해당 함수에서 발생가능한 모든 경우들인 N이며, 이미 개발이 완료되었기에 끝났다는 심리적 요인 때문에 테스트를 작성하는 것이 꺼려지기 때문이다. (격공…..)

실패 테스트부터 작성해야 함.

즉, 순차적으로 실패하는 테스트를 먼저 작성하고, 오직 테스트가 실패할 경우에만 새로운 코드를 작성해야 한다. 그리고 중복된 코드가 있으면 제거를 하는 것이다.

정리

테스트코드 ⇒ 자신의 코드가 의도된 대로 정확히 작동하는지 검증하는 절차

테스트를 처음 작성할 때에는 귀찮고 개발을 느리게 한다는 느낌을 받을 수 있지만,

장기적으로 보면 반드시 개발 비용을 아껴줄 것이다.

테스트코드의 종류 ⇒ UI test, Integration test, Unit test

테스트코드를 작성할 때는 일곱 테스트 원칙과 F.I.R.S.T 단위테스트 원칙을 참고

테스트코드를 작성하면서 개발을 진행하는 방법론인 TDD

TDD방식을 사용하면 코드의 버그가 줄어들고 소스코드는 간결해진다

TDD 개발 주기

실패하는 테스트 코드작성

테스트 코드를 성공시키기 위한 실제 코드를 작성

리팩토링

'React' 카테고리의 다른 글

[React] 리액트에서의 아키텍쳐 (1)  (0) 2023.06.23
[React] 비지니스로직과 뷰  (0) 2023.03.24
[React] 조건부 렌더링  (0) 2023.03.20