1. 테스트 코드의 정의와 사용 이유

테스트 코드(Test Code)란 소프트웨어 개발 후 기능과 동작을 테스트하는 데 사용되는 코드이다. 개발자가 예상한대로 프로그램이 실행하는 지 확인하는 역할을 한다. 어떤 기능을 테스트할 것인지에 대해 각각 테스트 케이스를 분류하고, 다양한 라이브러리와 프레임워크를 이요해 작설항 수 있다. 테스트 코드를 작성하면 그만큼 자원이 더 낭비된다는 단점이 있지만, 이를 뛰어넘는 장점이 있기 때문에 사용이 권장된다. 

  1. 장애 방지: 테스트 코드를 통해 출시 이전 소프트웨어의 결함을 찾아내고 미리 수정을 할 수 있다. 이를 통해 회사는 고객 신뢰도를 지키고 낭비되는 장애 복구 비용을 절약할 수 있다. 
  2. 협업 증진: 중간 작업 결과를 쉬벡 공유할 수 있는 수단이 된다. 불필요한 문서 작업 대신 테스트 코드 결과를 보여주면 되니 협업 과정이 훨씬 빨라진다. 
  3. 코드 품질 향상: 개발 과정에서 반복적인 테스트와 버그 수정을 통해 전반적인 소프트웨어 품질을 향상시킬 수 있다. 또한 의존성이 높은 코드에 대한 테스트 코드는 짜기 어렵다는 특성 때문에, 테스트 코드를 작성하는 과정에서 의존성이 낮은 좋은 코드를 개발할 수 있게 된다.
  4. 리팩토링 지원과 코드 자신감: 테스트 코드가 존재하면 코드 수정, 특히 구조 변경과 같은 대규모 수정에 있어 좀 더 안심하고 진행할 수 있다. 코드 수정 시 기능이 전과 같이 돌아가지 않을 걱정이 생기는 것은 개발자로서 보편적으로 겪는 고민인데, 테스트 코드를 통해 대규모 리팩토링 진행하면서도 정상적으로 동작하는 지 확인 가능하기 때문이다.

 

2. 테스트 코드의 종류와 예시

테스트 코드는 무엇을, 왜 테스트 하느냐에 따라 종단 테스트(E2E), 통합 테스트(Integration), 단위 테스트(Unit Test), 정적 테스트(Static) 등 다양한 종류가 있다. 아래는 Kent.dot.C가 테스트 코드 분류를 설명한 테스트 코드 트로피이다. (KentDotC가 리액트의 저명사인만큼 프론트엔드에 좀 더 치우쳐져 있는 점은 고려해야 한다.) 그 외에 Snapshot 테스트 등 다양한 테스트가 다양한 이름으로 불리우고 있지만, 크게 아래 4가지 분류 안에 속하는 것 같다. 특히 이 중 Unit Testing과 Integration Testing이 개발 시 가장 많이 사용되는 듯 하다. 그럼 한 번 각자 간단히 알아보자.

https://kentcdodds.com/blog/the-testing-trophy-and-testing-classifications

 

1. 정적 테스트(Static Testing)

소프트웨어를 실행하지 않은 상태에서 오류를 찾아내는 테스트 방법이다. 정적 테스트에는 코드 리뷰, 워크 스루, 인스펙션, 정적 분석 도구 사용 등 다양한 방식이 포함된다. 코드 리뷰(Code Review)는 개발자가 작성한 코드를 동료 개발자들이 검토하는 과정이다. GitHub 등에서 PR 날리고 merge 전에 하는 그 과정 말이다. 워크 스루(Walkthrough)는 좀더 본격적인 팀 간의 코드 검토 과정으로, 작성된 코드나 문서를 작성자가 설명하고 팀이 이를 검토하는 비공식적 회의이다. 자신의 코드를 PPT로 소개하고... 단체 피드백을 받는 그런 과정을 말하는 듯 하다. 인스펙션(Inspection)은 코드나 문서의 결함을 체계적으로 찾기 위해 사전에 준비된 체크리스트와 절차를 따르는 공식적 검토 과정이다. 마지막으로 정적 분석 도구(Static Analysis Tools)는 평소 사용하던 ESLint가 그 대표적인 예이다. 코드 규칙을 검사하고, 잠재적 오류를 탐지해 개발 초기 단계에서 버그를 발견할 수 있다. 

 

