단위 테스트(Unit Test)

 

 

➤ 단위 테스트(Unit Test) 란?

 

컴퓨터에서 단위 테스트는 소스 코드의 특정 모듈이 의도된 대로 잘 작동하는지 검증하기 위해 모든 함수와 메서드에 대한 테스트 케이스를 작성하는 절차를 말한다.

 

애플리케이션에서 Postman 같은 툴을 사용해서 테스트하면 정확하게 어느 부분에서 문제가 발생하는지 알기 어렵다.

Java에서는 메서드 같이 아주 작은 단위를 가지는 기능들을 테스트할 수 있다.

Spring도 역시 계층별로 테스트할 수 있는 테스트 기법을 지원해준다.

 

일반적으로 단위 테스트는 최대한 독립적이고 작은 단위인 것이 좋다.

 

참고) 유닛테스트 위키

 

 

 

▶️ 기능 테스트

 

  • 테스트 단위가 가장 큰 테스트
  • 애플리케이션을 사용하는 사용자 입장에서 애플리케이션이 제공하는 기능이 올바르게 동작하는지 테스트
  • 주로 테스트하는 주체는 개발자가 될수도 있지만 일반적으로 테스트 전문 부서(QA) 또는 외부 QA 업체가 되기도 한다.
  • API 툴이나 데이터베이스까지 연관되어 있어서 개발한 애플리케이션과 연관된 대상이 많기 때문에 단위 테스트라고 볼 수 없다.

 

 

▶️ 통합 테스트

 

  • 클라이언트 측 툴 없이 개발자가 작성한 테스트 코드를 실행시켜 이루어지는 경우가 많다.
  • 주로 개발자가 테스트의 주체가 된다.
  • 여러 계층이 연관되어 있고 DB까지 연결되어 있어서 독립적인 테스트가 가능하다고 볼 수 없으므로 단위 테스트라고 볼 수 없다.

 

 

▶️ 슬라이스 테스트

 

  • 애플리케이션을 특정 계층으로 쪼개어서 하는 테스트를 의미한다.
  • API 계층, 서비스 계층, 데이터 액세스 계층이 각각 슬라이스 테스트의 대상이 될 수 있다.
  • HTTP 요청이 필요하고, 외부 서비스가 연동되거나 데이터 액세스 계층은 DB와 연동되어 있기 때문에 단위 테스트라고 보기 어렵다.
  • Mock(가짜) 객체를 사용하여 계층별로 끊어서 테스트 할 수 있기 때문에 어느 정도 테스트 범위를 좁힐 수 있다.
  • 애플리케이션의 일부만 테스트하기 때문에 부분 통합 테스트라고 부르기도 한다.

 

 

👉🏻 DB를 사용하면 왜 단위 테스트라고 보기 힘든것일까?

통합 테스트나 슬라이스 테스트에서 DB와 연동된다고 해서 무조건 단위 테스트라고 부르기 어려운 것이 아니다. 데이터베이스의 상태가 테스트 전후로 동일하게 유지될 수 있다면 데이터베이스와 연동된다해도 단위 테스트에 포함될수는 있다.

 

 

 

▶️ 단위 테스트

 

  • 핵심 로직(비즈니스 로직)에서 사용하는 클래스들이 독립적으로 테스트하기 가장 좋은 대상이기 때문에 단위 테스트라고 부른다.
  • 의도한 대로 잘 동작하는지 확인하기 위해서는 메서드를 테스트하는 것이 좋다.
  • 단위 테스트 코드는 대부분 메서드 단위로 작성된다.

 

 

 

➤ 단위 테스트를 해야하는 이유?

 

  • 구현한 코드가 의도한대로 동작하는지 결과를 빠르게 확인할 수 있다.
  • 문제점을 쉽게 찾을 수 있다.
  • 언제라도 단위 테스트를 믿고 리팩토링을 할 수 있다.
  • 버그 리포트를 전달 받을 경우 버그가 발생한 기능의 테스트 케이스를 돌려보면서 문제가 발생한 원인을 단계적으로 찾아가기가 용이하다.

 

 

 

※ 테스트 케이스(Test Case) 란?

 

테스트를 위한 입력 데이터, 실행 조건, 기대 결과를 표현하기 위한 명세를 의미한다.

