우아한 테크코스 미션을 진행하던 도중, TDD로 개발을 하는데 랜덤 데이터를 테스트해야 하는 경우가 생겼다. 이 때, 랜덤 데이터를 뽑는걸 어떻게 테스트해야 할까 고민하다가 좀 검색하고 찾아보니 랜덤 데이터를 사용하는 클래스의 생성자에 랜덤 데이터를 뽑는 인터페이스를 받도록 구현하고 테스트 클래스에서는 구현 객체를 특정 정수를 리턴하도록 구현하였다. 그 결과 어느정도 만족스럽게 된 것 같다.

 

RandomUtil 인터페이스 생성


우선, 랜덤값을 발생시키는 generate 메소드를 가지는 RandomUtil 인터페이스를 만들어 둔다.

public interface RandomUtil {
    public int generate();
}

 

클래스 생성자에 RandomUtil 받도록 구현


Example 클래스에서 RandomUtil의 구현 객체에 의존하도록 생성자에 RandomUtil을 추가해 준다.

 

public class Example {
    RandomUtil randomUtil;

    public Example(RandomUtil randomUtil) {
        this.randomUtil = randomUtil;
    }

    public int generate() {
        return randomUtil.generate();
    }
}

 

테스트 케이스 작성


위와 같이 생성자에서 RandomUtil을 추가해 주면, 넘겨준 RandomUtil을 조정해주는 방식을 통해 원하는대로 테스트를 진행할 수 있다. 따라서, Random의 결과를 조정해 줄 수 있으므로, 각각의 결과에 따라 테스트 케이스를 작성하는 방식으로 진행하였다.

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.Random;

import static org.junit.jupiter.api.Assertions.*;

class ExampleTest {
    @Test
    @DisplayName("generate의 결과로 1이 나옴")
    void generate_1() {
        Example example = new Example(() -> 1);
        assertEquals(example.generate(), 1);
    }

    @Test
    @DisplayName("generate의 결과로 5가 나옴")
    void generate_5() {
        Example example = new Example(() -> 5);
        assertEquals(example.generate(), 5);
    }

    @Test
    @DisplayName("generate의 결과로 난수가 나옴")
    void generate_Random() {
        Example example = new Example(() -> {
            Random random = new Random();
            return random.nextInt(100);
        });
        int randomData = example.generate();
        assertTrue(0 <= randomData && randomData < 100);
    }
}

'Language > Java' 카테고리의 다른 글

JVM의 동작원리  (0) 2022.10.22
lambda 예외 핸들링  (0) 2022.10.02
[ Junit ] 테스트 작성  (0) 2022.02.10
[ Java ] 쉽게 최대공약수 구하기  (0) 2022.02.03
[ Java ] 문자열 알파벳순 정렬하기  (0) 2022.01.12

이번 우아한테크코스 시간에는 테스트 작성 기법과 TDD에 관하여서 학습 및 실습을 하였다. 실제로 페어와 TDD로 의식적으로 개발을 하려다 보니, 테스트 케이스를 어떻게 작성해야 할 지 고민을 많이 하게 되는 것 같고, 무엇보다 junit을 능숙하게 사용하지 못하다 보니 턱턱 막히는 경우가 많아 따로 정리해보려고 한다ㅎㅎ


Dependency


처음에는 왜 자동완성이 안될까 고민했었는데 Dependency 설정을 하지 않아서 문제가 있었음을 알 수 있었다..ㅋㅋ

testImplementation 'org.assertj:assertj-core:3.22.0'
testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2'

 

애노테이션(Annotation)


Junit을 사용하면 애노테이션을 활용하여 테스트 케이스를 구현할 수 있다.

  1.  @Test
    • 테스트를 진행 할 메소드
    • AssertEquals 등 Assert 클래스의 static 메소드 활용
  2. @BeforeEach
    • 각각의 테스트(@Test)를 진행하기 전에 실행 할 메소드 
  3. @AfterEach
    • 각각의 테스트(@Test)를 진행한 후에 실행 할 메소드
  4. @BeforeAll
    • 모든 테스트를 실행하기 전에 단 한번만 실행되는 메소드
    • static으로 선언
  5. @AfterAll
    • 모든 테스트를 실행한 후에 단 한번만 실행되는 메소드
    • static으로 선언

