티스토리 뷰

개발/python

다시쓰는 Flask unittest (하편)

Jaeyeon Baek 2019. 6. 10. 23:31

http://flask.pocoo.org/docs/1.0/

이전 편 : 다시쓰는 Flask unittest (상편)

 

앞서 우리는 Flask의 기본 예제코드를 pytest를 통해 테스트해봤습니다. 몇 몇 분들은 아마 눈치를 채셨을지 모르겠지만 사실 앞에서 테스트한 내용은 함수 단위의 테스트지 실제 Flask 서버와 무관합니다. /hello, /world 와 같은 API를 테스트한다고는 했지만 사실상 hello(), world() 함수를 호출하고 끝냈으니까요. 이번에는 조금 더 현실적인 Flask 코드를 놓고 unittest를 진행하는 방법에 대해서 알아보도록 하겠습니다. 일단 아래처럼 미리 준비된 코드를 가져와봤습니다.

#!/usr/bin/env python
# coding=utf8
# content of my_flask.py

# Restful API
from flask import Flask
from flask_restful import reqparse, Resource, Api

parser = reqparse.RequestParser()
parser.add_argument('id')

class HealthCheck(Resource):
    def post(self):
        args = parser.parse_args()
        if args == None or args.get("id", None) == None:
            return {"status": "error"}
        else:
            return {'status': 'ok'}
    def get(self):
        return {'status': 'error'}

# Set API
app = Flask(__name__)
api = Api(app)

api.add_resource(HealthCheck, '/')

if __name__ == "__main__":
    app.run(host='0.0.0.0', debug=True, port = 5000)

코드 내용은 설명이 필요 없을정도로 심플한데요, 헬스체크 모듈을 만들고, POST method를 등록해놨습니다. 그리고 데이터에 id 필드가 있으면 ok, 없으면 error 상태를 리턴합니다. 이제 이 코드를 대상으로 unittest를 진행하면 되는데 테스트에서는 여러가지 상황을 두고 테스트 할 수 있겠습니다. 테스트 케이스로는 예를들어 post 방식이 아닐수도 있고, id 필드가 없을수도 있겠죠. 그리고 정상적으로 id 값이 설정되어 있는 경우도 있을겁니다. 이런 모든 상황에 대한 테스트 코드를 작성하는게 우리의 목표입니다. 앞에서 살펴봤던것처럼 파일의 이름은 test_ 로 시작해야 pytest가 인식을 하니 test_flask.py 로 지정했습니다. 그리고 코드 내용은 아래와 같습니다.

#!/usr/bin/env python
# coding=utf8
# content of test_flask.py

"""
====================================
 :mod: Test case
====================================
.. module author:: Jaeyeon Baek <oops.jybaek@gmail.com>
"""

import pytest
import json

import sys
sys.path.append(".")
from my_flask import app

@pytest.fixture
def client():
    client = app.test_client()

    yield client

def test_healthcheck(client):
    rv = client.post('/', data=dict(
        id=3367055,
    ), follow_redirects=True)
    assert 'ok' in json.loads(rv.data.decode("utf-8"))['status']

    rv = client.post('/', data=dict(
    ), follow_redirects=True)
    assert 'error' in json.loads(rv.data.decode("utf-8"))['status']

    rv = client.get('/', data=dict(
    ), follow_redirects=True)
    assert 'error' in json.loads(rv.data.decode("utf-8"))['status']

@pytest.fixture로 client를 구현해놨고, test_healthcheck 함수에 인자로 client를 넘겼습니다. 이제 테스트 모듈이 동작할 때 my_flask에서 선언했던 app을 가져와서 test_client() 모듈을 호출하게 될겁니다. 아래 실행결과를 살펴보시죠.

$ pytest 
========================== test session starts ===========================
platform linux -- Python 3.6.5, pytest-4.6.2, py-1.8.0, pluggy-0.12.0
rootdir: /Users/jybaek/work/10_study/flask, inifile:
collected 1 item                                                         

test_flask.py .                                           [100%]

======================== 1 passed in 0.07 seconds ========================

다시 위에 코드를 살펴보면 실제 테스트 케이스는 세 가지인데 결과는 한 개로 나타나는 것을 확인할 수 있습니다. 이것은 pytest가 함수 단위로 동작하기 때문인데요, 세부적으로 나눠서 테스트 하기 위해서는 함수를 분리해서 사용할 수 있겠습니다.

def test_healthcheck_ok(client):
    rv = client.post('/', data=dict(
        id=3367055,
    ), follow_redirects=True)
    assert 'ok' in json.loads(rv.data.decode("utf-8"))['status']

def test_healthcheck_error(client):
    rv = client.post('/', data=dict(
    ), follow_redirects=True)
    assert 'error' in json.loads(rv.data.decode("utf-8"))['status']

def test_healthcheck_get(client):
    rv = client.get('/', data=dict(
    ), follow_redirects=True)
    assert 'error' in json.loads(rv.data.decode("utf-8"))['status']

위와 같이 테스트 단위로 함수를 나누고 pytest를 진행했을 때는 아래와 같이 세부적인 단위를 직접 확인할 수 있다는 장점이 있지만 관리해야 하는 테스트 케이스가 너무 많아진다는 것은 인적 리소스 낭비로 이어질 수 있습니다.

$ pytest -v
========================== test session starts ===========================
platform linux -- Python 3.6.5, pytest-4.6.2, py-1.8.0, pluggy-0.12.0 -- /Users/jybaek/anaconda3/envs/python36/bin/python
cachedir: .pytest_cache
rootdir: /Users/jybaek/work/10_study/flask, inifile:
collected 3 items                                                        

test_flask.py::test_healthcheck_ok PASSED                 [ 33%]
test_flask.py::test_healthcheck_error PASSED              [ 66%]
test_flask.py::test_healthcheck_get PASSED                [100%]

======================== 3 passed in 0.07 seconds ========================

특별히 어느 단위까지 나눠서 테스트를 진행해야 한다는 표준은 없으니 조직이나 프로젝트의 특성이나 규칙에 맞게 진행하면 되겠습니다. 조금 더 구체적인 내용을 확인해보고 싶으신분들은 공식 홈페이지의 예제를 참고해보세요.

 

지금까지 Flask와 unittest를 묶어서 살펴봤는데요, 여기까지 다룬 내용을 기반으로 한다면 어떤 복잡한 시나리오의 Flask 서버도 unittest 할 수 있겠습니다. Flask를 테스트하시려는 어느분께는 도움이 되셨기를 바랍니다. 

댓글
최근에 올라온 글
최근에 달린 댓글
글 보관함
Total
Today
Yesterday