TDD on Python with Kata - [1] Poetry + Pytest 환경 설정

sjk·2022년 10월 25일
0

TDD on Python

목록 보기
1/2
post-thumbnail

Introduction

Python에서 TDD (Test Driven Development) 환경을 구성하고, 간단한 TDD Kata를 진행하며 익숙해져 보겠습니다.

In this Series ...

  • 테스트 프레임워크로 Pytest 를 사용합니다.
  • 패키징과 의존성 관리 도구로 Poetry 를 사용합니다.
  • 다양한 환경에서의 테스트와 CI/CD를 위해 Tox 를 사용합니다.
  • 코드는 Github 저장소에서 관리됩니다.
  • CircleCI 를 통해 CI/CD 파이프라인을 관리합니다.

Poetry

pyenvpoetry 가 설치된 상태를 가정하겠습니다. 설치되지 않은 경우 다음 문서를 참고하세요.
wikidocs - pyenv
poetry official

poetry를 이용해서 새로운 프로젝트를 시작하겠습니다.

-> % poetry new tdd-kata
Created package tdd_kata in tdd-kata

-> % cd tdd-kata && tree
.
├── README.md
├── pyproject.toml
├── tdd_kata
│   └── __init__.py
└── tests
    └── __init__.py

2 directories, 5 files

poetry와 pyproject.toml 파일을 통해 프로젝트를 관리합니다. author와 python version을 제외한 파일의 내용은 다음과 같을 것입니다.

[tool.poetry]
name = "tdd-kata"
version = "0.1.0"
description = ""
authors = ["sjk <18274655+sejkimm@users.noreply.github.com>"]
readme = "README.md"
packages = [{include = "tdd_kata"}]

[tool.poetry.dependencies]
python = "^3.9"


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

poetry를 이용해 Python 패키지 저장소 의 패키지를 설치할 수 있습니다.
--group dev 인자값을 추가하였기 때문에 pytest 패키지는 dev dependency에 추가될 것입니다.

-> % poetry add --group dev pytest        
Using version ^7.1.3 for pytest

Updating dependencies
Resolving dependencies... (0.1s)

Writing lock file

Package operations: 8 installs, 0 updates, 0 removals

  • Installing pyparsing (3.0.9)
  • Installing attrs (22.1.0)
  • Installing iniconfig (1.1.1)
  • Installing packaging (21.3)
  • Installing pluggy (1.0.0)
  • Installing py (1.11.0)
  • Installing tomli (2.0.1)
  • Installing pytest (7.1.3)

다시 한번 pyproject.toml 파일의 내용을 확인해 보겠습니다.

[tool.poetry]
name = "tdd-kata"
version = "0.1.0"
description = ""
authors = ["sjk <18274655+sejkimm@users.noreply.github.com>"]
readme = "README.md"
packages = [{include = "tdd_kata"}]

[tool.poetry.dependencies]
python = "^3.9"


[tool.poetry.group.dev.dependencies]
pytest = "^7.1.3"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.poetry.group.dev.dependencies] 에 pytest가 추가된 것을 확인할 수 있습니다.
pyproject.tomlpoetry.lock 파일이 있으면, 새로운 Directory에서 다음 명령어로 동일한 패키지 의존성 환경을 만들 수 있습니다.

poetry install

위 명령어는 pyproject.toml 파일을 파싱하여 의존성 환경을 구성합니다.
poetry.lock 파일이 존재하면 해당 파일에 작성된 패키지와 동일한 버전을 설치하고, poetry.lock 파일이 없다면 최신 버전으로 설치한 뒤 poetry.lock 파일을 생성합니다.

환경을 확인하기 위해 테스트 파일을 생성하고 간단한 테스트를 작성해 보겠습니다.

# /tests/test_simple.py

def test_add():
    assert 1 + 2 == 3

프로젝트의 root 디렉토리에서 다음 명령어를 실행하여 poetry 환경에서 pytest 명령을 실행하고 결과를 확인할 수 있습니다.

-> % poetry run pytest   
=============================== test session starts ================================
platform darwin -- Python 3.9.13, pytest-7.1.3, pluggy-1.0.0
rootdir: /Users/sejkimm/dev/project/tdd-kata
collected 1 item                                                                   