BeforeAll과 AfterAll이 static으로 선언되는 이유는 static으로 선언해 두어야 각 테스트를 실행하기 전에 실행되어지기 때문이라고 한다.


예시


  • BeforeEach 애노테이션을 통해 테스트 진행 전, numbers에 값을 설정해 준다.
    • 이 때, Set 자료구조의 특성상 중복된 1은 제거된다.
  • Test 애노테이션에서는 실제 테스트가 진행된다.
    • BeforeEach의 결과로 Set 자료구조에는 [1, 2, 3]이 들어가 있다.
    • 따라서, size()는 3이어야 하므로 이를 assertEquals 메소드를 통해 테스트를 진행한다.
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

private Set<Integer> numbers;

@BeforeEach
void setUp() {
    numbers = new HashSet<>();
    numbers.add(1);
    numbers.add(1);
    numbers.add(2);
    numbers.add(3);
}

@Test
void setSizeTest() {
    assertEquals(numbers.size(), 3);
}

 

 

최대공약수를 구할 때, 여지껏 두개의 수를 입력받아서 나머지와 나눗셈 연산을 통해 구하는 방법을 사용했지만 최근에는 파이썬에서는 Math.gcd라는 함수가 있어 잘 사용했지만, Java에서는 Math 클래스에 gcd 함수가 없더라.. 내장함수를 쓰면 편하니까 비슷한 함수가 있나 찾아보니 BigInteger라는 클래스에 gcd라는 함수가 있었다!

 

BigInteger 클래스를 사용해서 최대공약수를 구하려면 다음과 같은 단계를 거쳐 최대공약수를 구할 수 있다.

  1. 2개의 숫자(int형)를 BigInteger 클래스로 변환한다.
    • 이 때, BigInteger의 valueOf 함수를 사용해서 변환해 준다.
  2. BigInteger 클래스의 gcd 함수를 사용하여 최대공약수를 구한다.
  3. 구한 gcd 값을 다시 int형으로 변환한다.
    • 이 때, BigInteger 클래스의 intValue() 메서드를 사용해서 변환해 준다.

 


예시


BigInteger a1 = BigInteger.valueOf(5);
BigInteger a2 = BigInteger.valueOf(10);
int gcd = a1.gcd(a2).intValue();

'Language > Java' 카테고리의 다른 글

[ Junit ] 랜덤 데이터 테스트  (0) 2022.02.10
[ Junit ] 테스트 작성  (0) 2022.02.10
[ Java ] 문자열 알파벳순 정렬하기  (0) 2022.01.12
[알고리즘] Java로 구현한 Union-find  (0) 2021.07.11
[알고리즘] HashMap  (0) 2021.07.11

https://programmers.co.kr/learn/courses/30/lessons/42746?language=java 

 

코딩테스트 연습 - 가장 큰 수

0 또는 양의 정수가 주어졌을 때, 정수를 이어 붙여 만들 수 있는 가장 큰 수를 알아내 주세요. 예를 들어, 주어진 정수가 [6, 10, 2]라면 [6102, 6210, 1062, 1026, 2610, 2106]를 만들 수 있고, 이중 가장 큰

programmers.co.kr



풀이 과정


  1. 핵심은 정렬하기 전에 숫자를 반복해서 4자리로 맞추어주는 것이다. 맞추어주지 않고 정렬한다면 값 자체로 큰 값 순서대로 정렬이 되기 때문이다.
    • 1 -> 1111, 2 -> 2222, 10 -> 1010, ...
  2. 따라서, 반복해서 만들어 준 숫자, 실제 숫자를 저장하는 클래스를 만들어 준 다음, 입력받은 numbers의 값들을 클래스로 매핑해서 리스트로 저장한다.
  3. 다음으로 반복해서 만들어 준 숫자 기준으로 내림차순 정렬한다.
  4. 정렬된 리스트를 기준으로, 하나씩 실제 숫자를 꺼내서 정답 문자열에 붙여준다. 단, 이 때 맨 앞자리가 0인 경우는 0을 출력시켜 주면 된다.
    1. 0이 3번 나온 경우 000 리턴이 아닌 0 리턴임에 유의!!

