토비의 스프링 정리 프로젝트 #2.5 학습 테스트로 배우는 스프링

Jake Seo·2021년 7월 16일
0

토비의 스프링

목록 보기
15/29

학습 테스트란?

때로는 자신이 만들지 않은 프레임워크나 다른 개발팀에서 만들어서 제공한 라이브러리 등에 대해서도 테스트를 작성해야 한다. 이런 테스트를 학습 테스트(learning test)라고 한다.

학습 테스트의 목적은 잣니이 사용할 API나 프레임워크의 기능을 테스트로 보면서 사용 방법을 익히려는 것이다. 자신이 테스트를 만드려고 하는 기술이나 기능에 대해 얼마나 제대로 이해하고 있는지, 그 사용 방법을 바로 알고 있는지를 검증하려는 게 목적이다.

테스트 코드를 만드는 과정을 통해 API의 사용 방법도 익히고, 내가 가진 기술에 대한 지식도 검증할 수 있다. 때로는 어설프게 알고 있거나 오해하고 있던 지식을 테스트를 만드는 과정을 통해 바로잡기도 한다.

학습 테스트의 장점

다양한 조건에 따른 기능을 손쉽게 확인해볼 수 있다.

단순히 콘솔로 결과 값을 확인하는 것이 아니라, 학습 테스트는 자동화된 테스트 코드로 만들어지기 때문에 다양한 조건에 따라 기능이 어떻게 동작하는지 빠르게 확인할 수 있다.

학습 테스트 코드를 개발 중에 참고할 수 있다.

수동으로 예제를 만들다보면, 코드를 계속 수정해가며 기능을 확인해보기 때문에 마지막에는 최종 수정한 예제 코드만 남아있게 된다. 반면에 학습 테스트는 다양한 기능과 조건에 대한 테스트코드를 개별적으로 만들고 남겨둘 수 있다.

개발자들은 필요하다면 언제든지 학습 테스트로 만들었던 코드를 참고해볼 수 있다. 또는 좀 더 나은 사용 방법을 발견했다면, 학습 테스트의 코드를 수정해서 다른 개발자와 공유할 수 있다.

프레임워크나 제품을 업그레이드할 때 호환성 검증을 도와준다.

요즘은 모든 제품이 매우 빠르게 업데이트 된다. 문제는 이렇게 새로운 버전으로 업그레이드할 때, API 사용법에 미묘한 변화가 생긴다거나, 기존에는 잘 동작하던 기능에 문제가 발생할 수도 있다는 점이다.

학습 테스트를 꾸준히 작성했다면, 기존에 사용하던 API가 기능에 문제가 없다는 사실을 미리 확인할 수 있다. 학습 테스트에 애플리케이션에서 자주 사용하는 기능에 대한 테스트를 만들어놓았다면 새로운 버전의 프레임워크나 제품을 학습 테스트에만 먼저 적용하고 미리 확인해볼 수 있다.

테스트 작성에 좋은 훈련이 된다.

테스트를 작성하는데 아직 충분히 훈련되어 있지 않거나 부담을 갖고 있다면, 학습 테스트를 충분히 작성하면서 테스트 코드 작성을 연습할 수 있다.

새로운 기술을 공부하는 과정이 즐거워진다. (레퍼런스만 보는 것보다 재밌다.)

문서에 딱딱하게 쓰여진 기능이 테스트 코드를 통해 실제로 동작하는 모습을 보는 것은 즐겁다.

스프링 학습 테스트의 레퍼런스

스프링 학습 테스트를 만들 때, 참고할 수 있는 가장 좋은 소스는 바로 스프링 자신에 대한 테스트 코드이다. 스프링은 꼼꼼하게 테스트를 만들어가며 개발해온 프레임워크다. 거의 모든 기능에 대한 방대한 양의 테스트가 만들어져 있다.

스프링 테스트를 잘 살펴보면 레퍼런스 문서에는 미처 설명되지 않았던 중요한 정보도 많이 얻고, 테스트를 작성하는 방식에 대한 좋은 팁을 얻을 수도 있다.

학습 테스트 예제

JUnit 테스트 오브젝트 테스트

JUnit은 테스트 메소드를 수행할 때마다 정말 새로운 오브젝트를 만드는지 확인해보자.

public class JUnitTest {
    static JUnitTest testObject;

    @BeforeAll
    public static void beforeAll() {
        testObject = new JUnitTest();
    }

    @AfterEach
    public void afterEach() {
        testObject = this;
    }

    @Test
    public void test1() {
        assertNotSame(testObject, this);
        System.out.println("testObject = " + testObject);
        System.out.println("this = " + this);
    }

    @Test
    public void test2() {
        assertNotSame(testObject, this);
        System.out.println("testObject = " + testObject);
        System.out.println("this = " + this);
    }

    @Test
    public void test3() {
        assertNotSame(testObject, this);
        System.out.println("testObject = " + testObject);
        System.out.println("this = " + this);
    }
}

위와 같이 만들어보았다. 프린트문은 테스트가 정상적으로 작동하는지 눈으로 한번 더 확인해보고 지우면 된다.