tests/test_simple.py .                                                       [100%]

================================ 1 passed in 0.01s =================================

TDD Kata

이 글에서 TDD로 해결하고자 하는 예제는 다음 페이지에 소개되어 있습니다.
osherove.com/tdd-kata-1

테스트는 Arrange/Act/Assert (AAA) 패턴을 최대한 적용하여 작성하고자 합니다.
Bill Wake - 3A: Arrange, Act, Assert

만들고자 하는 어플리케이션은 String Calculator 입니다.
시작 전 주의사항은 다음과 같습니다.

  • 다음 Task의 내용을 미리 읽지 마세요.
  • 한번에 하나의 Task를 처리하세요. 점진적인 작업을 통해 배우는 것이 중요합니다.
  • 올바른 입력값에 대해서만 테스트하세요. 이 Kata는 잘못된 입력에 대해서까지 테스트할 필요는 없습니다.

Task 1

다음 Method를 가진 문자열 계산기를 만들 것입니다.

———————————————
int Add(string numbers)
———————————————

Add() Method는 comma(,)들로 나누어진 최대 두 개의 숫자 문자열을 입력받아 그들의 합을 반환합니다.

입력 예시는 "", "1", "1,2" 입니다. (빈 문자열에 대해서는 0을 반환합니다.)

힌트:

  • 가장 간단한 예시인 빈 문자열에서 테스트 케이스를 만들고 하나, 두개의 숫자 입력으로 이동하세요.
  • 평소에는 생각하지 못했을 최대한 간단한 테스트도 작성하도록 노력하세요.
  • 각 테스트를 통과한 이후에는 리팩토링을 진행해야 합니다.

테스트 대상이 되는 빈 클래스 파일을 생성하고, 첫 번째 Task를 시작하겠습니다.

# /tdd-kata/string_calculator.py

class StringCalculator(object):
   def __init__(self) -> None:
        pass
        
    def Add(self):
        pass

테스트 파일을 생성하고 첫 번째 테스트를 작성합니다.

# /tests/test_string_calculator.py

from tdd_kata.string_calculator import StringCalculator


def test_add_empty_string_expect_0():
    string_calculator = StringCalculator()
    
    input_string = ""
    result = string_calculator.Add(input_string)

    assert result == 0

테스트를 실행합니다.

-> % poetry run pytest
=============================== test session starts ================================
platform darwin -- Python 3.9.13, pytest-7.1.3, pluggy-1.0.0
rootdir: /Users/sejkimm/dev/project/tdd-kata
collected 1 item                                                                   

tests/test_string_calculator.py F                                            [100%]

===================================== FAILURES =====================================
__________________________ test_add_empty_string_expect_0 __________________________

    def test_add_empty_string_expect_0():
        input_string = ""
        result = StringCalculator.Add(input_string)
    
>       assert result == 0
E       assert None == 0

tests/test_string_calculator.py:21: AssertionError
============================= short test summary info ==============================
FAILED tests/test_string_calculator.py::test_add_empty_string_expect_0 - assert N...
================================ 1 failed in 0.02s =================================

RED (failed)를 마주치는데 성공했습니다.
Add() 함수의 로직을 수정하여 RED를 GREEN (pass)로 변경해 보겠습니다.

# /tdd-kata/string_calculator.py

class StringCalculator(object):
    def __init__(self) -> None:
        pass

    def Add(self, numbers: str):
        try:
            result = int(numbers)
        except ValueError:
            result = 0
        return result
-> % poetry run pytest
=============================== test session starts ================================
platform darwin -- Python 3.9.13, pytest-7.1.3, pluggy-1.0.0
rootdir: /Users/sejkimm/dev/project/tdd-kata
collected 1 item                                                                   

tests/test_string_calculator.py .                                            [100%]

================================ 1 passed in 0.01s =================================

첫 번째 테스트에 대한 GREEN을 확인하였습니다.

Next Article

Task 1번의 나머지 구현사항부터 차례로 진행하며 tox를 이용해 여러 Python 인터프리터 환경에서의 테스트를 진행하도록 하겠습니다.

profile
Backend, Infra, ML Engineering

0개의 댓글