소스 코드


import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

class Element {
    int value;
    int realValue;

    public Element(int realValue, int value) {
        this.realValue = realValue;
        this.value = value;
    }
}

class Solution {
    public String solution(int[] numbers) {
        String answer = "";

        List<Element> elementList = Arrays.stream(numbers)
                .mapToObj(n -> {
                    String numStr = Integer.toString(n);
                    int idx = 0;
                    int value = 0;
                    while (idx < 4) {
                        value *= 10;
                        value += (Character.digit(numStr.charAt((idx++) % numStr.length()), 10));
                    }
                    return new Element(n, value);
                })
                .collect(Collectors.toList());

        elementList.sort((p1, p2) -> {
            return -Integer.compare(p1.value, p2.value);
        });

        for (Element e : elementList) {
            answer += e.realValue;
        }

        if (answer.charAt(0) == '0')
            return "0";
        return answer;
    }
}

'알고리즘[Java] > 프로그래머스' 카테고리의 다른 글

[ Lv 2 ] 튜플  (0) 2022.01.29
[ Lv 2 ] 짝지어 제거하기  (0) 2022.01.28
[ Lv 3 ] [ DP ] 보행자 천국  (0) 2021.12.21
[ Lv 2 ] [ BackTracking ] 단체사진 찍기  (0) 2021.12.19

https://programmers.co.kr/learn/courses/30/lessons/64065?language=java 

 

코딩테스트 연습 - 튜플

"{{2},{2,1},{2,1,3},{2,1,3,4}}" [2, 1, 3, 4] "{{1,2,3},{2,1},{1,2,4,3},{2}}" [2, 1, 3, 4] "{{4,2,3},{3},{2,3,4,1},{2,3}}" [3, 2, 4, 1]

programmers.co.kr

 


풀이 과정


  1. 우선 주어진 문자열의 양쪽 중괄호를 제거한다.
  2. 정규 표현식을 사용하여 {} 내부의 값들로만 이루어진 Set 리스트를 만들어 준다.
    1. "{" 나 "}" 를 정규 표현식을 사용하여 표시하기 위해서는 "\\{", "\\}"로 표시해주면 된다.
    2. 정규 표현식으로 괄호를 포함한 문자열을 추출했다면, 괄호를 제거한 다음 "," 를 기준으로 split 해준 다음 Set을 만들어준다.
  3. Set 리스트를 길이 순으로 오름차순 정렬한다.
  4. 튜플을 구해준다.
    1. 남아있는 집합중 길이가 가장 짧은 Set을 가져온 다음, 여지껏 방문했던 집합을 빼준다.
    2. 빼주게 되면, 하나의 요소가 남는데 해당 요소가 튜플의 요소이므로 튜플에 추가한다.

소스 코드


import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

class Solution {

    public ArrayList<String> extractSetList(String s) {
        String regex = "\\{[0-9|,]+\\}";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(s);
        String target = s.substring(1, s.length()-1);
        ArrayList<String> setList = new ArrayList<String>();
        while (matcher.find()) {
            setList.add(matcher.group());
        }
        return setList;
    }

    public Set<String>[] extract(String s) {
        ArrayList<String> setList = extractSetList(s);
        return setList.stream()
                .map(p -> {
                    p = p.substring(1, p.length()-1);
                    String[] setInformation = p.split(",");
                    return new HashSet(Arrays.asList(setInformation));
                })
                .toArray(i -> new HashSet[i]);
    }

    public int[] solution(String s) {
        List<String> answer = new ArrayList<String>();
        Set<String> currentSet = new HashSet<>();

        Set<String>[] sets = extract(s);

        Arrays.sort(sets, (p1, p2) -> {
           return Integer.compare(p1.size(), p2.size());
        });

        for (Set<String> targetSet : sets) {
            targetSet.removeAll(currentSet);
            String targetElement = targetSet.iterator().next();
            answer.add(targetElement);
            currentSet.addAll(targetSet);
        }

        return answer.stream()
                .mapToInt(i -> Integer.parseInt(i))
                .toArray();
    }


}

