python testing for flask
TRANSCRIPT
Unit testing
순서와 상관없이 독립적으로 실행 가능한 테스트
보통 컴포넌트나 모듈 단위을 �상으로 테스트
Functional testing
결과 값이 요구사항�로 나왔는지 확인
일종의 시나리오 테스트로 요구사항을 검증하는데 사용
Integration testing
단위 테스트를 통과한 기능이 정상적으로 동작하는지 검증
통합 모듈의 기능, 모듈 간의 인터페이스 검증
테스트 시나리오를 바탕으로한 실행방법과 예상결과를 정의 후 테스트
Friskweb API
회원가입 / 로그인JWT 인증 토큰 관련 테스트
서비스
리스트 받아오기, 디테일 읽어오기
구매
인증리스트 받아오기
각 인증 진행 테스트 (FB, KFTC)
구매 완료 확인
마이페이지
나의 인증현황 받아오기
주문내역 받아오기, 보고서 보기
회원탈퇴
기존의 테스트 코드
class AppTest(unittest.TestCase): def setUp(self): self.app = app.test_client() self.access_token = self.login()
def login(self): # 올바른 계정으로 로그인 시도 p = self.app.post('/signin', data={'id': 'ultratest' result = json.loads(p.data) self.assertEqual(result['e_msg'], ERROR.OK)
return result['access_token']
def test_signin(self): # 잘못된 아이디로 로그인 시도 p = self.app.post('/signin', data={'id': 'thereisnoidlikethis' result = json.loads(p.data) self.assertEqual(result['e_msg'], ERROR.ID_NOT_EXISTS)
기존 테스트 코드의 한계
test_back.py에 일괄적으로 때려박고 있음
각 모듈별로 정의된 기능을 따로 테스트하기 힘듬
테스트 시 출력되는 문구가 불친절해서 assert_helper를 또 만듬
테스트 모듈의 재사용이 힘듬
일부 모듈에 test fixture가 필요한 경우,클래스를 또 만들어서 setup(), teardown()을 지정
test/run.py를 파이참으로 실행해야 가능
pytestPyPy 팀이 meta‑tracing JIT 컴파일러라는 매우 복잡한 소프트웨어를 테스트하기 위해 직접 만들어온 프레임워크 (기존 테스팅 프레임워크들이 제�로만족을 못했기 때문에)
기능이 많고 개발이 매우 활성화 되어 있음
속도가 빠르고 병렬처리 가능
모듈화된 test fixture
assert rewriting
테스트 케이스 매개변수화
unittest, doctest도 함께 지원
간단히 비교해보자 ‑ unittest
import unittest
class TruthTest(unittest.TestCase): def testTrue(self): assertEqual(True, 1)
def testFalse(self): assertEqual(False, 0)
if __name__ == '__main__':unittest.main()
unittest를 import 해야한다
쓸데없이 assertEqual 함수를 권고한다
pytest
pytest tests/test_app.pypytest backend/pytest backend/b2cpytest
test_, 또는 _test가 붙은 파이썬 파일을 알아서 읽는다.
setup.py # your setuptools Python package metadatamypkg/ __init__.py appmodule.py ... test/ test_app.py ...
pytest fixtures
# 모듈 내에서 사용하도록 설정@pytest.fixture(scope="module")
# 세션에서 사용하도록 설정@pytest.fixture(scope="session")
unittest의 Setup(), Teardown() function과 같은 역할
테스트의 전처리, 후처리 작업을 수행함
예를 들면, DB Connection
pytest fixtures
import pytest
@pytest.fixturedef smtp(): import smtplib return smtplib.SMTP("merlinux.eu")
def test_ehlo(smtp): response, msg = smtp.ehlo() assert response == 250 assert 0 # for demo purposes
fixture function을 decorator를 통해 등록하고 사용 가능.
pytest mark
@pytest.mark.skip(reason="test unnecessary")def test_the_unknown(): ...
각 테스트 모듈에 마킹(?) 할 수 있다.이렇게 하면 실패해도 그냥 스킵하고 넘어간다!
@pytest.mark.xfail(run=False)def test_function(): ...
당연히 실패할 테스트를 작성하고 xfail() 이라고 마킹!http://doc.pytest.org/en/latest/skipping.html
pytest monkeypatching
몽키패치란?
일반적인 의미는 런타임에서 모듈, 변수를 변경하는 것을 의미
테스트환경에서 몽키패치는?
올바른 동작을 보장하기 위해 오류가 발생하는 외부 데이터 소스에 �한 데이터 호출을 테스트해야할 때 사용 (전역설정, 네트워크 액세스 등)주로 파이썬과 같은 동적 프로그래밍 언어에서 사용
Application Factory Pattern
def create_app(config_filename): app = Flask(__name__) configure_app(app)
from yourapplication.model import db db.init_app(app)
from yourapplication.views.admin import admin from yourapplication.views.frontend import frontend app.register_blueprint(admin) app.register_blueprint(frontend)
return app
fixture에 등록하여 구조적으로 테스트 가능
Configuration
from config import configure_app
app = Flask(__name__)configure_app(app, 'develop')
class Config(object): DEBUG = False TESTING = False DATABASE_URI = 'sqlite://:memory:'
class ProductionConfig(Config): DATABASE_URI = 'mysql://user@localhost/foo'
class DevelopmentConfig(Config): DEBUG = True
class TestingConfig(Config): TESTING = True
Configuration
config = { "default": "config.DevelopmentConfig", "development": "config.DevelopmentConfig", "production": "config.ProductionConfig", "testing": "config.TestingConfig"}
def configure_app(app, settings): app.config.from_object(config[settings])
개발 / 프로덕션 / 테스트 환경을 마음�로 설정 가능
Exception / Error handling
모든 메세지를 error.py에 하드코딩하는 것이 아니라에러코드에 �한 메세지로 인터페이스화
def bad_request(message): response = jsonify({'status': 'bad request', 'message' response.status_code = 400 return response
def unauthorized(message): response = jsonify({'status': 'unauthorized', 'message' response.status_code = 401 return response def not_found(message): response = jsonify({'status': 'not found', 'message': message}) response.status_code = 404 return response