목차
이 글은 Notion에서 작성 후 재편집한 포스트입니다.
서론
옛날부터 패키지 구조를 어떻게 해야 효율적으로 관리할 수 있을까? 에 대한 관심이 정말 많았다.
많은 사람들이 코딩테스트, 혹은 알고리즘 역량 강화를 위한 문제풀이를 많이 한다.
그 때 마다 메인함수를 작성하자니, 손이 많이 가고, 기존 코드는 주석 처리하고.. 이런게 굉장히 비효율적이라고 생각했다.
그래서 어떻게 관리하시는지들 개발자 톡방에 물어봤다.
테스트 코드를 활용해본다 라.. 생각지도 못한 방법이었다.
어차피 개발을 한다면 테스트코드는 많이 작성하게 되어있으니, 이런 사소한 부분도 체득시키면 테스트 코드 환경에 익숙해질 것 같아서, 바로 적용해봤다.
JUnit5와 IntelliJ 환경에서 진행했다. JUnit 적용 방법은 아래 참고.
참고
https://joonfluence.tistory.com/693
본론
기존엔 어떻게 했나?
보통 문제풀이 사이트들은 클래스이름 Main의 Main 함수에 작성하라고 한다. (프로그래머스는 solution임)
이처럼 메인 함수를 필요로 하고 있기 때문에, 풀이 함수 이름을 solution으로 통일해서, 별도의 메인함수에서 테스트할 때마다 import 해서 사용하는 방식을 차용했다.
// maximumDifferenceInNumbers.java
package codetree.brute_force_III;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
public class maximumDifferenceInNumbers {
public static int n, k;
public static int[] arr;
public static int countNum(int l, int r){
int cnt =0;
for(int i = 0; i < n; i++){
if(l <= arr[i] && arr[i] <= r)
cnt++;
}
return cnt;
}
public static void solution(){
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
k = sc.nextInt();
arr = new int[n];
for(int i = 0; i < n; i++)
arr[i] = sc.nextInt();
int answer = 0;
for(int i = 1; i <= 10001; i++){
answer = Math.max(answer, countNum(i, i+k));
}
System.out.println(answer);
}
}
// main.java
import static codetree.brute_force_III.maximumDifferenceInNumbers.solution;
public class Main {
public static void main(String[] args) {
solution();
}
}
이렇게 하면 메인 함수를 하나만 두고, import만 해주면 되서 이전보다 훨씬 편했었다.
그런데 좀 더 나은 방법이 없을까? 하고 고민했었고, 테스트 코드 환경을 적용시켜 보기로 했다.
테스트 코드 환경 적용시키기
먼저, 기존에 작성된 greatJump.java 라는 문제 파일은 다음과 같이 작성했다.
// greatJump
import java.util.*;
public class Main {
static int MAX = 100;
static int[] arr;
public static int n, k;
public static boolean isPossible(int limit){
int lastIdx = 0;
for(int i = 1; i < arr.length; i++){
if(arr[i] <= limit){
if(i-lastIdx > k)
return false;
lastIdx = i;
}
}
return true;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
k = sc.nextInt();
arr = new int[n];
for(int i = 0; i < n; i++)
arr[i] = sc.nextInt();
int answer = 0;
// 뛸 수 있는 거리
for(int i = Math.max(arr[0], arr[n-1]); i <= MAX; i++){
if(isPossible(i)){
System.out.println(i);
break;
}
}
}
}
main 함수 내에 입력을 받고, 추가적인 처리를 진행하면서 isPossible 함수를 호출하는 로직이 있다. 메인함수는 테스트 코드를 작성하기 부적절하다. 따라서 별도로 입력이 없어도 클래스 변수에 접근할 수 있도록 다음과 같이 로직을 바꿔줬다.
package codetree.brute_force_III;
import java.util.Scanner;
public class greatJump {
public static int[] arr = new int[100];
public static int n, k;
public static boolean isPossible(int num){
//마지막 인덱스로부터 k를 넘지않으면서 이동이 가능한가?
int lastIdx = 0;
for (int i = 1; i < n; i++) {
if (arr[i] <= num) {
if (i - lastIdx > k) {
return false;
}
lastIdx = i;
}
}
return true;
}
//변경된 부분, 기존 로직을 함수로 따로 빼서 작성해줬다.
public static int findJumpValue() {
int result = 0;
for (int i = Math.max(arr[0], arr[n - 1]); i < 100; i++) {
if (isPossible(i)) {
result = i;
break;
}
}
return result;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
k = sc.nextInt();
int result = findJumpValue();
System.out.println(result);
}
}
핵심 로직들을 함수로 따로 작성해줬다.
그리고 이 함수들에 대한 테스트코드는 다음과 같이 작성해준다.
//greatJumpTest.java
package codetree.brute_force_III;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class greatJumpTest {
@Test
void isPossibleTest() {
greatJump temp = new greatJump();
// 예상 결과가 true인 경우
temp.arr = new int[]{2, 3, 5, 4, 1}; // 임의의 배열을 설정해줍니다.
temp.n = 5;
temp.k = 2;
assertTrue(temp.isPossible(4));
// 예상 결과가 false인 경우
temp.arr = new int[]{2, 3, 5, 4, 1}; // 임의의 배열을 설정해줍니다.
temp.n = 5;
temp.k = 3;
assertFalse(temp.isPossible(2));
}
@Test
void findJumpValueTest() {
greatJump temp = new greatJump();
// 예상 결과가 4인 경우
temp.arr = new int[]{2, 3, 5, 4, 1}; // 임의의 배열을 설정해줍니다.
temp.n = 5;
temp.k = 2;
int result1 = temp.findJumpValue();
assertEquals(4, result1);
// 예상 결과가 2인 경우
temp.arr = new int[]{2, 1, 1, 1, 1}; // 임의의 배열을 설정해줍니다.
temp.n = 5;
temp.k = 2;
int result2 = temp.findJumpValue();
assertEquals(4, result2);
}
@Test
void main() {
greatJump temp = new greatJump();
}
}
코드트리라서 문제를 공개할 수는 없지만, 문제에서 n = 5, k = 2, arr = [2, 1, 1, 1, 1] 일 때 답은 4로 나온다.
코드에서 greatJump 인스턴스를 생성해주고, 그 뒤에 사용자가 입력할 값을 하드코딩으로 입력하는 방식이다!
그리고 이 함수들에 대해서 테스트를 돌려주면 다음과 같다.
이런식으로 문제를 풀다보면, 단계별로 내가 어느 부분에서 미스가 나는지 추적이 용이해질 것 같다.
그리고 무엇보다 코드가 구조를 띄게 된다. 일단 기분이 좋음.
결론
JUnit을 활용해 테스트코드로 알고리즘 문제풀이 프로젝트를 구조화 시켰다.
장단점을 정리해보자.
장점
- 테스트 코드로 작성하면서, TDD에 익숙해질 수 있다.
- 단위 테스트 환경을 조성하여, 문제가 틀렸을 때 단계별로 추적이 가능하다.
- 프로젝트 코드 자체가 구조를 띄게 된다.
- '객체지향 프로그래밍' 스럽게 개발이 가능하다.
단점
- 테스트 코드를 따로 작성해야해서 시간이 별도로 든다.
- main 함수에서 핵심 로직을 다루는 함수를 어떻게 따로 작성해야할지 설계가 필요하다.
- 귀찮다.
어쨋든 테스트코드에 기반한 것이기 때문에, TDD의 장단점과 일맥상통하다고 생각한다.
하지만 테스트코드는 개발을 진행하면서 요즘엔 필수 덕목이니, 이번 기회에 익숙해지는건 어떤가?
구독 및 하트는 정보 포스팅 제작에 큰 힘이됩니다♡
'개발 환경 관련' 카테고리의 다른 글
[ERROR] Could not establish connection to ~~(feat. vs code) (2) | 2021.02.02 |
---|---|
[sourcetree] 소스트리 기본 사용법, 보는 법 (0) | 2020.11.26 |
[Eclipse, Tomcat] Tomcat 404 not found, 요청된 리소스 [/]은(는) 가용하지 않습니다. (7) | 2020.10.15 |
[Eclipse] local history 삭제, 복구, 이전 기록 (3) | 2020.10.13 |
[Eclipse] Maven build error, An error occurred while automatically activating bundle org.eclipse.m2e.core (552). (0) | 2020.09.17 |