메서드 등 하나의 단위를 테스트하기 위해 작성하는 테스트 코드이다.

 

 

 

➤ F.I.R.S.T 원칙

 

단위 테스트를 위한 테스트 케이스를 작성하기 위한 가이드 원칙으로 F.I.R.S.T 원칙을 참고할 수 있다.

 

 

▶️ Fast(빠르게)

 

일반적으로 테스트 케이스는 빨라야 한다.

자주 돌려야 문제를 빨리 찾는데 너무 느려서 돌리기 힘들면 테스트 케이스를 작성하는 의미가 퇴색될 것이다.

 

 

▶️ Independent(독립적으로)

 

각각의 테스트 케이스는 독립적으로 동작해야 한다.

어떤 테스트 케이스를 먼저 실행해도 실행되는 순서와 상관없이 정상적인 실행이 보장되어야 한다.

 

 

▶️ Repeatable(반복 가능하도록)

 

어떤 환경에서도 반복해서 실행이 가능해야 한다.

외부 리소스나 외부 서비스와 연동하는 경우 동일한 테스트 결과를 보장하지 못하기 때문에 단위 테스트 시에는 외부와의 연동을 끊어주는 것이 바람직하다.

 

 

▶️ Self-validating(셀프 검증이 되도록)

 

성공 또는 실패라는 자체 검증 결과를 보여주어야 한다.

테스트 케이스 스스로가 결과가 옳은지 그른지 판단할 수 있어야 한다.

 

 

▶️ Timely(시기 적절하게)

 

테스트 하려는 기능 구현을 하기 직전에 작성해야 한다.

TDD(테스트 주도 개발) 방식에서는 기능 구현 전에 실패하는 테스트 케이스를 먼저 작성한다.

 

 

 

 

➤ JUnit 없이 비즈니스 로직에 단위 테스트 적용해보기

 

 

JUnit : Java 기반의 소프트웨어를 테스트하기 위한 표준 테스트 프레임워크

 

단위 테스트를 제일 쉽고 빠르게 적용할 수 있는 부분은 헬퍼(helper) 클래스 또는 유틸리티(utility) 클래스 이다.

 

 

 

▶️ Given-When-Then 표현 스타일

 

given-when-then 은 BDD(Behavior Driven Development)에서 사용하는 용어이다.

 

Given

  • 테스트를 위한 준비 과정을 명시한다.
  • 테스트에 필요한 전제 조건들이 포함된다.
  • 테스트 대상에 전달되는 입력 값(테스트 데이터)도 Given에 포함된다.

 

When

  • 테스트 할 동작(대상)을 지정한다.
  • 단위 테스트에선 일반적으로 메서드 호출 통해 테스트를 진행한다.

 

Then

  • 테스트의 결과를 검증하는 영역이다.
  • 예상하는 값과 테스트 대상 메서드의 동작 수행 결과 값을 비교해서 기대한대로 동작을 수행하는지 검증(Assertion)하는 코드가 포함된다.

 

 

✅ Assertion(어써션) 이란?

 

직역하면 단언, 단정 이라는 의미이다.

테스트에서는 어써션이라는 용어는 테스트 결과를 검증할 때 주로 사용한다.

예상하는 결과 값이 참이길 바라는 것이다.

단언문, 단정문이라고 표현하기도 한다.

조건문 등으로 직접 검증할 수 있다.

 

 

 

 

JUnit

 

 

➤ JUnit 이란?

 

Java 언어로 만들어진 애플리케이션을 테스트 하기 위한 Open source Test Framework로

Java의 표준 테스트 프레임워크라고 볼 수 있다.

TestNG라는 경쟁사도 있다.

Spring Boot의 디폴트 테스트 프레임워크는 JUnit이다.

 

 

추가학습) TestNG 란?

  • Cédric Beust가 2004년에 만든 자바 기반 testing framework 이다.
  • JUnit 과 NUnit 에서 영감을 받았다.
  • JUnit 과 오랫동안 경쟁한 테스팅 프레임워크이다.
  • NG는 Next Generation(차세대)라는 의미이다.
  • JUnit 과 다른 TestNG 만의 장점
    • group : Groups 주석으로 사용, 한번 컴파일 한 후엔 같은 그룹끼리 테스트가 가능하다.
    • multithread testing을 지원한다.

 

 

 

