서론
오늘은 Java와 IntelliJ 환경에서 테스트 환경을 구축하는 방법과 Junit을 활용해 간단하게 테스트 코드를 작성하는 방법에 대해서 알아보겠습니다. 본격적인 내용에 들어가기 앞서, 먼저 질문을 하나 드리고자 합니다. 우리(개발자)는 왜 작성해야 할까요?
테스트 코드를 작성해야 하는 까닭
자바를 활용한 백엔드에선 테스트 코드 없이 테스팅하기 어렵다.
제 경험을 이야기해보자면, 저는 주로 Javascript를 활용했기 때문에, 제가 작성한 코드의 결괏값을 확인하기 편했었습니다. console로 결괏값을 출력하면 끝이기 때문이죠. 사내에서 프론트엔드 개발할 때도 테스트코드의 필요성에 대해서 크게 체감하진 못했습니다. 반면 백엔드 개발은 테스트 코드 없이 각각의 기능을 테스트하기 어렵다고 느꼈습니다. 특히 Java의 경우에는 main 메서드를 거쳐, 어플리케이션이 실행되므로, 테스트 코드 없이 기능 테스트를 하려는 메인 메서드에서 인스턴스를 생성 후 직접 결괏값을 호출해봐야 하죠. 따라서 작성한 코드가 올바르게 동작하는지 테스트를 할 때, 어려움을 겪을 수 밖에 없습니다. 혹은 런타임에서 오류가 발생했을 때 디버거를 통해 디버깅해서 잘못된 점을 찾아줘야 하죠.
테스트 코드는 개발의 불확실성을 줄이는 확실한 도구이다.
좀 더 일반론적인 관점에서 보자면, 테스트 코드를 작성하는 까닭은 소프트웨어 개발의 불확실성을 줄이기 위함이라고 생각합니다. 큰 규모의 어플리케이션을 개발하다보면, 자연스럽게 기능이 복잡하게 되고 개발자의 예상대로 기능(혹은 해당 기능을 수행하는 함수 혹은 메서드)가 동작하지 않을 수 있습니다. 이 때, 문제의 원인을 파악하기 위해서는 정상 작동하는 것은 무엇이고 그렇지 않은 건 무엇인지 파악하는 과정을 거쳐야 합니다. 테스트 코드를 작성해두면 이러한 불확실성을 줄일 수 있을 뿐 아니라, 한번 작성한 테스트 코드는 반복적으로 실행할 수 있기 때문에 코드를 리팩터링하거나 기능을 추가할 때 유용하게 활용될 수 있습니다.
본론
테스트 환경설정하기
자 이제, 본격적으로 간단한 테스트코드를 작성하기 위한 환경을 구성하도록 하겠습니다. 저 같은 경우에는 개발 생산성을 높이기 위해, IntelliJ IDE를 사용 중인데요, 동일한 실습환경을 구성하시길 권장드립니다. 또한 실습 OS가 Mac이라는 점도 미리 양해 부탁드립니다.
Mac 기준으로 File > new > Project
를 실행해줍니다. 먼저, 아래 스크린샷처럼 말이죠.
그런 뒤, 프로젝트명
과 경로
를 설정해줍니다. 그리고 언어
를 설정해줍니다. Build System도 IntelliJ로 설정해줍니다. 그 까닭은 Gradle이나 Maven으로 설정했을 때보다 실행 및 빌드 속도가 빠르기 때문이죠.
그러면 src 폴더에 Main 클래스가 생성되고, 해당 파일엔 main 메서드가 생성된 것을 확인하실 수 있을 겁니다. 그리고 테스트를 위해 정~말 간단한 함수를 Main Class에서 작성해주겠습니다.
public String hello(){
return "hello world";
}
그리고 해당 코드를 검증하기 위해 테스트 코드가 위치할 디렉토리를 설정해주겠습니다. 프로젝트의 최상위 경로에 test란 폴더를 추가해주겠습니다.
그런 뒤, 프로젝트 폴더를 마우스 우클릭해 속성을 열어, Open Moduls Settings를 클릭합니다.
Project Settings의 Modules에서 Sources 중 test 폴더를 선택한 후, Mark as Tests로 설정해줍니다.
이렇게 설정해주는 까닭은 코드(테스트 코드와의 구분을 위해 운영 코드라고 명명하겠습니다)와 테스트 코드를 분리하기 위함인데요, 테스트 코드는 기본적으로 개발 시에만 사용되고 배포 후에는 사용되지 않습니다. 개발자가 정확한 개발을 하기 위해 작성하는 코드이기 때문이죠. 따라서 배포된 환경에서는 의미가 없으며, 포함될 이유도 없죠. 이렇게 test 폴더로 명시하면 배포 시에 활용되지 않을 뿐 아니라, 운영 코드와 분리됨으로 보기에도 좋죠.
위 과정이 복잡하고 귀찮으시다면, IntelliJ에서 제공하는 기능을 활용하는 방법도 있습니다. Mac 기준으로 control
+ Enter
를 누르면, 아래처럼 Generate 창이 뜹니다. (window에서는 Alt + Enter를 입력해줍니다.)
여기서 Test...을 눌러줍니다. 그러면 아래와 같이, 자동으로 test 디렉토리와 test 폴더 설정이 되는 것을 확인하실 수 있습니다.
배경지식
테스팅을 하다보면 assert란 단어가 자주 등장합니다. 한글론 '주장하다'란 뜻을 가능 명사인데요, assert의 명사형인 Assertion은 컴퓨터 프로그래밍에서 개발자가 반드시 참(true)이어야 한다고 생각하는 사항을 표현한 논리식을 말합니다. 테스트 프레임워크에선 해당 개념을 차용해, 테스트가 통과하기 위한 조건을 코드로 명시할 수 있도록 돕습니다.
테스트 코드 작성하기
그럼 앞서 작성한 테스트 코드를 Junit을 통해, 간단하게 검증해보도록 하겠습니다. 테스트 코드는 아래와 같이 간단하게 작성해줄 수 있는데요. 먼저 `@Test는 해당 메서드가 Test 코드임을 나타내는 어노테이션을 추가해줍니다. 해당 구문을 추가함으로써 테스트가 실행될 수 있게 됩니다.
import org.junit.jupiter.api.Test;
class MainTest {
@Test
void hello() {
...
}
}
그 다음으론 Junit의 Assertions의 assertEqauls 메서드를 활용해, main 클래스의 hello 메서드의 반환값과 같은 지 비교해줍니다. assertEqauls의 첫번째 인자로는 기댓값(expected)이 오고, 두번째 인자로는 실제값(actual)이 옵니다.
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class MainTest {
@Test
void hello() {
Main main = new Main();
Assertions.assertEquals("hello world", main.hello());
}
}
두 값이 서로 같으면 테스트가 아래와 같이, 테스트 통과 메세지가 출력됩니다.
그러나 같지 않으면 아래와 같이, 에러 메세지가 출력됩니다. 따라서 개발자는 이를 통해 코드를 수정해줄 수 있게 되겠죠.
에러처리
테스트코드 처리에서 중요한 부분 중 하나가 또 있습니다. 바로 에러처리입니다. 간단하게 이번 예시에서는 hello 메서드를 두 번 이상 호출했을 때 에러를 반환하고 테스트코드로 에러 처리가 제대로 수행됐는지 살펴보도록 하겠습니다. 앞서 작성한 코드에 Hello 클래스를 추가해주겠습니다.
public class Hello {
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
그리고 이 함수의 id 값을 Main 클래스의 count로 지정해주겠습니다.
public class Main {
private static long count = 0L;
public static String hi() {
Hello hello = new Hello();
hello.setId(++count);
if(hello.getId() > 1){
throw new IllegalStateException("hi must be called once");
}
return "hello world";
}
}
아래와 같이, 테스트를 작성하면 성공적으로 수행되는 것을 알 수 있습니다.
class MainTest {
@Test
void hello() {
Main main = new Main();
Assertions.assertEquals(main.hi(), "hello world");
IllegalStateException e = Assertions.assertThrows(IllegalStateException.class, () -> main.hi());
Assertions.assertEquals(e.getMessage(), "hi must be called once");
}
}
마무리
이상으로 간단하게 테스트 코드를 작성하는 방법과 테스트 환경을 구축하는 방법에 관해서 알아보았습니다. 아직 테스팅에 관해선 모르는 부분이 많지만, 새롭게 알게 된 유용한 정보들이 있으면 언제든 글을 보충해놓도록 하겠습니다. 본문의 글과 다른 의견이 있다면, 의견을 남겨주셔도 감사하겠습니다. 오늘도 읽어주셔서 감사합니다.
'Language > Java & Kotlin' 카테고리의 다른 글
[Java] 동기화란 무엇인가? (1) 상호배제 (0) | 2023.10.03 |
---|---|
[Java] JVM의 동작 원리 (부제 : Java를 처음 공부하는 당신에게) (0) | 2023.09.22 |
[Java] 객체 생성 패턴이란? (빌더패턴, 자바빈패턴, 점층적 생성자 패턴) (0) | 2023.06.21 |
[Java] JDK, JRE, JVM (0) | 2022.05.22 |
[Java] 자주 쓰이는 util 클래스 정리하기 (1) HashMap (0) | 2022.05.12 |
댓글