2. 단위 테스트(Unit Testing)

더 이상 나눌 수 없는 가장 작은 단위의, 다른 기능과 섞이지 않은 독립적인 기능을 테스트한다. 라이브러리는 언어에 따라 다양하게 존재하며 대표적으로는 JavaScript Jest, Java JUnit 있다. 참고로 단위 테스트 코드를 먼저 작성한 이에 따라 기능을 작성하는 TDD(Test-Driven Development) 통해 개발하면 불필요한 기능 개발에 들어가는 시간을 줄일 있다.

 

아래는 단위 테스트 코드의 개념을 이해하기 위한 간단한 예시이다. 각각의 독립적인 기능1(sum), 기능2(subtract)을 Jest 라이브러리를 이용해 각각 테스트하는 코드이다. 

export function sum(a, b) {
	return a + b;
}
export function subtract(a, b) {
	return a - b;
}
import { sum, subtract } from "./utils"
describe('산수 계산 함수 테스트', () => {
    test('1+2의 결과는 3이다.', () => {
        expect(sum(1, 2)).toBe(3);
    })
        
    test('1-2의 결과는 -1이다.', () => {
        expect(subtract(1, 2)).toBe(-1);
    })
})

 

3. 통합 테스트(Integration Testing)

각 독립적인 기능들의 상호 작용을 테스트한다. 기능들이 각각 잘 동작한다면, 이것들이 모여서 함께 동작할 때도 잘 돌아가는 지 테스트한다. 대표적인 통합 테스트 코드 라이브러리로는 JavaScript Testing Library과 Storybook, Java testcontainers 등이 있각 통합 단위에 따라(화면 단위인지, 기능 단위인지), 대상에 따라(UI만 테스트, 기능만 테스트, 둘 다 테스트) 다양한 테스트가 있는 것 같다. 예를 들어 프론트엔드에서의 화면 단위 UI 테스트인 Snapshot 테스트도 여기에 해당된다(고 이해했다.) 

 

아래는 각각의 기능이 모여 복리 계산 기능을 잘 이루는지를 테스트한다. 

import { render, screen, fireEvent } from '@testing-library/react';
import CompoundInterestCalculator from './App';

describe('복리 계산 앱 실행', () => {
	test('원금 100만원을 연이율 10%의 복리로 3년간 은행에 예금한다면, 3년 후 원리 합계는 1,331,000원이다.', () => {
		render(<CompoundInterestCalculator />);

		const 예치금 = screen.getByRole('input', { name: /예치금/ });
        	const 연이율 = screen.getByRole('input', { name: /연이율/ });
		const 기간 = screen.getByRole('input', { name: /기간/ });
		const 계산버튼 = screen.getByRole('button');

		fireEvent.change(예치금, { target: { value: '1000000' } });
		fireEvent.change(연이율, { target: { value: '10' } });
		fireEvent.change(기간, { target: { value: '3' } });
		fireEvent.keyDown(계산버튼, { key: 'Enter' });
        	await waitFor(() => {
            		expect(screen.queryByText('결과')).toBe('1,331,000');
        	});
    })
})

 

 

4. 종단 테스트(End-To-End Testing, E2E Testing)

사용자의 입장에서 소프트웨어가 처음부터 끝까지 정상적으로 흐름이 이어지는 지 확인하는 테스트이다. 보통 E2E 테스트라 불린다. 이러한 E2E테스트는 UI뿐만 아니라 기능까지 테스트하기 떄문에 시간과 비용이 많이 든다는 단점이 있다. GUI를 통해 화면에 컴포넌트가 실제로 렌더링 되는 모든 과정까지 테스트하고, 서버에 실제로 API 요청을 보내기 때문에 다른 테스트와는 차원이 다른 시간과 비용이 소모되며, 테스트 실패 가능성도 높다. 따라서 E2E 테스트는 중요한 기능 위주로만 적용해야 한다. 보통 실제 서비스에서는 서비스 장애를 잡을 목적으로 일정 시간(ex. 4시간) 이러한 E2E 테스트가 동작하도록 테스트 자동화를 해둔다고 한다. 사용 라이브러리로는 Cypress, Playwrite 등이 있다.

 

3. 어떤 테스트 코드를 언제 써야할까?

https://martinfowler.com/bliki/TestPyramid.html

 