https://programmers.co.kr/learn/courses/30/lessons/12973?language=java 

 

코딩테스트 연습 - 짝지어 제거하기

짝지어 제거하기는, 알파벳 소문자로 이루어진 문자열을 가지고 시작합니다. 먼저 문자열에서 같은 알파벳이 2개 붙어 있는 짝을 찾습니다. 그다음, 그 둘을 제거한 뒤, 앞뒤로 문자열을 이어 붙

programmers.co.kr


풀이 과정


  1. Java에서는 Python과 다르게 문자열 다루기가 조금 난해한거 같다..ㅜ
    1. 문자열의 toCharArray 메서드를 사용하거나 아니면 int[] 배열 형식으로 문자들을 처리하는 방식이 가장 좋아보이는 것 같음.
  2. 문제의 내용을 살펴보면, 두가지 과정을 반복해서 진행하면 된다.
    1. 스택의 맨 위의 값이 이번에 넣으려는 문자와 같다면, 스택에 있는 값을 빼주고 다음 문자로 넘어감.
    2. 스택의 맨 위 값과 이번에 넣으려는 문자와 다르면, 그냥 스택에 문자를 넣어준다.
  3. 마지막으로, 스택이 비어있는지 여부를 확인한 다음 스택이 비어있다면 1, 비어있지 않다면 0을 출력한다.

소스 코드


import java.util.Stack;

class Solution
{
    public int solution(String s)
    {
        Stack<Integer> stack = new Stack<>();
        int[] targetChars = s.chars()
                .map(i -> i - '0')
                .toArray();

        for (int ch : targetChars) {
            if (!stack.isEmpty() && stack.peek() == ch) {
                stack.pop();
                continue;
            }
            stack.add(ch);
        }

        if (stack.isEmpty()) {
            return 1;
        }
        return 0;
    }
}

'알고리즘[Java] > 프로그래머스' 카테고리의 다른 글

[ Lv 2 ] 가장 큰 수  (0) 2022.01.30
[ Lv 2 ] 튜플  (0) 2022.01.29
[ Lv 3 ] [ DP ] 보행자 천국  (0) 2021.12.21
[ Lv 2 ] [ BackTracking ] 단체사진 찍기  (0) 2021.12.19

https://www.acmicpc.net/problem/1946

 

1946번: 신입 사원

첫째 줄에는 테스트 케이스의 개수 T(1 ≤ T ≤ 20)가 주어진다. 각 테스트 케이스의 첫째 줄에 지원자의 숫자 N(1 ≤ N ≤ 100,000)이 주어진다. 둘째 줄부터 N개 줄에는 각각의 지원자의 서류심사 성

www.acmicpc.net


풀이 과정


  1. 지원자 A의 성적이 어떤 지원자 B보다 서류 심사, 면접 성적이 모두 떨어지는 경우는 선발되지 않는다라는 조건이 있으므로, 따라서 서류 심사를 기준으로 오름차순 정렬을 해 준다.
  2. 정렬된 리스트를 기준으로 순차적으로 지원자를 검사한다.
    1. 서류 심사를 기준으로 오름차순 정렬을 해주었으므로, 서류심사 성적은 무조건 이전 지원자보다 안좋다고 볼 수 있으므로, 면접 성적만을 보면 된다.
    2. 따라서, 이전 지원자들보다 면접 성적이 좋아야만 합격이다. 따라서, 합격한 지원자들의 면접 성적중 가장 좋은 면접 성적보다 좋은지 확인하고, 좋다면 가장 좋은 면접 성적을 갱신하고 합격 카운팅을 해준다.
  3. 전체 합격자의 수를 출력한다.

소스 코드


import sys

input = lambda: sys.stdin.readline().rstrip()
T = int(input())

for _ in range(T):
    N = int(input())
    applicants = [list(map(int, input().split())) for _ in range(N)]
    applicants.sort()
    max_r2 = 999999
    answer = 0
    for _, r2 in applicants:
        if r2 < max_r2:
            max_r2 = r2
            answer += 1

    print(answer)

  

