서비스를 개발하다 보면 우리는 구현한 기능 혹은 메소드를 테스트해야할 경우가 발생할텐데, 이는 개발자로서 중요한 순서이자 역량이다.
하지만 이를 접하지 않는다면 코드 구현력은 물론 더 큰 문제가 발생할 가능성이 높다. 그래서 테스트를 통해 내가 만든 기능들이 올바른 방향으로 흘러가는지에 관해 check 할 필요가 있는데, 이 과정 속에서 Test가 중요하다.
또한 내가 만든 메소드가 비효율적이라면 이는 추후 큰 비용의 발생을 야기할 수 있다 (내가 말하는 큰 비용이라하면 유지보수나, 사용되는 리소스를 의미한다.) 그래서 개발자 입장에선 불필요하거나, 책임에 맞지 않는 코드를 사용하는 것을 줄여야한다.
간단한 예제를 통해 문제점을 발견하고 직접 Test, refactoring까지 경험해 보았다.
public class Calculator {
//Production Code ( 실제 서비스를 담당하는 부분 )
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;}
// Main (여기선 윗 프로덕션 코드가 바르게 동작하는지 확인하기 위한 Test Code)
public static void main(String[] args) {
Calculator calculator =new Calculator();
System.out.println(calculator.add(1, 2));
// 이하 생략
}
}
간단한 사칙연산을 구현한 코드인데, 여기서 확인할 수 있는 문제점은 다음과 같다.
프로덕션 코드와 테스트 코드가 같은 클래스에 위치하고 있다는 점
테스트 코드라하면 테스트 단계에서만 필요하다. 그래서 굳이 서비스 시점에서 같이 배포할 필요가 없는데, 이 문제를 해결하기 위해 프로덕션 코드와 테스트 코드를 분리할 수 있다.
public class CalculatorTest {
public static void main(String[] args) {
Calculator cal = new Calculator();
System.out.println(cal.add(9, 3));
// 이하는 생략
}
}
윗 코드처럼 Test부와 프로덕션 코드를 분리할 수있는데, 여전히 main 메소드에서 프로덕션의 코드의 여러 메소드의 테스트를 동시에 진행한다는 점이 동일하다. 그래서 앞서 작성한 프로덕션의 코드의 복잡도가 증가할수록 결국 이 메인부도 복잡도가 증가할 수 밖에 없는 구조를 가지고 있다.
이 문제를 해결하기 위해선 테스트 코드를 각 메소드 별로 분리할 수 있다.
public static void main(String[] args) {
Calculator cal = new Calculator();
add(cal);
multiply(cal);
subtract(cal);
}
private static void add(Calculator cal) {
System.out.println(cal.add(9, 3));
}
private static void multiply(Calculator cal) {
System.out.println(cal.multiply(9, 3));
}
private static void subtract(Calculator cal){
System.out.println(cal.subtract(3,5));
}
과연 윗 코드처럼 이렇게 분리함으로써 우리가 가진 근본적인 문제를 해결할 수 있을까?
솔직히 나는 잘 모르겠다. 나는 내가 구현하고있는 메서드에만 집중하고 싶은데 그렇지 못한다는 점에서 결국 이 또한 최종 해결책이 될 순 없다. 테스트 코드는 Calculator 클래스의 모든 메소드를 테스트 할 수 밖에 없다. ( 이 말이 와닿지 않음 ) 그렇다고 주석처리를 한다? 이것 또한 바르지 못함.
그래서 이러한 문제점을 해결하기 위해 등장한 라이브러리가 바로 Junit이다.
이렇게 라이브러리를 추가하면 되고, 이로서 @Test 어노테이션을 사용할 수 있다.
아래 print문을 이용해 직접 값을 눈으로 확인할 수 있으며, 내가 기대한 결과가 맞는지 assertEquals 메소드를 이용해 확인할 수 있는데, @Test 어노테이션이 붙은 add() 메소드를 실행시켜보면 다음과 같다.
이렇게 메소드에 대해 각 각 테스트를 진행할 수 있고, 테스트 메소드를 독립적으로 시행할 수 있어서 내가 구현한 프로덕션 코드의 메소드에만 집중이 가능하다. 또 다른 메소드에 영향을 받지 않는다는 장점이 있다. 하지만 위와 같이 메서드가 여러개 존재하면 중복성 코드가 발견될 것이다.
그러나 문법상에선 문제가 없지만 Junit은 테스트를 실행하기 위해 초기화 작업을 윗 방식으로 구현하는 것을 추천하지 않는다.
Junit에서는 이렇게 초기화 하는 방식이 아닌 @Before ( @BeforeEach junit 버전에 따라 다름) ,@After (@AfterEach) 방식을 권장한다. 그 이유는 @Runwith, @Rule과 같은 어노테이션을 사용하려면 @Before내 위치해야 하며, 이 어노테이션을 사용해서 테스트 메소드에 대해 초기화 하는 작업을 하는 것이 추후 문제가 발생할 가능성을 없앨 수 있다.
public class CalculatorTest {
private Calculator cal;
@BeforeEach
public void setup() {
cal = new Calculator();
System.out.println("before");
}
@Test
public void add() {
assertEquals(9, cal.add(4, 5));
System.out.println("add");
}
@Test
public void subtract() {
assertEquals(3, cal.subtract(6, 3));
System.out.println("subtract");
}
@AfterEach
public void teardown(){
System.out.println("teardown");
}
}
이렇게 코드를 작성할 수 있는데, 실행시켜보면 다음과 같다.
해당 코드로 확인해본 결과로는 각 테스트 메서드가 실행될 때, @before과 @After을 거쳐간다는 점이다. 이렇게 매 메서드마다 전처리와 후처리를 통해서 테스트간 영향을 미치지 않도록 하는 것이 좋다.
(근데 궁금한점이 왜 여기서 subtract가 먼저 실행됐는지가 궁금하다.)
참고자료 :
https://junit.org/junit5/docs/current/user-guide/#writing-tests-test-execution-order
'책 읽기' 카테고리의 다른 글
[Clean Code/클린 코드] - 객체와 자료구조 (0) | 2023.06.18 |
---|---|
[Clean Code/클린 코드] - 형식 맞추기 (1) | 2023.06.04 |
[Clean Code/클린 코드] - 주석 (0) | 2023.06.04 |
[Clean Code/클린 코드] - 함수 (0) | 2023.05.29 |
[Clean Code/클린 코드] - 의미있는 이름 (0) | 2023.05.29 |