다양한 테스트 코드의 종류와 그 쓰임에 대해 알아보았다. 종류마다 쓰임이 다른 것처럼 그 비용도 다 다르다. 그리고 실세계에서는 우리는 늘 그 비용을 고려할 수 밖에 없다. 그 유명한 "테스트 피라미드" 개념에 따르면 테스트 비용은 앞서 설명된 Static, Unit 단위에서 가장 저렴하고 빠르게 시행할 수 있으며, 로직이 섞이는 Integration 단위부터 비용과 그 무게가 증가한다. Server와 통신까지 필요해지는 E2E 테스트는 그 값이 비싸지고 가장 시행하기 무겁다. 

 

이러한 테스트 특징을 고려했을 때, 쓰임과 비용을 고려해 적절히 사용하는 것이 좋다. Martin Fowler 무겁고 값이 비싼 E2E 테스트만 시행하고 버그를 통해 고쳐나가기보다는, 작고 가벼운 Unit Testing와 Integration Testing 을 자동화하여 더 자주 실행하는 것을 추천한다. "So the pyramid argues that you should do much more automated testing through unit tests than you should through traditional GUI based testing." 추가적으로, Kent C. Dodds는 그 중 가장 가성비 좋은 Integration 단위 테스트를 가장 많이 사용할 것을 권장한다. "Write tests. Not too many. Mostly integration.”

 

두 거장에게서 반복적으로 언급된 통합 테스트(Integration Testing)는 앞서도 간략히 설명했지만, 통합 단위에 따라(화면 단위인지, 기능 단위인지), 대상에 따라(UI만 테스트, 기능만 테스트, 둘 다 테스트) 등 다양한 기준에 따라 다양한 테스트가 있는 것으로 보인다. 특히 다양한 컴포넌트들 화면 단위로 테스트를 하는 UI 통합 테스트인 Snapshot test와, API개발이 미뤄지는 현업 상황에서 기능 테스트를 실용적으로 도와줄 msw와 jest를 사용한 통합 기능 테스트, 이 2가지 통합 테스트가 프론트 개발에서는 가장 효율성 좋은 Integration Testing이지 않을까 생각한다. 여기서 한 발짝 더 나아간다면, 정말 '이 기능 오류나면 서비스가 망한다' 싶은 중요한 기능들만 추려 Cypress를 통해 E2E테스트까지 적절한 텀으로 자동화해 시행하면 좋지 않을까 싶다.

 

4. 통합 테스트(Integration Testing) 직접 적용해 보기: msw, jest, Storybook

(작성 중)

 

5. 테스트가 용이한 코드란 어떤 코드인가?

(작성 예정)

 


참조

1. 

https://kentcdodds.com/blog/the-testing-trophy-and-testing-classifications

 

The Testing Trophy and Testing Classifications

Stay up to date Stay up to date All rights reserved © Kent C. Dodds 2024

kentcdodds.com

 

2.

https://martinfowler.com/bliki/TestPyramid.html

 

bliki: Test Pyramid

Write most of your tests at a low level (unit tests) with a few broad-stack tests, eg via UI. UI tests tend to be fragile and slow.

martinfowler.com

 

3.

https://www.epicweb.dev/good-code-testable-code

 

Good Code, Testable Code

Learn what testability means, how it relates to code complexity, and why it's essential for effective testing.

www.epicweb.dev

 

4.

https://fe-developers.kakaoent.com/2022/220825-msw-integration-testing/

 

MSW를 활용하는 Front-End 통합테스트 | 카카오엔터테인먼트 FE 기술블로그

송기연(Kaki) 음악과 별을 좋아하는 개발자입니다.

fe-developers.kakaoent.com

 

5.

https://blog.banksalad.com/tech/test-in-banksalad-ios-3/

 

뱅크샐러드 iOS팀이 숨쉬듯이 테스트코드 짜는 방식 3편 - 스펙별 단위 테스트 | 뱅크샐러드

안녕하세요! 뱅크샐러드에서 iOS…

blog.banksalad.com

 

6.

https://fe-developers.kakaoent.com/2023/230209-e2e/

 

E2E 테스트 도입 경험기 | 카카오엔터테인먼트 FE 기술블로그

방경민(kai) 사용자들에게 보이는 부분을 개발한다는 데서 프론트엔드 개발자의 매력을 듬뿍 느끼고 있습니다.

fe-developers.kakaoent.com

 

 

 

Last Edited: 24/10/14

 

+ Recent posts