https://programmers.co.kr/learn/courses/30/lessons/92341?language=python3 

 

코딩테스트 연습 - 주차 요금 계산

[180, 5000, 10, 600] ["05:34 5961 IN", "06:00 0000 IN", "06:34 0000 OUT", "07:59 5961 OUT", "07:59 0148 IN", "18:59 0000 IN", "19:09 0148 OUT", "22:59 5961 IN", "23:00 5961 OUT"] [14600, 34400, 5000]

programmers.co.kr

 


풀이 과정


본문이 너무 길어서 내가 이해한게 맞나 자주 확인해보아야 했던 문제 같다.

  1. 시간을 처리할 때는 그냥 분으로 모두 바꿔준 다음에 처리해주는게 체감상 제일 좋다. 따라서 시-분으로 구성되어 있는 형태를 분으로 바꾸어주는 함수를 만들어 준다.
  2. 이후, 입력된 기록에 맞추어서 다음 과정을 반복한다.
    1. 전체 주차 시간을 저장해 둘 딕셔너리를 따로 만들어 둔다.
    2. IN 표시가 되어있는 경우는, 해당 차의 주차 시간을 딕셔너리에 저장한다.
    3. OUT 표시가 되어 있는 경우는, 출차 시간에서 딕셔너리에 저장된 주차 시간을 빼준 값을 전체 주차 시간을 저장해 둘 딕셔너리에 더해 준다. 또한 이 때, OUT이 되었음을 나타내기 위해 값을 -1로 처리해 준다.
  3. 위 과정을 끝내고 나서, OUT 처리가 되지 않은 경우는 23:59에 출차한 것으로 간주하여 처리해 준다.
    • (값이 -1이 아닌 경우)
  4. 마지막으로, 주차 요금을 구해 준다.
    1. 차량 번호순으로 구해주어야 하므로 처음부터 딕셔너리의 키값을 정렬해서 진행하는게 편하다.
    2. 기본 시간보다 작거나 같은 경우는 기본 요금으로 처리해 준다.
    3. 기본 시간보다 크다면 다음 수식을 적용해준다.
      1. 기본 요금 + math.ceil((주차 시간 - 기본 시간) / 단위 시간) * 단위 요금
      2. 올림 기호를 사용하는 법을 모르므로 math.ceil로 표시함..ㅎㅎ

소스 코드


from collections import defaultdict
import math

def htom(s):
    return 60 * int(s[:2]) + int(s[3:])


def solution(fees, records):
    answer = []
    park_time = defaultdict(lambda: 0)
    in_time = defaultdict(lambda: 0)
    for record in records:
        time, number, status = record.split()
        if status == 'IN':
            in_time[number] = htom(time) 
        if status == 'OUT':
            park_time[number] += htom(time) - in_time[number]
            in_time[number] = -1
    
    last_time = htom("23:59")
    default_time, default_fee, per_minute, per_fee = fees
    for key in sorted(in_time.keys()):
        if in_time[key] != -1:
            park_time[key] += last_time - in_time[key]
        if park_time[key] <= default_time:
            answer.append(default_fee)
        else:
            answer.append(default_fee + (
                math.ceil((park_time[key] - default_time) / per_minute)
            ) * per_fee)
    
    
    
    return answer

https://programmers.co.kr/learn/courses/30/lessons/92335

 

코딩테스트 연습 - k진수에서 소수 개수 구하기

문제 설명 양의 정수 n이 주어집니다. 이 숫자를 k진수로 바꿨을 때, 변환된 수 안에 아래 조건에 맞는 소수(Prime number)가 몇 개인지 알아보려 합니다. 0P0처럼 소수 양쪽에 0이 있는 경우 P0처럼 소

programmers.co.kr

 


