개발 환경 관련

[JUnit5, IntelliJ] 테스트 코드 기반으로 알고리즘 문제풀이 프로젝트 패키지 구조 관리하기

Emil :) 2023. 7. 20. 16:23
728x90
반응형

목차

이 글은 Notion에서 작성 후 재편집한 포스트입니다.


서론


옛날부터 패키지 구조를 어떻게 해야 효율적으로 관리할 수 있을까? 에 대한 관심이 정말 많았다.

많은 사람들이 코딩테스트, 혹은 알고리즘 역량 강화를 위한 문제풀이를 많이 한다.
그 때 마다 메인함수를 작성하자니, 손이 많이 가고, 기존 코드는 주석 처리하고.. 이런게 굉장히 비효율적이라고 생각했다.

그래서 어떻게 관리하시는지들 개발자 톡방에 물어봤다.

테스트 코드를 활용해본다 라.. 생각지도 못한 방법이었다.

어차피 개발을 한다면 테스트코드는 많이 작성하게 되어있으니, 이런 사소한 부분도 체득시키면 테스트 코드 환경에 익숙해질 것 같아서, 바로 적용해봤다.

JUnit5와 IntelliJ 환경에서 진행했다. JUnit 적용 방법은 아래 참고.

참고


https://ildann.tistory.com/5

 

IntelliJ IDEA에 JUnit 추가하기 / 테스트 코드 작성

IntelliJ IDEA에서 JUnit을 추가하고 테스트 코드를 작성해보자. 아래 코드는 Java의 메서드 오버로딩 예제 코드이다. Main.java public class Main { public static void main(String arg []){ Multiplier multiplier = new Multiplier

ildann.tistory.com

https://joonfluence.tistory.com/693

 

[Java] IntelliJ에서 Junit으로 테스트 환경을 구축하는 방법

서론 오늘은 Java와 IntelliJ 환경에서 테스트 환경을 구축하는 방법과 Junit을 활용해 간단하게 테스트 코드를 작성하는 방법에 대해서 알아보겠습니다. 본격적인 내용에 들어가기 앞서, 먼저 질문

joonfluence.tistory.com

본론


기존엔 어떻게 했나?


보통 문제풀이 사이트들은 클래스이름 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의 장단점과 일맥상통하다고 생각한다.
하지만 테스트코드는 개발을 진행하면서 요즘엔 필수 덕목이니, 이번 기회에 익숙해지는건 어떤가?

구독 및 하트는 정보 포스팅 제작에 큰 힘이됩니다♡

728x90
반응형