➤ JUnit 기본 작성법

 

Spring Boot Initializr 에서 Gradle 기반의 Spring Boot 프로젝트를 생성하고 오픈하면 기본적으로 test 디렉토리가 만들어진다.

그리고 기본적으로 스타터가 포함되고 JUnit도 포함되어 있다.

 

 

▶️ JUnit을 사용한 테스트 케이스의 기본 구조

 

애플리케이션에서 테스트 하고자 하는 대상(Target)이 존재하고

테스트 메서드 위에 @Test 애너테이션을 추가해준다.

 

 

 

▶️ Assertion 메서드 사용하기

 

✔️ assertEquals()

 

org.junit.jupiter.api.Assertions.assertEquals 를 import 해서 assertEquals 메서드를 사용할 수 있다.

 

 

 

✔️ assertNotNull()

 

  • Null 여부 테스트를 진행한다.
  • 첫 번째 파라미터 : 테스트 대상 객체
  • 두 번째 파라미터 : 테스트에 실패했을 때 표시할 메시지(생략 가능)

 

 

✔️ assertThrows()

 

  • 예외(Exception) 테스트
  • 원하는 예외를 던졌을 경우 테스트 passed
  • 결과 예외보다 상위 타입의 예외를 기대해도 동일하게 passed
  • 첫 번째 파라미터 : 발생이 기대되는 예외 클래스 (Exception.class)
  • 두 번째 파라미터 : 람다 표현식으로 테스트 대상 메서드를 호출 (Executable 타입)

 

 

※ Executable 함수형 인터페이스

assertThrows() 의 두 번째 파라미터인 람다 표현식은 JUnit에서 지원하는 Executable 함수형 인터페이스이다.

메서드 하나만 정의되어 있고 리턴값이 없다.

void execute() throws Throwable;

잠재적으로 Throwable 을 던질 수 있는 모든 일반 코드 블럭을 구현하는데 사용된다.

 

참고) Executable 공식문서

 

 

✔️ assertDoesNotThrow()

 

어떤 예외도 던지지 않는지 테스트 한다.

첫 번째 파라미터 : 람다 표현식으로 테스트 대상 메서드를 호출 (Executable 타입)

 

 

 

참고) JUnit5 Assertions 공식문서

 

 

 

▶️ 테스트 케이스 실행 전, 후처리

 

✔️ @BeforeEach

 

이 애너테이션을 추가한 메서드는 테스트 케이스가 각각 실행될 때 마다 케이스 실행 직전에 먼저 실행되어 초기화 작업 등을 진행할 수 있다.

 

 

✔️ @BeforeAll

 

클래스 레벨에서 테스트 케이스를 한꺼번에 실행 시키면 테스트 케이스가 실행되기 전에 한번만 초기화 작업을 한다.

⭐️ @BeforeAll() 에너테이션을 추가한 메서드는 정적 메서드(static method)여야 한다

 

 

 

▶️ 테스트 케이스 실행 후, 후처리

 

@AfterEach, @AfterAll 

@BeforEach, @BeforeAll() 과 동일하게 동작하는데 테스트 케이스 실행이 끝난 시점에 후처리 작업을 할 때 사용한다.

 

 

▶️ Assumption

 

Junit 5에 추가된 기능이다.

Assumption은 말 그대로 ‘가정’할 때 사용한다.

특정 환경에서만 테스트 케이스가 실행되도록 할 수있다.

 

org.junit.jupiter.api.Assumptions 안에서 메서드를 Import 해서 사용한다.

 

 

✔️ assumeTrue()

 

  • 파라미터로 입력된 값이 true 일 때 나머지 아래 로직을 실행한다.
  • 특정 OS 환경 등의 특정 조건에서 선택적인 테스트가 필요할 때 유용하다.

 

※ 실행환경의 OS name 검사하기

System.getProperty(“os.name”).startsWith(“Mac”)

assumeTrue(System.getProperty("os.name").startsWith("Mac"));

getProperty의 파라미터로 os.name을 String 타입으로 전달하면 실행환경의 OS 이름을 리턴한다.

startsWith는 해당 String이 파라미터로 전달하는 String으로 시작하는지 검사한다.

 

 

 

 

 

감사합니다.

오개념에 대한 지적은 늘 환영입니다. 😉