살다보면 이래저래 시간이 없다는 핑계로 그냥 넘어가자하는 순간들이 많다.
하지만 넘기고 나서 계속해서 신경 쓰이고 불편해서 '아...그냥 그때라도 할걸..' 하는 순간들이 생각보다 많은데 고것이 나에게는 Test다. 바쁘다..진도도 나가고 싶고 문서도 만들고 싶고 Validation 라이브러리도 적용하고 싶다. 하지만 그런 자유를 얻으려면 반드시 테스트가 필요하다. 우리는 할 수 있다. 테스트를 하자.
flask로 user CR 만들기
sudo apt-get install python3-venv #설치
python3 -m venv venv #생성
. venv/bin/activate #활성화
이쯤되면 외울때도 됫겠다.
pip install flask
pip install sqlalchemy
pip install mysqlclient
# in main.py
from flask import Flask, jsonify, request
from flask.views import MethodView
from sqlalchemy import create_engine, text
import config
def create_app(test_config=None):
app = Flask(__name__)
if test_config:
app.config.update(test_config)
database = create_engine(app.config['DB_URL'], encoding = 'utf-8', max_overflow = 0)
# 뷰
class UserListView(MethodView):
def __init__(self, database):
self.db = database
def get(self):
users = self.db.execute("SELECT * FROM users")
return jsonify({'users': [dict(row) for row in users] }), 200
# 라우팅
app.add_url_rule('/user', view_func=UserListView.as_view('user',db))
return app
if __name__ == '__main__':
app = create_app()
app.run(host='0.0.0.0', debug=True, port=8000)
간단하게 sqlachemy를 이용해서 유저정보를 작성하는 앱을 만들었다.
여기서 테스트 시 test_config를 통해 DB_URL를 TEST용 DB로 바꿔 줄 것이다.
테스트를 작성하기 위해서는 다음을 고려해야한다.
# in test_main.py
...
import pytest
from main import create_app
from sqlalchemy import create_engine, text
import config
TEST_CONFIG = {
'TESTING': True,
'DB_URL': config.TEST_DB_URL
}
@pytest.fixture(scope='session') # 테스트 실행시 한번만 실행
def app():
app = create_app(TEST_CONFIG)
return app
@pytest.fixture(scope='session')
def db():
db = create_engine(TEST_CONFIG['DB_URL'], encoding = 'utf-8', max_overflow = 0)
return db
@pytest.fixture # 매 테스트 실행 마다 실행
def client(app, db):
db_file(db, 'test.sql')
client = app.test_client()
return client
...
여기서 create_app에 설정해둔 TEST_CONFIG를 넣어주면서 테스트용 DB에서 테스트가 진행될 수 있도록 바꾸었다.
또한 db_file이라는 파일이라는 함수를 작성하여 'test.sql' 정보를 읽어 테스트 함수마다 초기화되도록 fixture function 단위인 client에 넣어주었다.
- @pytest.fixture
함수형 테스트에서 사전작업이 필요한 부분들을 묶어 줄 수 있다.
아무 설정도 하지않으면 매 테스트 마다 실행된다.- @pytest.fixture(scope='session')
테스트 전반에 한번만 수행된다.
app을 생성하거나 db를 지정하는 동작은 테스트시 한번만 하면되므로session
으로 지정하였다.
test 파일에 다음 항목을 추가해주었다.
UserView를 테스트 하기 위해서
users 테이블를 만들고 한명의 유저를 Insert하는 sql를 넣었다.
-- in test.sql
DROP TABLE users;
CREATE TABLE users
(
`id` INT NOT NULL AUTO_INCREMENT COMMENT 'pk',
`name` VARCHAR(100) NOT NULL COMMENT '유저이름',
PRIMARY KEY (id)
);
INSERT INTO users (id, name) VALUES (1, 'user1');
#mysql 접속
>> source test.sql
>> desc users
이제 실제 UserView를 테스트하는 함수를 작성해보자.
# in test_main.py
...
def test_get_user(client):
response = client.get('/user')
assert response.status_code == 200
assert response.json == { 'users': [{'id': 1, 'name':'user1'}] }
이렇게 하면 test.sql에서 만들어진 user1의 유저를 가져온다.
작성할 것은 다 작성했다. 실행해보자.
pytest -s
테스트가 잘 통과되었다.
DB에 user1이 잘들어왔는지도 확인해보자.
이런 방식으로 테스트DB를 원하는대로 세팅하고 테스트를 진행하면된다.
test_*
파일을 자동으로 읽어온다. 테스트파일은 반드시 test_
로 시작하도록 한다. def test_
로 시작하도록 한다.main.py
# in main.py
from flask import Flask, jsonify, request
from flask.views import MethodView
from sqlalchemy import create_engine, text
import config
def create_app(test_config=None):
app = Flask(__name__)
app.config.from_pyfile("config.py")
if test_config:
app.config.update(test_config)
db = create_engine(app.config['DB_URL'], encoding = 'utf-8', max_overflow = 0)
# 뷰
class UserListView(MethodView):
def __init__(self, database):
self.db = database
def get(self):
users = self.db.execute("SELECT * FROM users")
return jsonify({'users': [dict(row) for row in users] }), 200
# 라우팅
app.add_url_rule('/user', view_func=UserListView.as_view('user',db))
return app
if __name__ == '__main__':
app = create_app()
app.run(host='0.0.0.0', debug=True, port=8000)
tast_main.py
# in test_main.py
import pytest
from main import create_app
from sqlalchemy import create_engine, text
import config
TEST_CONFIG = {
'TESTING': True,
'DB_URL': config.TEST_DB_URL
}
def db_file(db, filename):
sql_lines = []
with open(filename, 'r') as file_data:
# .sql를 주석을 제외하고 라인별로 분류
sql_lines = [line.strip('\n') for line in file_data if not line.startswith('--') and line.strip('\n')]
with db.connect() as conn:
sql_command = ''
for line in sql_lines:
sql_command += line
# ; 나오면 execute
if sql_command.endswith(';'):
try:
conn.execute(text(sql_command))
except Exception as e:
print('Fail DB Reset!!')
print(e)
return False
finally:
sql_command = ''
return True
@pytest.fixture(scope='session') # 테스트 실행시 한번만 실행
def app():
app = create_app(TEST_CONFIG)
return app
@pytest.fixture(scope='session')
def db():
db = create_engine(TEST_CONFIG['DB_URL'], encoding = 'utf-8', max_overflow = 0)
return db
@pytest.fixture # 매 테스트 실행 마다 실행
def client(app, db):
db_file(db, 'test.sql')
client = app.test_client()
return client
def test_get_user(client):
response = client.get('/user')
assert response.status_code == 200
assert response.json == { 'users': [{'id': 1, 'name':'user1'}] }
config.py
DB = {
'host': 'localhost',
'user': 'root',
'pass': '',
'db': 'my_flask'
}
DB_URL = f'mysql://{DB["user"]}:{DB["pass"]}@{DB["host"]}/{DB["db"]}?charset=utf8'
TEST_DB = 'test'
TEST_DB_URL = f'mysql://{DB["user"]}:{DB["pass"]}@{DB["host"]}/{TEST_DB}?charset=utf8'
test.sql
DROP TABLE users;
CREATE TABLE users
(
`id` INT NOT NULL AUTO_INCREMENT COMMENT 'pk',
`name` VARCHAR(100) NOT NULL COMMENT '유저이름',
PRIMARY KEY (id)
);
INSERT INTO users (id, name) VALUES (1, 'user1');