ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 그래서 유닛테스트(Unit Test)가 뭔가요?
    소프트웨어 엔지니어링 2019. 5. 18. 14:26

    소프트웨어 업계에 종사하거나 개발을 많이 해봤다면 유닛테스트에 대해 종종 들었을 것이다. 업계에 종사하고있다면 실제로 유닛테스트를 매일 작성하고 있을수도 있다. 유닛테스트는 뭘까? 개발자들이 왜 유닛테스트를 하는걸까? 이 포스트에서는 유닛테스트(Unit Test)가 무엇인지, 또 소프트웨어 엔지니어들이 유닛테스트(Unit Test)를 왜 작성하는지에 대해 이야기 해 보도록 하겠다.


    예를 들기 위해 사용한 프로젝트의 구조는 아래와 같으며, 이 포스트는 자바8 + Gradle + JUnit4를 기준으로 작성되었다. 

    UnitTestTutorial
    ├── build.gradle
    ├── gradle
    │   └── wrapper
    │   ├── gradle-wrapper.jar
    │   └── gradle-wrapper.properties
    ├── gradlew
    ├── gradlew.bat
    ├── settings.gradle
    └── src
    ├── main
    │   ├── java
    │   │   └── MyService.java
    │   └── resources
    └── test
    ├── java
    │   └── MyServiceTest.java
    └── resources

    9 directories, 8 files

    그래서 유닛테스트(Unit Test)가 뭔가요?

    유닛테스트란 메서드를 테스트하는 또 다른 메서드이다. 하나의 유닛테스트(Unit Test)는 하나의 메서드의 특정 루틴을 검사한다. 예를 들어보자. 
    MyService.java 에는 isGreatherThanFive라는 간단한 메서드가 있다. 이 메서드는 매개변수로 들어온 Integer형의 변수 number가 5보다 크면 true를 작으면 false를 리턴한다. 
    public class MyService {

    public boolean isGreaterThanFive(Integer number){
    return number > 5;
    }
    }
    이 메서드를 프로그램을 실행하지 않고도 테스트 할 수 있을까? 물론 없다. 만약 전체 프로그램을 실행하지 않고도 이 메서드를 테스트하고 싶다면 우리는 유닛 테스트 메서드를 작성해야한다. 유닛 테스트는 우리가 작성한 코드를 테스트하기위한 또 다른 메서드이다. 하나의 유닛 테스트는 어떤 메서드의 특정 루틴을 검사한다. 무슨 뜻일까? 코드를 보는게 더 이해하기 쉬우니 실제 유닛 테스트 코드를 보도록 하자.
    import org.junit.Assert;
    import org.junit.Test;

    public class MyServiceTest {
    private MyService myService;

    @Test
    public void testMyMethod_GreaterThanFive() {
    myService = new MyService();
    boolean expectedResult = true;
    boolean actualResult = myService.isGreaterThanFive(10);
    Assert.assertEquals(expectedResult, actualResult);
    }

    @Test
    public void testMyMethod_LessThanFive() {
    myService = new MyService();
    boolean expectedResult = false;
    boolean actualResult = myService.isGreaterThanFive(1);
    Assert.assertEquals(expectedResult, actualResult);
    }
    }
    isGreaterThanFive라는 메서드가 택할 수 있는 경우는 2가지가 있다. 첫번째는 인풋이 5보다 커 true를 리턴하는 경우, 두번째는 인풋이 5보다 작거나 같아 false를 리턴하는 경우이다. 이 두 경우를 모두 검사하기 위해서 두개의 유닛테스트 메서드가 필요하다. 이렇게 작성된 유닛테스트들은 IDE내부에서 또는 Gradle/Maven의 빌드 스크립트의 일부로 실행할 수 있다.

    위처럼 유닛테스트는 개발자인 우리가 코드를 검사하기위해 직접 작성해야하는 것이다. 모든 경우을 검사할 필요는 없지만 핵심적인 경우는 유닛테스트 코드를 작성하는것이 일반적이다. 따라서 5개의 조건문이 있다면 5개의 유닛테스트를 작성해야 할 수도 있다. 어떤 경우에는 메서드의 코드를 작성하는 시간보다 유닛테스트를 작성하는 시간이 더 오래걸리기도 한다. 그렇다면 이렇게 개발자의 시간을 잡아먹는 유닛테스트를 왜 만드는걸까?

    그래서 유닛테스트(Unit Test)는 왜 만드나요?

    전체적으로 말했을 때 유닛테스트는 버그를 줄이고 코드 퀄리티를 높이기 위해 만든다. 세부적으로는 다양한 이유가 있겠지만 두가지를 이야기하도록 하겠다.
     첫번째는 프로그램이 크고, 메모리가 많이 들고, 다른 리소스(데이터베이스 등)이 필요한 경우 로컬 환경에서 쉽게 코드를 실행시켜보기 어렵기 때문이다. 이런 프로그램을 개발하는 개발자들은 유닛테스트를 만들어서 빠르게 자신의 코드가 정상적으로 작동 하는지 확인 할 수 있다.
     두번째는 디펜던시가 있는 다른 클래스들에서 버그가 나는것을 방지하기 위해서이다. 예를 들어 설명해보겠다. 
    여러분은 개발자이다. 여러분의 회사의 소프트웨어는 10만줄 정도의 코드를 가진 소프트웨어라고 하자. 어느날 여러분은 소프트웨어에서 버그가 발생되었다고 하여 버그를 고치러갔다. 



    어떤 RequestHandler라는 클래스에 버그가 있다. 더 디버깅을 해봤더니 RequestHandler클래스 안에서 사용중인 Service라는 클래스에 버그가 있는것 같다. 그래서 Service클래스의 코드를 고쳤고 버그가 고쳐진 것을 확인한 후 코드를 마스터 리파지토리로 올렸다. 여러분은 버그를 고쳤으니 기뻐해야할까? 



    여러분은 기뻐할 수 없다. 여러분은 RequestHandler에 있는 버그가 당연히 Service에서 파생된것으로 생각하고 Service를 고쳤다. 그리고 Service클래스를 고침으로써 이 클래스를 사용하는 다른 모든 클래스의 행동(Behavior)를 수정한것과 다름이 없는 셈이다. 그렇다면 이 모든 클래스가 버그 없이 정상적으로 동작하는지 어떻게 확인할 것인가? 직접 프로그램을 돌려 하나하나 수정할것인가? 애초에 이 서비스를 사용하는 모든 클래스를 어떻게 찾을것인가? 

    이런 고려 없이 프로덕션으로 코드를 릴리즈 했다고 하자.



    몇달 뒤 팀원 중 하나가 OtherHandler라는 클래스에서 버그를 발견했다. 이 팀원은 버그가 Service에서 파생된것으로 보고 Service의 버그를 고쳤다. 그리고 또 릴리즈를 했다. 그로인해 이전에 고쳤던 RequestHandler에 버그가 또 생겼다.



    그리고 몇달 뒤 여러분은 본인이 고친 버그가 왜 또 생긴건지 의아해 하고 있을 것이다. 이런 일이 정말 일어날까? 일어난다. 유닛테스트가 없는 회사에서 나도 종종 겪었던 일이다. 그렇다면 유닛테스트가 있었다면 상황이 어떻게 변했을까?


    유닛테스트가 있었다면 버그를 고치고 빌드(Build) 및 유닛 테스트 실행을 하는 순간 유닛 테스트가 Fail하기 시작할 것이다. 그러면 여러분은 보다 쉽게 이 Service에 의존(Dependency)하는 다른 클래스들을 확인할 수 있을 것이다. 그리고 Service에 정말 버그가 있는건지, 아니면 내 가정이 잘못되었는지 확인할 수 있을 것이다. 따라서 유닛테스트는 현재의 내 코드를 검사하는것 뿐만 아니라 미래의 다른 누군가(또는 나)의 코드를 검사하는 역할도 한다.
    이렇게 유닛테스트가 소프트웨어 개발에서 중요한 역할을 하면서, 요즘은 테스트 주도 개발방법론(Test Driven Development)처럼 테스팅 방법과 테스팅 코드를 미리 디자인/구현 후 실제 서비스를 코딩하기도 한다. TDD기법은 다른 포스트에서 더 자세히 설명하도록 하겠다.

    유닛테스트를 잘 작성한다고 여러분의 소프트웨어가 버그 없이 잘 돌아간다는 뜻은 아니다. 하나하나의 메서드가 잘 동작한다고 하더라도, 그 메서드가 이루는 로직(Business Logic)에 따라 버그가 있을수도 없을수도 있다. 이런 경우는 Integration Test/System Test/Acceptance Test등의 다른 테스팅 방법을 이용해 테스트해야 한다. 그렇지만 유닛 테스트가 있는 경우와 없는 경우 느껴지는 코드에 대한 자신감은 분명 다를것이다.


    댓글

f.software engineer @ All Right Reserved