JUnit

김우진·2021년 11월 2일
0

JUnit

목록 보기
1/2

이 글은 "자바 웹 프로그래밍 Next Step"의 책을 읽고 JUnit 부분을 구글링을 추가하여 필요성과 사용법을 적은 글입니다.

JUnit

The JUnit Platform serves as a foundation for launching testing frameworks on the JVM. (JUnit.org 출처)

JUnit은 자바 프로그래밍 언어용 유닛 테스트 프레임 워크이다. JUnit은 JVM 환경에서 테스트 프레임워크를 시작하기 위한 환경을 제공해주는 역할을 한다.

Before JUnit(feat. Main Method)

JUnit 없이도 Java에선 main() 메소드를 활용해 콘솔을 통해 구현한 기능을 test해 볼 수 있다.

아래와 같이 간단한 사칙연산을 계산하는 구현 코드가 있다고 하자.

public class Calculator {
    int add(int i, int j) {
        return i + j;
    }

    int subtract(int i, int j) {
        return i - j;
    }

    int multiply(int i, int j) {
        return i * j;
    }

    int divide(int i, int j) {
        return i / j;
    }

    public static void main(String[] args) {
        Calculator cal = new Calculator();
        System.out.println(cal.add(3,4));
        System.out.println(cal.subtract(5,4));
        System.out.println(cal.multiply(2,6));
        System.out.println(cal.divide(8,4));
    }
}

위 Calculator 클래스는 실제로 서비스를 담당하는 프로덕션 코드(production code)와 이 프로덕션 코드가 정상적으로 동작하는 지 확인하기 위한 main() 테스트 코드로 나뉜다. 하지만 이렇게 구성하는 경우, 테스트 코드가 테스트 단계가 아닌 서비스하는 시점에 같이 배포된다는 문제점이 있다. 따라서, 프로덕션 코드(Calculator 클래스)와 테스트 코드(Calculator main() method)를 분리 해주는 것이 좋다.

Class Calculator 코드
public class Calculator {
    int add(int i, int j) {
        return i + j;
    }

    int subtract(int i, int j) {
        return i - j;
    }

    int multiply(int i, int j) {
        return i * j;
    }

    int divide(int i, int j) {
        return i / j;
    }
}
Class CalculatorTest 코드
public class CalculatorTest {
    public static void main(String[] args) {
        Calculator cal = new Calculator();
        System.out.println(cal.add(3,4));
        System.out.println(cal.subtract(5,4));
        System.out.println(cal.multiply(2,6));
        System.out.println(cal.divide(8,4));
    }
}

이로써 테스트를 담당하는 별도의 클래스가 생성되었지만 이 역시 좋은 방식은 아니다. main() 메소드 하나에서 프로덕션 코드의 여러 메소드를 동시에 테스트하고 있기 때문이다. 이는 프로덕션 코드의 복잡도가 증가하면 증가할수록, main() 메소드의 복잡도도 증가하고, 최종적으로 main() 메소드를 유지하는 데 부담이 된다. 이 같은 문제를 해결하기 위해 테스트 코드를 각 메소드별로 분리해주는 것이 좋다.

public class CalculatorTest {
    public static void main(String[] args) {
        Calculator cal = new Calculator();
        add(cal);
        subtract(cal);
        multiply(cal);
        divide(cal);
    }
    
    private static void divide(Calculator cal) {
        System.out.println(cal.divide(8,4));
    }

    private static void multiply(Calculator cal) {
        System.out.println(cal.multiply(9,3));
    }
    
    private static void subtract(Calculator cal) {
        System.out.println(cal.subtract(5,4));
    }
    
    private static void add(Calculator cal) {
        System.out.println(cal.add(3,4));
    }
}

하지만 이 또한 최종적인 해결책은 될 수 없다. 그 이유는 우리가 프로그래밍하는 과정을 보면 한 번에 하나의 메소드 구현에 집중한다. 클래스가 가지고 있는 모든 메소드에 관심이 있는 것이 아니라 현재 내가 개발하고 있는 기능 구현에만 집중하고 싶다. 하지만 위와 같이 테스트 코드를 구현하면 Calculator 클래스 안의 모든 메소드를 한 번에 테스트 할 수 밖에 없다. 그렇다고 테스트하는 메소드 외 다른 메소드를 주석처리하는 것 역시 불합리하다.

이러한 main() 메소드를 활용한 test의 문제점은 테스트 결과를 매번 콘솔에 출력되는 값을 통해 수동으로 확인해야 한다는 것이다. 로직의 복잡도가 낮은 경우는 결과 값 예측이 가능하지만 로직의 복잡도가 높은 경우 한 로직을 머릿속으로 계산해 결과 값이 정상적으로 출력되는지 일일이 확인해야 하는 번거로움이 있다. 실제로 지금은 단순 사칙 연산이지만 안의 로직이 복잡한 경우 출력되는 결과값 만으론 로직이 재대로 실행되었는 지 확인하긴 어려울 것이다. 이러한 문제점을 해결하기 위해 등장한 것이 JUnit이다.

JUnit의 사용

@Test

@Test 어노테이션을 통해서 각 기능에 대한 test를 진행할 수 있다. 결과 확인 역시 console 창을 통해서 하는 것이 아닌 JUnit에서 제공하는 Assert 클래스를 통해서 예상 결과와 동작 결과가 같은지, 결과 값이 true/false 인지, 결과 값이 null 유무인지 등을 확인할 수 있는 메소드를 사용할 수 있다.

@Test
public void add() {
    Calculator cal = new Calculator();
    assertEquals(9, cal.add(6, 3));
}

@Before 과 @After

import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class CalculatorTest {
    @Test
    public void add() {
        Calculator cal = new Calculator();
        assertEquals(9, cal.add(6, 3));
    }

    @Test
    public void subtract() {
        Calculator cal = new Calculator();
        assertEquals(3, cal.subtract(6, 3));
    }
}

위와 같이 Test코드를 작성하면 Calculator 인스턴스를 생성하는 부분에서 코드의 중복이 발생한다. 이는 Test Class의 field에 Calculator 인스턴스를 생성해주는 방법으로 제거할 수 있다.

하지만, JUnit에선 이렇게 중복을 제거하지 않고 @Before 어노테이션을 사용한다 이유가 무엇일까?
만약 @Before 방식이 아닌 field 방식으로 Calculator 인스턴스를 사용한 경우 하나의 테스트를 통해 인스턴스가 변경된 경우 다음 테스트를 진행할 때 영향을 미칠 수 있다. @Before를 사용하여 아래와 같이 코드를 작성하면 테스트 메소드 간에 영향을 미치지 않으면서 독립적으로 테스트 메소드를 실행할 수 있다. @Before 어노테이션으로 초기화 작업을 진행 하듯이 @After 어노테이션을 통해 후처리 작업을 할 수 있다.

import static org.junit.Assert.assertEquals;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class CalculatorTest {
    private Calculator cal;

    @Before
    public void setUp() {
        cal = new Calculator();
        System.out.println("before");
    }
    
    @Test
    public void add() {
        assertEquals(9, cal.add(6, 3));
        System.out.println("add");
    }

    @Test
    public void subtract() {
        assertEquals(3, cal.subtract(6, 3));
        System.out.println("subtract");
    }

    @After
    public void teardown() {
        System.out.println("teardown");
    }
}
실행결과
before
subtract
teardown
before
add
teardown

출처

  1. JUnit.org 공식 문서의 User Guide: JUnit 공식문서의 UserGuide
  2. 박재성, 『자바 웹 프로그래밍 Next Step』, 로드북(2016), p43-74

0개의 댓글