Github 상의 python 코드를 보면 처음부터 무시무시한 argparse 부분이 등장하는데, 드디어 그것의 의미를 알게 되었다. 한마디로 정리하면, “굳이 필터링을 일일히 하지 않아도, 사용자의 입력을 ‘argparse’가 알아서 처리해준다"는 매우 편리헌 기능이었다. 마치 class을 지정하고 상속하는 것과 유사한 맥락이었다.

테스트 주도 개발 test-driven development

  • 소프트웨어 개발 전 테스트 코드 작성
  • 테스트 실행해서 소프트웨어가 실행하지 못하는 태스크 확인
  • 테스트 통과 기준 만족하는 소프트웨어 작성
  • 테스트 결과 확인
  • 추가 테스트 진행

2강을 활용했던 방법

책의 내용을 그대로 따라하기 보다는 직접 해결해보고 싶어서, 먼저 코드만 가지고 문제풀이를 진행했다. MAC이나 Linux의 경우 requirements.txt안의 파일들을 모두 설치해두었다면, make test라는 간단한 문장으로 pytest -xv test.py을 대신할 수 있다. 테스트 결과가 나오고 어디에서 문제가 생겼는지를 확인할 수 있는데, 이런 힌트들을 가지고 본인의 코드를 수정할 수 있다는 책의 구성이 마음에 들었다.

Solution 1

    if pos_arg[0].lower() in 'aeiou':
        pos_arg = 'an ' + pos_arg
    else:
        pos_arg = 'a ' + pos_arg

    print(f'Ahoy, Captain, {pos_arg} off the larboard bow!')

Solution 2 - if 표현식을 이용한 한줄컷 방법

    article = 'an' if pos_arg[0].lower() in 'aeiou' else 'a'

    print(f'Ahoy, Captain, {article} {pos_arg} off the larboard bow!')

중간에 들었던 의문점

  1. 지금이야 책에서 test.py 파일을 제공해주니까 오류를 편리하게 확인할 수 있었는데, 만약 내가 직접 밑바닥부터 개발을 해야한다면, 그때는 어떻게 해야할까? 즉, 내가 직접 테스트 코드를 어떻게 짤까? 라는 의문점이 들었다.
  2. 이를 해결하기 위해서 test.py 파일에 들어가서 구조를 분석해보았다. 코드의 앞부분에는 테스트하고자하는 파일명, 테스트할 예시들을 추가해두고, 뒤에서는 각 조건에 따라서 테스트를 진행하는 함수들이 존재했다. 도전과제에서는 관사와 단어의 대소문자가 일치하도록 (‘An Octopus’ 또는 ‘an octopus’) 코드를 수정하라고 했는데, 이걸로 테스트 코드 작성을 연습해보기로 했다.

오늘의 코드

crowsnest.py

#!/usr/bin/env python3
"""
Author : junha <junha@localhost>
Date   : 2024-02-20
Purpose: Rock the Casbah
"""

import argparse


# --------------------------------------------------
def get_args():
    """Get command-line arguments"""

    parser = argparse.ArgumentParser(
        description='Rock the Casbah',
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)

    parser.add_argument('word',
                        metavar='word',
                        help='A word')

    parser.add_argument('-s',
                        '--side',
                        metavar='str',
                        default='larboard',
                        choices=['larboard', 'starboard'])

    return parser.parse_args()


# --------------------------------------------------
def main():
    """Make a jazz noise here"""

    args = get_args()
    word = args.word
    side = args.side

    if word[0] in 'aeiou':
        article = 'an'
    elif word[0] in 'AEIOU':
        article = 'AN'
    else:
        article = 'A' if word[0].isupper() else 'a'

    print(f'Ahoy, Captain, {article} {word} off the {side} bow!')


# --------------------------------------------------
if __name__ == '__main__':
    main()

test.py

#!/usr/bin/env python3
"""tests for crowsnest.py"""

import os
from subprocess import getstatusoutput, getoutput

prg = './crowsnest.py'
consonant_words = [
    'brigantine', 'clipper', 'dreadnought', 'frigate', 'galleon', 'haddock',
    'junk', 'ketch', 'longboat', 'mullet', 'narwhal', 'porpoise', 'quay',
    'regatta', 'submarine', 'tanker', 'vessel', 'whale', 'xebec', 'yatch',
    'zebrafish'
]
vowel_words = ['aviso', 'eel', 'iceberg', 'octopus', 'upbound']
template = 'Ahoy, Captain, {} {} off the larboard bow!'


# --------------------------------------------------
def test_exists():
    """exists"""

    assert os.path.isfile(prg)


# --------------------------------------------------
def test_usage():
    """usage"""

    for flag in ['-h', '--help']:
        rv, out = getstatusoutput(f'{prg} {flag}')
        assert rv == 0
        assert out.lower().startswith('usage')


# --------------------------------------------------
def test_consonant():
    """brigantine -> a brigantine"""

    for word in consonant_words:
        out = getoutput(f'{prg} {word}')
        assert out.strip() == template.format('a', word)


# --------------------------------------------------
def test_consonant_upper():
    """brigantine -> a Brigatine"""

    for word in consonant_words:
        out = getoutput(f'{prg} {word.title()}')
        assert out.strip() == template.format('A', word.title())


# --------------------------------------------------
def test_vowel():
    """octopus -> an octopus"""

    for word in vowel_words:
        out = getoutput(f'{prg} {word}')
        assert out.strip() == template.format('an', word)


# --------------------------------------------------
def test_vowel_upper():
    """octopus -> an Octopus"""

    for word in vowel_words:
        out = getoutput(f'{prg} {word.upper()}')
        assert out.strip() == template.format('AN', word.upper())