풀이 과정


  1. 우선 주어진 수를 k진수 문자열로 변환시켜 준다.
    1. k진수 문자열로 변환시키는 방법은 수가 0이 될때까지 k로 나눈 나머지를 앞에 붙여주고, 수를 k로 나누어 주는 방식으로 계산하면 된다.
    2. 2진수는 bin으로 해줄수도 있지만.. k가 3~10이니깐.. 위 방식으로 구해주는게 제일 좋을듯?
  2. 구해준 2진수 문자열에서 조건에 맞는 소수를 구해준다.
    1. 결국 조건들을 보면, 숫자들이 0을 기준으로 split 된다는걸 볼 수 있다.
    2. 이 때, 그냥 "0" 으로 split 해준다면 문자열이 "00"같은 경우에는 빈 문자열도 등장하기 때문에, 정규 표현식을 사용해주는게 좋다. re 모듈을 사용하고, "0+"로 split 해준다
    3. split된 결과 중 소수인 개수를 구해서 리턴시켜 준다.

소스 코드


import math
import re

def convert(n, k):
    cv = ""
    while n != 0:
        cv = str(n % k) + cv
        n //= k
    
    return cv

def check_prime(n):
    if n == 1:
        return False
    
    for i in range(2, int(math.sqrt(n)) + 1):
        if n % i == 0:
            return False
        
    return True

def solution(n, k):
    PATTERN = r"0+"
    answer = 0
    convert_string = convert(n, k)
    st_list = re.split(PATTERN, convert_string)
    
    for st in st_list:
        if st == "":
            continue
        if check_prime(int(st)):
            answer += 1
        
    return answer

https://www.acmicpc.net/problem/2638

 

2638번: 치즈

첫째 줄에는 모눈종이의 크기를 나타내는 두 개의 정수 N, M (5 ≤ N, M ≤ 100)이 주어진다. 그 다음 N개의 줄에는 모눈종이 위의 격자에 치즈가 있는 부분은 1로 표시되고, 치즈가 없는 부분은 0으로

www.acmicpc.net

 


풀이 과정


  1. 전체적으로 3가지 과정을 반복한다.
    1. 치즈가 더이상 남아있지 않은지 체크한다
    2. 외부 공기의 위치들을 따로 구해준다.
      • 이 과정에서 테두리는 무조건 외부 공기로 간주해도 되기 때문에, (0, 0)에서부터 1이 아닌 0을 만나는 경우에 대해서만 BFS를 진행해주면 된다.
    3. 각각의 치즈와 인접한 4개의 칸 중 몇개의 칸이 외부 공기인지 개수를 구하고, 2개 이상이라면 제거해 준다. 이 때, 한번에 제거해 주어야 제거로 인해 다른 치즈가 영향을 받지 않는다.
    4. 시간을 1초 증가시켜 준다.
  2. 치즈가 남아있지 않는다면, 총 몇초가 지났는지 출력시켜 준다.

소스 코드


import sys
from collections import deque

input = lambda: sys.stdin.readline().rstrip()

N, M = map(int, input().split())
matrix = [list(map(int, input().split())) for _ in range(N)]

dx = [-1, 1, 0, 0]
dy = [0, 0, 1, -1]

def get_air_pos():
    air = set()
    air.add((0, 0))
    queue = deque([[0, 0]])
    
    while queue:
        x, y = queue.popleft()
        for i in range(4):
            nx, ny = x + dx[i], y + dy[i]
            if 0 <= nx and nx < N and 0 <= ny and ny < M:
                if (nx, ny) not in air and matrix[nx][ny] == 0:
                    air.add((nx, ny))
                    queue.append([nx, ny])

    return air

def is_empty():
    for i in range(N):
        for j in range(M):
            if matrix[i][j] != 0:
                return False
    return True

def destroy_cheese(air):
    destroy_cheese_pos = []
    for i in range(N):
        for j in range(M):
            if matrix[i][j] == 1:
                air_count = 0
                for k in range(4):
                    x, y = i + dx[k], j + dy[k]
                    if (x, y) in air:
                        air_count += 1
                if air_count >= 2:
                    destroy_cheese_pos.append([i, j])
                    
    for x, y in destroy_cheese_pos:
        matrix[x][y] = 0
                            

time = 0
while True:
    if is_empty():
        break
    air = get_air_pos()
    destroy_cheese(air)
    time += 1
    

print(time)

+ Recent posts