์ถ์ฒ:
๊ณต์ ๋ฌธ์, flask ์๋ฒ์ unittest ์ ์ฉํ๊ธฐ, ๊นํํ๋ฐฑ ์ฐธ๊ณ ํ ๋ธ๋ก๊ทธ
๊ณต์๋ฌธ์2 + ๊ณต์repo
django์์๋ unit test์ ๊ฐ๋ ๋ง ์ดํดํ๋ค๋ฉด ํ ์คํธ๋ฅผ ํ๋ ๊ฒ์ด ์ด๋ ต์ง ์์๋ค. ORM์ด ์์์ ๋ฐ์ดํฐ ๋ถ๋ถ์ ์ ์ฒ๋ฆฌํด์คฌ๊ธฐ ๋๋ฌธ์, ๋๋ ๊ทธ์ ๋ฐ์ดํฐ๋ฅผ ๋ง๋ค๊ณ ์ง์ฐ๊ธฐ๋ง ํ๋ฉด ๋๋ค.
ํ์ง๋ง flask์ ๊ฒฝ์ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค connection์ ์ง์ ํ๋ค๋ณด๋ ๋ดค์ ๋ ๋ฐ๋ก ์ดํด๊ฐ ๊ฐ์ง ์์๋ค. ๊ฐ ORM์ด ๋ ์ํด ๋ค์์ ๋ญ ํด์ฃผ๊ณ ์์๋์ง ๋ชฐ๋๊ธฐ ๋๋ฌธ์(...) ๋ ๊ทธ๋ฌ๋ ๊ฒ ๊ฐ๋ค.
์ฐ์ ๊นํํ๋ฐฑ์ ์ฐธ๊ณ ํ ๋ธ๋ก๊ทธ๋ฅผ ์ฐธ๊ณ ํ๊ณ , django์์ python ๋ด์ฅ ๋ชจ๋์ ์ฌ์ฉํ ๊ฒ๊ณผ ๋ฌ๋ฆฌ ์ด๋ฒ์๋ pytest๋ฅผ ์จ๋ณด์๋ค.
flaks ๊ณต์ repo๋ฅผ ๋ณด๋, ๋ฃจํธ์ test ๋๋ ํ ๋ฆฌ๋ฅผ ์์ฑํ๊ณ , ๊ทธ ์์ ๊ฐ ์ฑ์ ๋ํ ํ ์คํธ ํ์ผ์ ์์ฑํ๋๋ก ๋์ด์์๋ค. unit test๋ฅผ ์์ฑํ ํ์ผ์ ๊ฒฝ์ฐ ๋ฐ๋์ test๋ก ์์ํด์ผ ํ๋ค. ์๋ ์ด๋ฏธ์ง๋ฅผ ๋ณด๋ฉด ํ์ผ ์ด ๋ค test ์ธ๋๋ฐ๋ก ์์๋๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
๋จผ์ ์์ ํ ํ ์คํธ๋ฅผ ์ํด ํ ์คํธ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์์ฑํ๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ๊ด๋ จ ์ ๋ณด๋ฅผ ๋ด์ config.py์ test ์ ์ ๋ฅผ ๋ง๋ค๊ณ , mysql connector๋ก ํ ์คํธ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ฐ๊ฒฐํ๋ค. ์ฌ๊ธฐ์ ์ค์ํ ๊ฒ์ ํ ์คํธ ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ์ค์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์คํค๋ง๊ฐ ๋๊ธฐํ๋์ด ์์ด์ผ ํ๋ค๋ ์ ์ด๋ค.
test_db = {
'user': 'test',
'password': '1234',
'host': 'localhost',
'port': 3306,
'database': 'test_db'
}
test_config = {
"DB_URL": f"mysql+mysqlconnector://{test_db['user']}
:{test_db['password']}@{test_db['host']}
:{test_db['port']}
/{test_db['database']}?charset=utf8"
}
flask์์๋ ๋ด๊ฐ ๋ง๋ ์๋ํฌ์ธํธ๋ฅผ ํ ์คํธํ ์ ์๋ test_client๋ผ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค. pytest.fixture ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํ๋ฉด ํด๋น ์๋ํฌ์ธํธ๋ฅผ ๊ฐ์ง๊ณ ์๋ ํจ์์ ๋ฆฌํด ๊ฐ์ ์ธ์๋ก ๋ฃ์ด์ฃผ๊ฒ ๋๋ค.
@pytest.fixture
def api():
app = create_app(config.test_config)
app.config['TESTING'] = True # unit test์์ ์๋ฌ๋ฅผ ์บ์นํ๊ธฐ ์ํด, ์์ธ๊ฐ ํธ๋ค๋ง ๋์ง ์๋๋ก ์ค์ ํด์ฃผ๋ ๊ฒ
api = app.test_client()
return api
You must set app.testing = True in order for the exceptions to propagate to the test client. Otherwise, the exception will be handled by the application (not visible to the test client) and the only indication of an AssertionError or other exception will be a 500 status code response to the test client.
๊นํํ๋ฐฑ ์ฑ ์์ ๋ฏธ๋ํฐ๋ฅผ ๋ง๋ค๊ฒ ๋๋๋ฐ, ์๋๋ ๊ทธ ๋ฏธ๋ํฐ์์ ํธ์์ ์์ฑํ๋ api์ test ์ฝ๋๋ค.
๊ธฐ๋ณธ์ ์ธ ๋์ ์๋ฆฌ๋ ๋ด๊ฐ ๋ง๋ ์๋ํฌ์ธํธ์ return ๊ฐ๊ณผ test db์ return ๊ฐ์ ๋ง๋ ๊ฒ์ด ์ผ์นํ๋ ์ง๋ฅผ ํ์ธํ๋ ๊ฒ์ด๋ค.
์ฒซ ๋ฒ์งธ(#tweet)๋ post method๋ฅผ ํ ์คํธํ๋ ๊ฒ์ด๊ณ , db์ ๋ ค ๊ฐ์ธ data์ content_type, header๋ฅผ ๋ง๋ค์ด resp(response) ๋ณ์์ ์ ์ฅํ๋ค. ์ด๋ endpoint /tweet์ response์ ๋ด๊ฐ assert์์ ๊ฐ์ ํ status code๊ฐ ์ผ์นํ๋์ง ํ์ธํ๊ฒ ๋๋ค.
๋ ๋ฒ์งธ(#tweet check)๋ get method ํ ์คํธ๋ค. timeline/1 ํ์์ endpoint์์ response๋ฅผ ๊ฐ์ ธ์ค๊ณ , json ํ์์ผ๋ก ๋ง๋ ๋ค. ๊ทธ๋ฆฌ๊ณ ๋ด๊ฐ ์ค์ db๋ฅผ ํตํด ๋ฐ์์ผ ํ๋ ๊ฐ๊ณผ ์ฝ๋๋ฅผ assertํ๊ฒ ๋๋ ๋ฐฉ์์ด๋ค.
# tweet
resp = api.post(
"/tweet",
data=json.dumps({"tweet": "Hello World"}),
content_type="application/json",
headers={"Authorization": access_token}
)
assert resp.status_code == 200
# tweet check
resp = api.get(f"/timeline/{new_user_id}")
tweets = json.loads(resp.data.decode("utf-8"))
assert resp.status_code == 200
assert tweets == {
"user_id": 1,
"timeline" : [
{
"user_id": 1,
"tweet": "Hello World"
}
]
}