[백준] 2504번 문제풀이

Rally·2024년 2월 22일

목적

TDD 개념을 익히기 위해 백준문제 2504문제를 풀어보았다.

TDD란 무엇인가?

TDD(Test Driven Development)의 약자로 '테스트 주도 개발'을 의미한다.
작은 단위의 테스트케이스를 작성한 다음, 해당 테스트를 통과하는 코드를 작성하고 이를 통과하는 코드를 추가하는 소프트웨어 개발 방법이다.

TDD의 장점

  • 향상된 코드 품질: 더 나은 디자인, 더 깨끗하고 안정적인 코드로 이어진다.
  • 초기 버그 감지: 버그는 개발 주기 초기에 식별되고 수정된다.

TDD의 단점

  • 개발시간 증가: 테스트와 개발을 동시에 이루어지므로 개발프로세스가 느려질 수 있다.
  • 테스트케이스 관리: 시스템이 발전함에 따라 테스트케이스가 많아질 수 있다.

TDD 개발주기


TDD 주기는 크게 3단계로 이루어져있다.

  • Red: 실패하는 작은 단위의 테스트 케이스를 작성한다.
  • Green: 테스트 케이스를 성공시키기 위한 실제 코드를 작성한다.
  • Blue: 테스트 통과 코드를 중복코드 제거, 추상화 등 리팩토링을 수행한다.
    위 3단계를 테스트 단위가 완료될 때까지 반복한다.

TDD 적용해보기

아래 코드는 백준문제 2504번을 파이썬(3.0 이상)에서 TDD를 적용한 것이다.

테스트케이스 작성

import unittest

def calculateBrackets(s: str) -> int:
    pass

class TestBracketCalculate(unittest.TestCase):
    def test_valid_brackets(self):
        self.assertEqual(calculateBrackets("()"), 2)
        self.assertEqual(calculateBrackets("[]"), 3)
        self.assertEqual(calculateBrackets("(()[[]])([])"), 28)

    def test_invalid_brackets(self):
        self.assertEqual(calculateBrackets(")("), 0)
        self.assertEqual(calculateBrackets("([)"), 0)
        self.assertEqual(calculateBrackets("(()"), 0)
        self.assertEqual(calculateBrackets("([)]"), 0)

if __name__ == "__main__":
    unittest.main()

unittest 라이브러리를 import해서 TestCase 클래스를 상속한다. 이 때 테스트케이스는 test_valid_brackets, test_invalid_brackets이다. unittest는 테스트 메소드명에 test라는 단어가 들어가있어야 테스트케이스로 간주한다. assertEqual는 서로 결과값이 같다고 생각되어지는 2개의 인자를 검사하며 여기서는 calculateBrackets함수에 대한 결과값을 검사한다. 결과는 .......

당연히 실패.

세부기능 구현

calculateBrackets함수를 세부적으로 구현한 것이다. 함수 설명은 주석으로 대체한다.

def calculateBrackets(s: str) -> int:
    stack = []  # 스택 초기화
    pair = {')': '(', ']': '['}  # 괄호의 짝을 나타내는 딕셔너리
    score = {')': 2, ']': 3}  # 괄호 종류에 따른 점수
    temp = 1  # 현재 깊이에서의 임시 점수
    result = 0  # 최종 결과값

    for i, char in enumerate(s):  # 문자열을 순회하며 각 문자(char)와 인덱스(i)에 대해 반복
        if char in "([":
            stack.append(char)  # 여는 괄호는 스택에 추가
            # 괄호에 따른 점수를 temp에 곱함 (2배 또는 3배)
            temp *= score.get(char, 1) * 2 if char == '(' else 3
        elif char in ")]":
            # 스택이 비었거나 스택의 마지막 요소가 해당 닫는 괄호와 짝이 맞지 않는 경우 0 반환
            if not stack or stack[-1] != pair[char]:
                return 0
            # 직전 문자가 해당하는 여는 괄호인 경우, 결과값에 현재 임시 점수(temp) 추가
            if s[i - 1] == pair[char]:
                result += temp
            stack.pop()  # 스택에서 괄호 제거
            temp //= score[char]  # temp를 해당 괄호의 점수로 나눔

    # 모든 순회가 끝난 후 스택이 비어있지 않으면 0 반환, 그렇지 않으면 계산된 결과 반환
    return result if not stack else 0

테스트 실행

테스트 결과 성공. 즉 기능적으로 정상. 이젠 코드 개선에 집중하면 된다. 만약 실패했다면 실패한 테스트케이스를 바탕으로 재검토하면 된다.

결론

  • 테스트와 개발을 같이 진행하니 코드 신뢰도가 높아짐을 느낀다.
  • 한 가지 함수(기능)에 얼마나 많은 테스트케이스를 만들어서 검증해야하는지는 의문이다.

Reference

profile
새로운 것을 배우고 즐기며, 그 안에서 성장하길 원합니다.

0개의 댓글