처음에 @BeforeAll을 통해 새 JUnitTest 오브젝트를 생성한 뒤에 각 테스트가 끝나면 @AfterEach를 통해 새로운 this, 즉, JUnitTest 오브젝트를 넣어주었다.

애플리케이션 컨텍스트 테스트

@ExtendWith(SpringExtension.class) // (JUnit5)
@ContextConfiguration(locations="/spring/applicationContext.xml")
public class ApplicationContextTest {
    @Autowired ApplicationContext applicationContext;
    static Set<ApplicationContext> applicationContexts = new HashSet<>();

    @AfterAll
    public static void afterAll() {
        assertEquals(applicationContexts.size(), 1);
    }

    @Test
    public void test1() {
        applicationContexts.add(applicationContext);
    }

    @Test
    public void test2() {
        applicationContexts.add(applicationContext);
    }

    @Test
    public void test3() {
        applicationContexts.add(applicationContext);
    }
}

Set<ApplicationContext>에 매번 applicationContext를 추가시켰지만, 마지막에 Set에 담긴 사이즈는 1인 것을 테스트함으로써, 매번 같은 ApplicationContext를 사용한다는 것을 테스트해보았다.

그런데 단순히 이렇게 해서는 인스턴스 멤버를 계속 추가하는 것 같으니 이전의 JUnitTest 클래스와 합쳐보자.

JUnit와 ApplicationContext를 함께 테스트

@ExtendWith(SpringExtension.class) // (JUnit5)
@ContextConfiguration(locations="/spring/applicationContext.xml")
public class JUnitTest {
    @Autowired ApplicationContext applicationContext;
    JUnitTest jUnitTest = this;
    static Set<ApplicationContext> applicationContexts = new HashSet<>();
    static Set<JUnitTest> junitTests = new HashSet<>();

    @AfterAll
    public static void afterAll() {
        assertEquals(applicationContexts.size(), 1);
        assertEquals(junitTests.size(), 3);
    }

    @Test
    public void test1() {
        applicationContexts.add(applicationContext);
        junitTests.add(jUnitTest);
    }

    @Test
    public void test2() {
        applicationContexts.add(applicationContext);
        junitTests.add(jUnitTest);
    }

    @Test
    public void test3() {
        applicationContexts.add(applicationContext);
        junitTests.add(jUnitTest);
    }
}

멤버 인스턴스에 정의된 두 멤버를 각각 타입에 맞는 Set에 추가하고, 결과적으로 junitTest는 사이즈가 3, applicationContexts는 사이즈가 1임을 테스트해보았다.

버그 테스트

버그 테스트(bug test)란 코드에 오류가 있을 때, 그 오류를 가장 잘 드러내줄 수 있는 테스트를 말한다.

버그가 발생한 경우, 무턱대고 코드를 이것저것 수정해보는 것보다 버그 테스트를 만드는 편이 좋다. 버그 테스트는 일단 실패하도록 만든 뒤에, 버그 테스트가 성공할 수 있또록 애플리케이션 코드를 계속 수정해나간다. 테스트가 성공하면 버그는 해결된 것이다.

버그 테스트는 테스트의 완성도를 높여준다.

버그는 기존 테스트에서 미처 검증하지 못했던 부분이 있기 때문에 버그가 생기는 것이므로 불충분했던 테스트를 보완해주는 역할을 한다.

버그 테스트는 버그의 내용을 명확하게 분석해준다.

버그가 있을 때 테스트로 만들어서 실패하게 만들려면 어떤 이유 때문에 문제가 생겼는지 명확히 알아야 한다. 따라서 버그를 조금 더 효과적으로 분석할 수 있다. 또한 그 과정에서 그로 인해 발생할 수 있는 다른 오류를 함께 발견할 수도 있다.

예외적인 상황이나 입력 값때문에 발생하는 오류였다면, 테스트 코드를 만들면서 오류를 발생시키는 값의 범위가 어떤 것인지 분석해볼 기회가 주어진다. 테스트의 중요한 기법 중 하나인 동등분할, 경계값 분석을 적용해볼 수도 있다.

기술적인 문제를 해결하는데 도움이 된다.

때로는 코드 설정을 아무리 살펴봐도 버그의 원인이 무엇인지 정확하게 파악하기 힘들 때가 있다. 이럴 땐 동일한 문제가 발생하는 가장 단순한 코드와 그에 대한 버그 테스트를 만들어보면 도움이 된다.

동등 분할과 경계값 분석이란?

동등 분할

같은 결과를 내는 값의 범위를 구분해서 각 대표 값으로 테스트를 하는 방법을 말한다. 이를테면 어떤 작업이 true, false, Exception 세가지 결과를 갖고 있다면, 각각의 결과를 내는 테스트들을 작성해보는 것이 좋다.

경계값 분석

에러는 동등분할 범위의 경계에서 많이 발생한다는 특징을 이용해 경계의 근처에 있는 값을 이용해 테스트해보는 방법을 말한다. 숫자인 경우 0 혹은 그 주변값, 또는 정수의 최대값, 최소값으로 테스트해볼 수 있다.

profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

1개의 댓글

comment-user-thumbnail
2022년 10월 10일

오타 제보합니다~
"학습 테스트의 목적은 잣니이 사용할 API" → "학습 테스트의 목적은 자신이 사용할 API

답글 달기