개발/프론트엔드 (JS & React)

[Refactoring JavaScript] 테스트 2 - TDD, Characterization Tests

jungwon_ 2022. 9. 16. 16:58

앞선 글에서 리팩토링을 이야기할 때 테스트를 빼놓을 수 없다는 내용을 짚고 넘어갔다.

 

[Refactoring JavaScript] 노트 1 - 리팩토링에 대해

리팩토링이라는 단어는 잘못 사용되기 쉽다. 그래서 간단하게 짚고 넘어가고자한다. 리팩토링은 interface 혹은 behavior를 유지하되 코드 구현이 달라지는 것을 의미한다. interface나 behavior이 바뀐다

jwdevv.tistory.com

 

[Refactoring JavaScript] 테스트 코드 1 - 왜 테스트 코드를 작성해야 하나, 테스트 방법

4년차 개발자인 나의 주요 관심사는 "어떻게 더 좋은 코드를 만들 것인가"이다. 그동안 여러 책과 구글의 도움을 받아 리팩터링을 해왔지만, 복습 및 내가 뭘 모르고 있는지 배우기 위해 를 읽기

jwdevv.tistory.com

그러면서 퀄리티 코드를 만들기 위한 프로세스로 TDD (Test-Driven Development)를 소개했다.

 

또한 다양한 테스트 방법을 이야기하며 언급만하고 지나간 테스트 방법이 세가지 있는데, 오늘은 그 중 하나인 Characterization test에 대해서 정리해보고자 한다.


우선 프로젝트에서 어떻게 테스트를 진행할지 절차를 만든다고 가정하자.

 

만약 새로운 프로젝트라면 test coverage를 높이기 위해 TDD 방법을 사용하기로 약속할 수 있다.

 

그러나 만약 이미 legacy 코드가 많은 경우 TDD를 진행하기는 어렵다. 왜냐하면 TDD 는 테스트 코드를 먼저 작성하고 난 다음에 구현하는 방식이기 때문이다. 이런 경우 많은 양의 legacy 코드를 다시 작성 해야할 것이다. 이는 규모가 큰 프로젝트에서는 부담스러운 방법이다.

 

이런 경우 legacy 코드에는 characterization 테스트를 적용할 수 있다.

 

Characterization Test란 쉽게 말해서 테스트되지 않는 코드를 위한 테스트를 의미한다. 테스트 코드가 없으니까 테스트를 추가한다는 뜻이다. TDD와 다르게 이 경우에는 코드가 먼저 구현되고 테스트 코드를 작성하는 경우다.


TDD (테스트 주도 개발 방법론)

앞으로 작성되는 모든 새로운 코드에는 TDD 방법론을 적용하기로 했다고 가정하자.

 

우선 어떤 기능을 추가할 것인지 생각해보자.

 

예를 들어 Todo List를 만들기 위해 input element에 텍스트를 입력하고 Add 버튼을 입력하면 Todo 리스트에 추가가 되도록 하는 기능을 만든다고 가정하자.

 

이 경우에는 테스트코드를 먼저 작성한다.

 

대략 input에 "Grocery Shopping"을 입력하고 Add 버튼을 누른 다음 DOM에서 Todo 리스트에 "Grocery Shopping"이 추가되었는지를 assert 메소드를 작성한다.

 

그런 다음 테스트를 통과할 수 있도록 Add 버튼 기능을 구현한다.

 

구현이 끝나고 나서는 더 나은 코드로 리팩토링 할 것인지 아니면 다음 케이스로 넘어갈 것인지를 결정한다.

 

이렇게 TDD 방법론을 사용하면 test coverage를 높일 수 있다는 장점이 있다.

 

 

즉 TDD 방법론은 새로운 프로젝트를 시작하거나 앞으로 작성되는 모든 새로운 코드에 대해 적용하기 좋은 방법론이다.

 

 

+ 추가 내용

책 Refactoring JavaScript의 Chapter 4에서 다루는 예제는 보통 pure function을 기준으로 다루는 듯하다. 하지만 위에 내가 적은 예시는 react testing library 예제를 생각하며 썼으므로 UI 테스팅에 가깝다.

 

공부를 더 하며 비즈니스 로직과 UI 테스팅을 구분해야겠다는 생각이 들었다. 그래서 프론트엔드의 관점에서의 TDD를 조금 더 조사해보았다.

 

발견한 포스트[2]에 따르자면 TDD는 좋은 방법론이지만 일부에서는 UI에서는 사용하기 어렵다는 의견이 있었다.

 

TDD는 1) 프로젝트 요구사항이 명확하고 2) intput과 output이 명확할 때 사용하기 좋은 방법론인데, 이는 pure function이나 API endpoint에는 적용하기 좋으나 UI는 두 조건을 충족하지 못한다는 이유에서다.

 

그러나 포스트 작성자는 1) UX 디자이너 혹은 프로젝트 매니저에게 받는 mockup 자체가 명확한 프로젝트 요구사항이며 2) 리액트에서 컴포넌트는 props와 state의 function으로 UI를 리턴하므로 명확한 input과 output이 있다고 볼 수 있다고 주장한다.

 

또한 그는 mockup만 가지고는 구현을 끝내기 전까지 어떤 element를 사용해서 구현할 지 세부 구현사항을 알 수 없기 때문에 TDD를 적용하기 힘들다는 일부의 주장에 대해서도 반대한다.

 

왜냐하면 사람들이 가장 많이 사용하는 자바스크립트 UI 라이브러리인 리액트를 기준으로 설명했을때 리액트 테스팅 라이브러리의 철학인 'user behavior를 중심으로 테스트한다'는 것과 관련이 없기 때문이다.

 

즉 UI 테스트에서 중요한 점은 UI가 어떻게 생겼고 작동하는지이지, 구현의 세부적인 디테일이 아니기 때문에 TDD는 여전히 프론트엔드에서 좋은 방법론이라는 것이다.


Characterization 테스트

 

이번에는 legacy 코드에 적용할 테스트 방법을 알아보자.

 

우선 이 프로젝트는 규모가 큰 프로젝트고 test coverage가 0이며 이미 사용자들이 사용하는 프로젝트라 가정하자.

 

이런 경우 무턱대고 코드를 변경하는 것은 위험하다.

 

코드 변경이 어떤 side effect을 일으킬지도 모르고 최악의 경우 기존에 굴러가던 기능마저 안될 수도 있기 때문이다.

 

비효율적이고 굴러가는 코드가 효율적이지만 굴러가지 않는 코드보다 낫다.

 

 

이런 경우 우리는 이미 구현된 함수를 바탕으로 테스트를 작성할 수 있다.

 

예를 들어 물건을 장바구니에 담는 기능이 있다고 가정하자. 그와 관련된 function addToCart(item)가 있다.

 

function addToCart(item)을 관찰해보니 어떤 아이템을 넘겨줬을 때 카트 안에 담기면 되는 것 같다.

 

그러면 그에 대한 테스트코드를 작성하고 테스트코드가 제대로 동작하는지 확인한다.

 

그리고 나서 addToCart(item)을 리팩토링 할 수 있다.

 

이렇게 테스트가 없는 코드에 coverage를 추가하기 위한 테스트를 Characterization 테스트라 하며 legacy 코드나 코드 구현을 먼저 한 경우에 적용할 수 있다.


Reference

[1] Refactoring JavaScript [Book] by Evan Burchard

[2] Test-Driven Development for Building User Interfaces [Blog Post] by Tyler Hawkins: https://dev.to/thawkin3/test-driven-development-for-building-user-interfaces-1ka8