Spaces:
Paused
A newer version of the Gradio SDK is available:
5.5.0
ํ ์คํธ[[testing]]
๋จผ์ ๐ค Transformers ๋ชจ๋ธ์ด ์ด๋ป๊ฒ ํ ์คํธ๋๋์ง ์ดํด๋ณด๊ณ , ์๋ก์ด ํ ์คํธ๋ฅผ ์์ฑ ๋ฐ ๊ธฐ์กด ํ ์คํธ๋ฅผ ๊ฐ์ ํ๋ ๋ฐฉ๋ฒ์ ์์๋ด ์๋ค.
์ด ์ ์ฅ์์๋ 2๊ฐ์ ํ ์คํธ ์ค์ํธ๊ฐ ์์ต๋๋ค:
tests
- ์ผ๋ฐ API์ ๋ํ ํ ์คํธexamples
- API์ ์ผ๋ถ๊ฐ ์๋ ๋ค์ํ ์์ฉ ํ๋ก๊ทธ๋จ์ ๋ํ ํ ์คํธ
Transformers ํ ์คํธ ๋ฐฉ๋ฒ[[how-transformers-are-tested]]
PR์ด ์ ์ถ๋๋ฉด 9๊ฐ์ CircleCi ์์ ์ผ๋ก ํ ์คํธ๊ฐ ์งํ๋ฉ๋๋ค. ํด๋น PR์ ๋ํด ์๋ก์ด ์ปค๋ฐ์ด ์์ฑ๋ ๋๋ง๋ค ํ ์คํธ๋ ๋ค์ ์งํ๋ฉ๋๋ค. ์ด ์์ ๋ค์ ์ด config ํ์ผ์ ์ ์๋์ด ์์ผ๋ฏ๋ก ํ์ํ๋ค๋ฉด ์ฌ์ฉ์์ ๋ก์ปฌ ํ๊ฒฝ์์ ๋์ผํ๊ฒ ์ฌํํด ๋ณผ ์ ์์ต๋๋ค.
์ด CI ์์ ์
@slow
ํ ์คํธ๋ฅผ ์คํํ์ง ์์ต๋๋ค.github actions์ ์ํด ์คํ๋๋ ์์ ์ 3๊ฐ์ ๋๋ค:
torch hub integration: torch hub integration์ด ์๋ํ๋์ง ํ์ธํฉ๋๋ค.
self-hosted (push):
main
๋ธ๋์น์์ ์ปค๋ฐ์ด ์ ๋ฐ์ดํธ๋ ๊ฒฝ์ฐ์๋ง GPU๋ฅผ ์ด์ฉํ ๋น ๋ฅธ ํ ์คํธ๋ฅผ ์คํํฉ๋๋ค. ์ด๋src
,tests
,.github
ํด๋ ์ค ํ๋์ ์ฝ๋๊ฐ ์ ๋ฐ์ดํธ๋ ๊ฒฝ์ฐ์๋ง ์คํ๋ฉ๋๋ค. (model card, notebook, ๊ธฐํ ๋ฑ๋ฑ์ ์ถ๊ฐํ ๊ฒฝ์ฐ ์คํ๋์ง ์๋๋ก ํ๊ธฐ ์ํด์์ ๋๋ค)self-hosted runner:
tests
๋ฐexamples
์์ GPU๋ฅผ ์ด์ฉํ ์ผ๋ฐ ํ ์คํธ, ๋๋ฆฐ ํ ์คํธ๋ฅผ ์คํํฉ๋๋ค.
RUN_SLOW=1 pytest tests/
RUN_SLOW=1 pytest examples/
๊ฒฐ๊ณผ๋ ์ฌ๊ธฐ์์ ํ์ธํ ์ ์์ต๋๋ค.
ํ ์คํธ ์คํ[[running-tests]]
์คํํ ํ ์คํธ ์ ํ[[choosing-which-tests-to-run]]
์ด ๋ฌธ์๋ ํ ์คํธ๋ฅผ ์คํํ๋ ๋ค์ํ ๋ฐฉ๋ฒ์ ๋ํด ์์ธํ ์ค๋ช ํฉ๋๋ค. ๋ชจ๋ ๋ด์ฉ์ ์ฝ์ ํ์๋, ๋ ์์ธํ ๋ด์ฉ์ด ํ์ํ๋ค๋ฉด ์ฌ๊ธฐ์์ ํ์ธํ ์ ์์ต๋๋ค.
๋ค์์ ๊ฐ์ฅ ์ ์ฉํ ํ ์คํธ ์คํ ๋ฐฉ๋ฒ ๋ช ๊ฐ์ง์ ๋๋ค.
๋ชจ๋ ์คํ:
pytest
๋๋:
make test
ํ์๋ ๋ค์๊ณผ ๊ฐ์ด ์ ์๋ฉ๋๋ค:
python -m pytest -n auto --dist=loadfile -s -v ./tests/
์์ ๋ช ๋ น์ด๋ pytest์๊ฒ ์๋์ ๋ด์ฉ์ ์ ๋ฌํฉ๋๋ค:
- ์ฌ์ฉ ๊ฐ๋ฅํ CPU ์ฝ์ด ์๋งํผ ํ ์คํธ ํ๋ก์ธ์ค๋ฅผ ์คํํฉ๋๋ค. (RAM์ด ์ถฉ๋ถํ์ง ์๋ค๋ฉด, ํ ์คํธ ํ๋ก์ธ์ค ์๊ฐ ๋๋ฌด ๋ง์ ์ ์์ต๋๋ค!)
- ๋์ผํ ํ์ผ์ ๋ชจ๋ ํ ์คํธ๋ ๋์ผํ ํ ์คํธ ํ๋ก์ธ์ค์์ ์คํ๋์ด์ผ ํฉ๋๋ค.
- ์ถ๋ ฅ์ ์บก์ฒํ์ง ์์ต๋๋ค.
- ์์ธํ ๋ชจ๋๋ก ์คํํฉ๋๋ค.
๋ชจ๋ ํ ์คํธ ๋ชฉ๋ก ๊ฐ์ ธ์ค๊ธฐ[[getting-the-list-of-all-tests]]
ํ ์คํธ ์ค์ํธ์ ๋ชจ๋ ํ ์คํธ:
pytest --collect-only -q
์ง์ ๋ ํ ์คํธ ํ์ผ์ ๋ชจ๋ ํ ์คํธ:
pytest tests/test_optimization.py --collect-only -q
ํน์ ํ ์คํธ ๋ชจ๋ ์คํ[[run-a-specific-test-module]]
๊ฐ๋ณ ํ ์คํธ ๋ชจ๋ ์คํํ๊ธฐ:
pytest tests/utils/test_logging.py
ํน์ ํ ์คํธ ์คํ[[run-specific-tests]]
๋๋ถ๋ถ์ ํ ์คํธ ๋ด๋ถ์์๋ unittest๊ฐ ์ฌ์ฉ๋ฉ๋๋ค. ๋ฐ๋ผ์ ํน์ ํ์ ํ ์คํธ๋ฅผ ์คํํ๋ ค๋ฉด ํด๋น ํ ์คํธ๋ฅผ ํฌํจํ๋ unittest ํด๋์ค์ ์ด๋ฆ์ ์์์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ์ ์ ์์ต๋๋ค:
pytest tests/test_optimization.py::OptimizationTest::test_adam_w
์์ ๋ช ๋ น์ด์ ์๋ฏธ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
tests/test_optimization.py
- ํ ์คํธ๊ฐ ์๋ ํ์ผOptimizationTest
- ํด๋์ค์ ์ด๋ฆtest_adam_w
- ํน์ ํ ์คํธ ํจ์์ ์ด๋ฆ
ํ์ผ์ ์ฌ๋ฌ ํด๋์ค๊ฐ ํฌํจ๋ ๊ฒฝ์ฐ, ํน์ ํด๋์ค์ ํ ์คํธ๋ง ์คํํ ์๋ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
pytest tests/test_optimization.py::OptimizationTest
์ด ๋ช ๋ น์ด๋ ํด๋น ํด๋์ค ๋ด๋ถ์ ๋ชจ๋ ํ ์คํธ๋ฅผ ์คํํฉ๋๋ค.
์์์ ์ธ๊ธํ ๊ฒ์ฒ๋ผ OptimizationTest
ํด๋์ค์ ํฌํจ๋ ํ
์คํธ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
pytest tests/test_optimization.py::OptimizationTest --collect-only -q
ํค์๋ ํํ์์ ์ฌ์ฉํ์ฌ ํ ์คํธ๋ฅผ ์คํํ ์๋ ์์ต๋๋ค.
adam
์ด๋ผ๋ ์ด๋ฆ์ ํฌํจํ๋ ํ
์คํธ๋ง ์คํํ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
pytest -k adam tests/test_optimization.py
๋
ผ๋ฆฌ ์ฐ์ฐ์ and
์ or
๋ฅผ ์ฌ์ฉํ์ฌ ๋ชจ๋ ํค์๋๊ฐ ์ผ์นํด์ผ ํ๋์ง ๋๋ ์ด๋ ํ๋๊ฐ ์ผ์นํด์ผ ํ๋์ง๋ฅผ ๋ํ๋ผ ์ ์์ต๋๋ค.
not
์ ๋ถ์ ํ ๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.
adam
์ด๋ผ๋ ์ด๋ฆ์ ํฌํจํ์ง ์๋ ๋ชจ๋ ํ
์คํธ๋ฅผ ์คํํ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
pytest -k "not adam" tests/test_optimization.py
๋ ๊ฐ์ง ํจํด์ ํ๋๋ก ๊ฒฐํฉํ ์๋ ์์ต๋๋ค:
pytest -k "ada and not adam" tests/test_optimization.py
์๋ฅผ ๋ค์ด test_adafactor
์ test_adam_w
๋ฅผ ๋ชจ๋ ์คํํ๋ ค๋ฉด ๋ค์์ ์ฌ์ฉํ ์ ์์ต๋๋ค:
pytest -k "test_adam_w or test_adam_w" tests/test_optimization.py
์ฌ๊ธฐ์ or
๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ์ ์ํ์ธ์. ๋ ํค์๋ ์ค ํ๋๊ฐ ์ผ์นํ๋๋ก ํ๊ธฐ ์ํ ๋ชฉ์ ์ผ๋ก ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์
๋๋ค.
๋ ํจํด์ด ๋ชจ๋ ํฌํจ๋์ด์ผ ํ๋ ํ
์คํธ๋ง ์คํํ๋ ค๋ฉด, and
๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค:
pytest -k "test and ada" tests/test_optimization.py
accelerate
ํ
์คํธ ์คํ[[run-accelerate
-tests]]
๋ชจ๋ธ์์ accelerate
ํ
์คํธ๋ฅผ ์คํํด์ผ ํ ๋๊ฐ ์์ต๋๋ค. ์ด๋ฅผ ์ํด์๋ ๋ช
๋ น์ด์ -m accelerate_tests
๋ฅผ ์ถ๊ฐํ๋ฉด ๋ฉ๋๋ค.
์๋ฅผ ๋ค์ด, OPT
์์ ์ด๋ฌํ ํ
์คํธ๋ฅผ ์คํํ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
RUN_SLOW=1 pytest -m accelerate_tests tests/models/opt/test_modeling_opt.py
๋ฌธ์ ํ ์คํธ ์คํ[[run-documentation-tests]]
์์ ๋ฌธ์๊ฐ ์ฌ๋ฐ๋ฅธ์ง ํ
์คํธํ๋ ค๋ฉด doctests
๊ฐ ํต๊ณผํ๋์ง ํ์ธํด์ผ ํฉ๋๋ค.
์๋ฅผ ๋ค์ด, WhisperModel.forward
's docstring๋ฅผ ์ฌ์ฉํด ๋ด
์๋ค:
r"""
Returns:
Example:
```python
>>> import torch
>>> from transformers import WhisperModel, WhisperFeatureExtractor
>>> from datasets import load_dataset
>>> model = WhisperModel.from_pretrained("openai/whisper-base")
>>> feature_extractor = WhisperFeatureExtractor.from_pretrained("openai/whisper-base")
>>> ds = load_dataset("hf-internal-testing/librispeech_asr_dummy", "clean", split="validation")
>>> inputs = feature_extractor(ds[0]["audio"]["array"], return_tensors="pt")
>>> input_features = inputs.input_features
>>> decoder_input_ids = torch.tensor([[1, 1]]) * model.config.decoder_start_token_id
>>> last_hidden_state = model(input_features, decoder_input_ids=decoder_input_ids).last_hidden_state
>>> list(last_hidden_state.shape)
[1, 2, 512]
```"""
์ํ๋ ํ์ผ์ ๋ชจ๋ docstring ์์ ๋ฅผ ์๋์ผ๋ก ํ ์คํธํ๋ ค๋ฉด ๋ค์ ๋ช ๋ น์ ์คํํ๋ฉด ๋ฉ๋๋ค:
pytest --doctest-modules <path_to_file_or_dir>
ํ์ผ์ ํ์ฅ์๊ฐ markdown์ธ ๊ฒฝ์ฐ --doctest-glob="*.md"
์ธ์๋ฅผ ์ถ๊ฐํด์ผ ํฉ๋๋ค.
์์ ๋ ํ ์คํธ๋ง ์คํ[[run-only-modified-tests]]
์์ ๋ ํ์ผ ๋๋ ํ์ฌ ๋ธ๋์น (Git ๊ธฐ์ค)์ ๊ด๋ จ๋ ํ ์คํธ๋ฅผ ์คํํ๋ ค๋ฉด pytest-picked์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด๋ ๋ณ๊ฒฝํ ๋ด์ฉ์ด ํ ์คํธ์ ์ํฅ์ ์ฃผ์ง ์์๋์ง ๋น ๋ฅด๊ฒ ํ์ธํ ์ ์๋ ์ข์ ๋ฐฉ๋ฒ์ ๋๋ค.
pip install pytest-picked
pytest --picked
์์ ๋์์ง๋ง, ์์ง ์ปค๋ฐ๋์ง ์์ ๋ชจ๋ ํ์ผ ๋ฐ ํด๋์์ ํ ์คํธ๊ฐ ์คํ๋ฉ๋๋ค.
์์ค ์์ ์ ์คํจํ ํ ์คํธ ์๋ ์ฌ์คํ[[automatically-rerun-failed-tests-on-source-modification]]
pytest-xdist๋ ๋ชจ๋ ์คํจํ ํ ์คํธ๋ฅผ ๊ฐ์งํ๊ณ , ํ์ผ์ ์์ ํ ํ์ ํ์ผ์ ๊ณ์ ์ฌ์คํํ์ฌ ํ ์คํธ๊ฐ ์ฑ๊ณตํ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๋ ๋งค์ฐ ์ ์ฉํ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. ๋ฐ๋ผ์ ์์ ํ ๋ด์ฉ์ ํ์ธํ ํ pytest๋ฅผ ๋ค์ ์์ํ ํ์๊ฐ ์์ต๋๋ค. ๋ชจ๋ ํ ์คํธ๊ฐ ํต๊ณผ๋ ๋๊น์ง ์ด ๊ณผ์ ์ ๋ฐ๋ณตํ ํ ๋ค์ ์ ์ฒด ์คํ์ด ์ด๋ฃจ์ด์ง๋๋ค.
pip install pytest-xdist
์ฌ๊ท์ ๋ชจ๋์ ์ฌ์ฉ: pytest -f
๋๋ pytest --looponfail
ํ์ผ์ ๋ณ๊ฒฝ ์ฌํญ์ looponfailroots
๋ฃจํธ ๋๋ ํฐ๋ฆฌ์ ํด๋น ๋ด์ฉ์ (์ฌ๊ท์ ์ผ๋ก) ํ์ธํ์ฌ ๊ฐ์ง๋ฉ๋๋ค.
์ด ๊ฐ์ ๊ธฐ๋ณธ๊ฐ์ด ์๋ํ์ง ์๋ ๊ฒฝ์ฐ,
setup.cfg
์ ์ค์ ์ต์
์ ๋ณ๊ฒฝํ์ฌ ํ๋ก์ ํธ์์ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค:
[tool:pytest]
looponfailroots = transformers tests
๋๋ pytest.ini
/tox.ini
ํ์ผ:
[pytest]
looponfailroots = transformers tests
์ด๋ ๊ฒ ํ๋ฉด ini-file์ ๋๋ ํฐ๋ฆฌ๋ฅผ ๊ธฐ์ค์ผ๋ก ์๋์ ์ผ๋ก ์ง์ ๋ ๊ฐ ๋๋ ํฐ๋ฆฌ์์ ํ์ผ ๋ณ๊ฒฝ ์ฌํญ๋ง ์ฐพ๊ฒ ๋ฉ๋๋ค.
์ด ๊ธฐ๋ฅ์ ๋์ฒดํ ์ ์๋ ๊ตฌํ ๋ฐฉ๋ฒ์ธ pytest-watch๋ ์์ต๋๋ค.
ํน์ ํ ์คํธ ๋ชจ๋ ๊ฑด๋๋ฐ๊ธฐ[[skip-a-test-module]]
๋ชจ๋ ํ
์คํธ ๋ชจ๋์ ์คํํ๋ ํน์ ๋ชจ๋์ ์ ์ธํ๋ ค๋ฉด, ์คํํ ํ
์คํธ ๋ชฉ๋ก์ ๋ช
์์ ์ผ๋ก ์ง์ ํ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด, test_modeling_*.py
ํ
์คํธ๋ฅผ ์ ์ธํ ๋ชจ๋ ํ
์คํธ๋ฅผ ์คํํ๋ ค๋ฉด ๋ค์์ ์ฌ์ฉํ ์ ์์ต๋๋ค:
pytest *ls -1 tests/*py | grep -v test_modeling*
์ํ ์ด๊ธฐํ[[clearing state]]
CI ๋น๋ ๋ฐ (์๋์ ๋ํ) ๊ฒฉ๋ฆฌ๊ฐ ์ค์ํ ๊ฒฝ์ฐ, ์บ์๋ฅผ ์ง์์ผ ํฉ๋๋ค:
pytest --cache-clear tests
ํ ์คํธ๋ฅผ ๋ณ๋ ฌ๋ก ์คํ[[running-tests-in-parallel]]
์ด์ ์ ์ธ๊ธํ ๊ฒ์ฒ๋ผ make test
๋ ํ
์คํธ๋ฅผ ๋ณ๋ ฌ๋ก ์คํํ๊ธฐ ์ํด
pytest-xdist
ํ๋ฌ๊ทธ์ธ(-n X
์ธ์, ์๋ฅผ ๋ค์ด -n 2
๋ฅผ ์ฌ์ฉํ์ฌ 2๊ฐ์ ๋ณ๋ ฌ ์์
์คํ)์ ํตํด ์คํ๋ฉ๋๋ค.
pytest-xdist
์ --dist=
์ต์
์ ์ฌ์ฉํ์ฌ ํ
์คํธ๋ฅผ ์ด๋ป๊ฒ ๊ทธ๋ฃนํํ ์ง ์ ์ดํ ์ ์์ต๋๋ค.
--dist=loadfile
์ ํ๋์ ํ์ผ์ ์๋ ํ
์คํธ๋ฅผ ๋์ผํ ํ๋ก์ธ์ค๋ก ๊ทธ๋ฃนํํฉ๋๋ค.
์คํ๋ ํ
์คํธ์ ์์๊ฐ ๋ค๋ฅด๊ณ ์์ธกํ ์ ์๊ธฐ ๋๋ฌธ์, pytest-xdist
๋ก ํ
์คํธ ์ค์ํธ๋ฅผ ์คํํ๋ฉด ์คํจ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค (๊ฒ์ถ๋์ง ์์ ๊ฒฐํฉ๋ ํ
์คํธ๊ฐ ์๋ ๊ฒฝ์ฐ).
์ด ๊ฒฝ์ฐ pytest-replay๋ฅผ ์ฌ์ฉํ๋ฉด ๋์ผํ ์์๋ก ํ
์คํธ๋ฅผ ๋ค์ ์คํํด์
์คํจํ๋ ์ํ์ค๋ฅผ ์ต์ํํ๋ ๋ฐ์ ๋์์ด ๋ฉ๋๋ค.
ํ ์คํธ ์์์ ๋ฐ๋ณต[[test-order-and-repetition]]
์ ์ฌ์ ์ธ ์ข ์์ฑ ๋ฐ ์ํ ๊ด๋ จ ๋ฒ๊ทธ(tear down)๋ฅผ ๊ฐ์งํ๊ธฐ ์ํด ํ ์คํธ๋ฅผ ์ฌ๋ฌ ๋ฒ, ์ฐ์์ผ๋ก, ๋ฌด์์๋ก ๋๋ ์ธํธ๋ก ๋ฐ๋ณตํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ง์ ์ ์ธ ์ฌ๋ฌ ๋ฒ์ ๋ฐ๋ณต์ DL์ ๋ฌด์์์ฑ์ ์ํด ๋ฐ๊ฒฌ๋๋ ์ผ๋ถ ๋ฌธ์ ๋ฅผ ๊ฐ์งํ๋ ๋ฐ์๋ ์ ์ฉํฉ๋๋ค.
ํ ์คํธ๋ฅผ ๋ฐ๋ณต[[repeat-tests]]
pip install pytest-flakefinder
๋ชจ๋ ํ ์คํธ๋ฅผ ์ฌ๋ฌ ๋ฒ ์คํํฉ๋๋ค(๊ธฐ๋ณธ๊ฐ์ 50๋ฒ):
pytest --flake-finder --flake-runs=5 tests/test_failing_test.py
์ด ํ๋ฌ๊ทธ์ธ์ pytest-xdist
์ -n
ํ๋๊ทธ์ ํจ๊ป ์๋ํ์ง ์์ต๋๋ค.
pytest-repeat
๋ผ๋ ๋ ๋ค๋ฅธ ํ๋ฌ๊ทธ์ธ๋ ์์ง๋ง unittest
์ ํจ๊ป ์๋ํ์ง ์์ต๋๋ค.
ํ ์คํธ๋ฅผ ์์์ ์์๋ก ์คํ[[run-tests-in-a-random-order]]
pip install pytest-random-order
์ค์: pytest-random-order
๊ฐ ์ค์น๋๋ฉด ํ
์คํธ๊ฐ ์๋์ผ๋ก ์์์ ์์๋ก ์์
๋๋ค.
๊ตฌ์ฑ ๋ณ๊ฒฝ์ด๋ ์ปค๋งจ๋ ๋ผ์ธ ์ต์
์ด ํ์ํ์ง ์์ต๋๋ค.
์์ ์ค๋ช
ํ ๊ฒ์ฒ๋ผ ์ด๋ฅผ ํตํด ํ ํ
์คํธ์ ์ํ๊ฐ ๋ค๋ฅธ ํ
์คํธ์ ์ํ์ ์ํฅ์ ๋ฏธ์น๋ ๊ฒฐํฉ๋ ํ
์คํธ๋ฅผ ๊ฐ์งํ ์ ์์ต๋๋ค.
pytest-random-order
๊ฐ ์ค์น๋๋ฉด ํด๋น ์ธ์
์์ ์ฌ์ฉ๋ ๋๋ค ์๋๊ฐ ์ถ๋ ฅ๋๋ฉฐ ์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
pytest tests
[...]
Using --random-order-bucket=module
Using --random-order-seed=573663
๋ฐ๋ผ์ ํน์ ์ํ์ค๊ฐ ์คํจํ๋ ๊ฒฝ์ฐ์๋ ์ ํํ ์๋๋ฅผ ์ถ๊ฐํ์ฌ ์ฌํํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
pytest --random-order-seed=573663
[...]
Using --random-order-bucket=module
Using --random-order-seed=573663
์ ํํ ๋์ผํ ํ
์คํธ ๋ชฉ๋ก(๋๋ ๋ชฉ๋ก์ด ์์)์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์๋ง ์ ํํ ์์๋ฅผ ์ฌํํฉ๋๋ค.
๋ชฉ๋ก์ ์๋์ผ๋ก ์ขํ๊ธฐ ์์ํ๋ฉด ๋ ์ด์ ์๋์ ์์กดํ ์ ์๊ณ ์คํจํ๋ ์ ํํ ์์๋ก ์๋์ผ๋ก ๋ชฉ๋ก์ ๋์ดํด์ผํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ --random-order-bucket=none
์ ์ฌ์ฉํ์ฌ pytest์๊ฒ ์์๋ฅผ ์์๋ก ์ค์ ํ์ง ์๋๋ก ์๋ ค์ผ ํฉ๋๋ค.
์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
pytest --random-order-bucket=none tests/test_a.py tests/test_c.py tests/test_b.py
๋ชจ๋ ํ ์คํธ์ ๋ํด ์๊ธฐ๋ฅผ ๋นํ์ฑํํ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
pytest --random-order-bucket=none
๊ธฐ๋ณธ์ ์ผ๋ก --random-order-bucket=module
์ด ๋ด์ฌ๋์ด ์์ผ๋ฏ๋ก, ๋ชจ๋ ์์ค์์ ํ์ผ์ ์์ต๋๋ค.
๋ํ class
, package
, global
๋ฐ none
์์ค์์๋ ์์ ์ ์์ต๋๋ค.
์์ธํ ๋ด์ฉ์ ํด๋น ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ธ์.
๋ ๋ค๋ฅธ ๋ฌด์์ํ์ ๋์์ pytest-randomly
์
๋๋ค.
์ด ๋ชจ๋์ ๋งค์ฐ ์ ์ฌํ ๊ธฐ๋ฅ/์ธํฐํ์ด์ค๋ฅผ ๊ฐ์ง๊ณ ์์ง๋ง, pytest-random-order
์ ์๋ ๋ฒํท ๋ชจ๋๋ฅผ ์ฌ์ฉํ ์๋ ์์ต๋๋ค.
์ค์น ํ์๋ ์๋์ผ๋ก ์ ์ฉ๋๋ ๋ฌธ์ ๋ ๋์ผํ๊ฒ ๊ฐ์ง๋๋ค.
์ธ๊ด๊ณผ ๋๋์ ๋ณ๊ฒฝ[[look-and-feel-variations]
pytest-sugar ์ฌ์ฉ[[pytest-sugar]]
pytest-sugar๋ ํ ์คํธ๊ฐ ๋ณด์ฌ์ง๋ ํํ๋ฅผ ๊ฐ์ ํ๊ณ , ์งํ ์ํฉ ๋ฐ๋ฅผ ์ถ๊ฐํ๋ฉฐ, ์คํจํ ํ ์คํธ์ ๊ฒ์ฆ์ ์ฆ์ ํ์ํ๋ ํ๋ฌ๊ทธ์ธ์ ๋๋ค. ์ค์นํ๋ฉด ์๋์ผ๋ก ํ์ฑํ๋ฉ๋๋ค.
pip install pytest-sugar
pytest-sugar ์์ด ํ ์คํธ๋ฅผ ์คํํ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
pytest -p no:sugar
๋๋ ์ ๊ฑฐํ์ธ์.
๊ฐ ํ์ ํ ์คํธ ์ด๋ฆ๊ณผ ์งํ ์ํฉ ๋ณด๊ณ [[report-each-sub-test-name-and-its-progress]]
pytest
๋ฅผ ํตํด ๋จ์ผ ๋๋ ๊ทธ๋ฃน์ ํ
์คํธ๋ฅผ ์คํํ๋ ๊ฒฝ์ฐ(pip install pytest-pspec
์ดํ):
pytest --pspec tests/test_optimization.py
์คํจํ ํ ์คํธ ์ฆ์ ํ์[[instantly-shows-failed-tests]]
pytest-instafail์ ํ ์คํธ ์ธ์ ์ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ์คํจ ๋ฐ ์ค๋ฅ๋ฅผ ์ฆ์ ํ์ํฉ๋๋ค.
pip install pytest-instafail
pytest --instafail
GPU ์ฌ์ฉ ์ฌ๋ถ[[to-GPU-or-not-to-GPU]]
GPU๊ฐ ํ์ฑํ๋ ํ๊ฒฝ์์, CPU ์ ์ฉ ๋ชจ๋๋ก ํ
์คํธํ๋ ค๋ฉด CUDA_VISIBLE_DEVICES=""
๋ฅผ ์ถ๊ฐํฉ๋๋ค:
CUDA_VISIBLE_DEVICES="" pytest tests/utils/test_logging.py
๋๋ ๋ค์ค GPU๊ฐ ์๋ ๊ฒฝ์ฐ pytest
์์ ์ฌ์ฉํ GPU๋ฅผ ์ง์ ํ ์๋ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด, GPU 0
๋ฐ 1
์ด ์๋ ๊ฒฝ์ฐ ๋ค์์ ์คํํ ์ ์์ต๋๋ค:
CUDA_VISIBLE_DEVICES="1" pytest tests/utils/test_logging.py
์ด๋ ๊ฒ ํ๋ฉด ๋ค๋ฅธ GPU์์ ๋ค๋ฅธ ์์ ์ ์คํํ๋ ค๋ ๊ฒฝ์ฐ ์ ์ฉํฉ๋๋ค.
์ผ๋ถ ํ ์คํธ๋ ๋ฐ๋์ CPU ์ ์ฉ์ผ๋ก ์คํํด์ผ ํ๋ฉฐ, ์ผ๋ถ๋ CPU ๋๋ GPU ๋๋ TPU์์ ์คํํด์ผ ํ๊ณ , ์ผ๋ถ๋ ์ฌ๋ฌ GPU์์ ์คํํด์ผ ํฉ๋๋ค. ๋ค์ ์คํต ๋ฐ์ฝ๋ ์ดํฐ๋ ํ ์คํธ์ ์๊ตฌ ์ฌํญ์ CPU/GPU/TPU๋ณ๋ก ์ค์ ํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค:
require_torch
- ์ด ํ ์คํธ๋ torch์์๋ง ์คํ๋ฉ๋๋ค.require_torch_gpu
-require_torch
์ ์ถ๊ฐ๋ก ์ ์ด๋ 1๊ฐ์ GPU๊ฐ ํ์ํฉ๋๋ค.require_torch_multi_gpu
-require_torch
์ ์ถ๊ฐ๋ก ์ ์ด๋ 2๊ฐ์ GPU๊ฐ ํ์ํฉ๋๋ค.require_torch_non_multi_gpu
-require_torch
์ ์ถ๊ฐ๋ก 0๊ฐ ๋๋ 1๊ฐ์ GPU๊ฐ ํ์ํฉ๋๋ค.require_torch_up_to_2_gpus
-require_torch
์ ์ถ๊ฐ๋ก 0๊ฐ, 1๊ฐ ๋๋ 2๊ฐ์ GPU๊ฐ ํ์ํฉ๋๋ค.require_torch_tpu
-require_torch
์ ์ถ๊ฐ๋ก ์ ์ด๋ 1๊ฐ์ TPU๊ฐ ํ์ํฉ๋๋ค.
GPU ์๊ตฌ ์ฌํญ์ ํ๋ก ์ ๋ฆฌํ๋ฉด ์๋์ ๊ฐ์ต๋๋ใ :
| n gpus | decorator |
|--------+--------------------------------|
| >= 0
| @require_torch
|
| >= 1
| @require_torch_gpu
|
| >= 2
| @require_torch_multi_gpu
|
| < 2
| @require_torch_non_multi_gpu
|
| < 3
| @require_torch_up_to_2_gpus
|
์๋ฅผ ๋ค์ด, 2๊ฐ ์ด์์ GPU๊ฐ ์๊ณ pytorch๊ฐ ์ค์น๋์ด ์์ ๋์๋ง ์คํ๋์ด์ผ ํ๋ ํ ์คํธ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
@require_torch_multi_gpu
def test_example_with_multi_gpu():
tensorflow
๊ฐ ํ์ํ ๊ฒฝ์ฐ require_tf
๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
@require_tf
def test_tf_thing_with_tensorflow():
์ด๋ฌํ ๋ฐ์ฝ๋ ์ดํฐ๋ ์ค์ฒฉ๋ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ๋๋ฆฐ ํ ์คํธ๋ก ์งํ๋๊ณ pytorch์์ ์ ์ด๋ ํ๋์ GPU๊ฐ ํ์ํ ๊ฒฝ์ฐ ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํ ์ ์์ต๋๋ค:
@require_torch_gpu
@slow
def test_example_slow_on_gpu():
@parametrized
์ ๊ฐ์ ์ผ๋ถ ๋ฐ์ฝ๋ ์ดํฐ๋ ํ
์คํธ ์ด๋ฆ์ ๋ค์ ์์ฑํ๊ธฐ ๋๋ฌธ์ @require_*
์คํต ๋ฐ์ฝ๋ ์ดํฐ๋ ์ฌ๋ฐ๋ฅด๊ฒ ์๋ํ๋ ค๋ฉด ํญ์ ๋งจ ๋ง์ง๋ง์ ๋์ด๋์ด์ผ ํฉ๋๋ค.
๋ค์์ ์ฌ๋ฐ๋ฅธ ์ฌ์ฉ ์์
๋๋ค:
@parameterized.expand(...)
@require_torch_multi_gpu
def test_integration_foo():
@pytest.mark.parametrize
์๋ ์ด๋ฌํ ์์ ๋ฌธ์ ๋ ์์ผ๋ฏ๋ก ์ฒ์ ํน์ ๋ง์ง๋ง์ ์์น์ํฌ ์ ์๊ณ ์ด๋ฌํ ๊ฒฝ์ฐ์๋ ์ ์๋ํ ๊ฒ์
๋๋ค.
ํ์ง๋ง unittest๊ฐ ์๋ ๊ฒฝ์ฐ์๋ง ์๋ํฉ๋๋ค.
ํ ์คํธ ๋ด๋ถ์์ ๋ค์์ ์ฌ์ฉํ ์ ์์ต๋๋ค:
- ์ฌ์ฉ ๊ฐ๋ฅํ GPU ์:
from transformers.testing_utils import get_gpu_count
n_gpu = get_gpu_count() #torch์ tf์ ํจ๊ป ์๋
๋ถ์ฐ ํ๋ จ[[distributed-training]]
pytest
๋ ๋ถ์ฐ ํ๋ จ์ ์ง์ ์ ์ผ๋ก ๋ค๋ฃจ์ง ๋ชปํฉ๋๋ค.
์ด๋ฅผ ์๋ํ๋ฉด ํ์ ํ๋ก์ธ์ค๊ฐ ์ฌ๋ฐ๋ฅธ ์์
์ ์ํํ์ง ์๊ณ pytest
๋ผ๊ณ ์๊ฐํ๊ธฐ์ ํ
์คํธ ์ค์ํธ๋ฅผ ๋ฐ๋ณตํด์ ์คํํ๊ฒ ๋ฉ๋๋ค.
๊ทธ๋ฌ๋ ์ผ๋ฐ ํ๋ก์ธ์ค๋ฅผ ์์ฑํ ๋ค์ ์ฌ๋ฌ ์์ปค๋ฅผ ์์ฑํ๊ณ IO ํ์ดํ๋ฅผ ๊ด๋ฆฌํ๋๋ก ํ๋ฉด ๋์ํฉ๋๋ค.
๋ค์์ ์ฌ์ฉ ๊ฐ๋ฅํ ํ ์คํธ์ ๋๋ค:
์คํ ์ง์ ์ผ๋ก ๋ฐ๋ก ์ด๋ํ๋ ค๋ฉด, ํด๋น ํ
์คํธ์์ execute_subprocess_async
ํธ์ถ์ ๊ฒ์ํ์ธ์.
์ด๋ฌํ ํ ์คํธ๋ฅผ ์คํํ๋ ค๋ฉด ์ ์ด๋ 2๊ฐ์ GPU๊ฐ ํ์ํฉ๋๋ค.
CUDA_VISIBLE_DEVICES=0,1 RUN_SLOW=1 pytest -sv tests/test_trainer_distributed.py
์ถ๋ ฅ ์บก์ฒ[[output-capture]]
ํ
์คํธ ์คํ ์ค stdout
๋ฐ stderr
๋ก ์ ์ก๋ ๋ชจ๋ ์ถ๋ ฅ์ด ์บก์ฒ๋ฉ๋๋ค.
ํ
์คํธ๋ ์ค์ ๋ฉ์๋๊ฐ ์คํจํ๋ฉด ์บก์ฒ๋ ์ถ๋ ฅ์ ์ผ๋ฐ์ ์ผ๋ก ์คํจ ์ถ์ ์ ๋ณด์ ํจ๊ป ํ์๋ฉ๋๋ค.
์ถ๋ ฅ ์บก์ฒ๋ฅผ ๋นํ์ฑํํ๊ณ stdout
๋ฐ stderr
๋ฅผ ์ ์์ ์ผ๋ก ๋ฐ์ผ๋ ค๋ฉด -s
๋๋ --capture=no
๋ฅผ ์ฌ์ฉํ์ธ์:
pytest -s tests/utils/test_logging.py
ํ ์คํธ ๊ฒฐ๊ณผ๋ฅผ JUnit ํ์์ ์ถ๋ ฅ์ผ๋ก ๋ณด๋ด๋ ค๋ฉด ๋ค์์ ์ฌ์ฉํ์ธ์:
py.test tests --junitxml=result.xml
์์ ์กฐ์ [[color-control]]
์์์ด ์๊ฒ ํ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํ์ธ์(์๋ฅผ ๋ค์ด ํฐ์ ๋ฐฐ๊ฒฝ์ ๋ ธ๋์ ๊ธ์จ๋ ๊ฐ๋ ์ฑ์ด ์ข์ง ์์ต๋๋ค):
pytest --color=no tests/utils/test_logging.py
online pastebin service์ ํ ์คํธ ๋ณด๊ณ ์ ์ ์ก[[sending test report to online pastebin service]]
๊ฐ ํ ์คํธ ์คํจ์ ๋ํ URL์ ๋ง๋ญ๋๋ค:
pytest --pastebin=failed tests/utils/test_logging.py
์ด๋ ๊ฒ ํ๋ฉด ๊ฐ ์คํจ์ ๋ํ URL์ ์ ๊ณตํ๋ remote Paste service์ ํ
์คํธ ์คํ ์ ๋ณด๋ฅผ ์ ์ถํฉ๋๋ค.
์ผ๋ฐ์ ์ธ ํ
์คํธ๋ฅผ ์ ํํ ์๋ ์๊ณ ํน์ ํน์ ์คํจ๋ง ๋ณด๋ด๋ ค๋ฉด -x
์ ๊ฐ์ด ์ถ๊ฐํ ์๋ ์์ต๋๋ค.
์ ์ฒด ํ ์คํธ ์ธ์ ๋ก๊ทธ์ ๋ํ URL์ ์์ฑํฉ๋๋ค:
pytest --pastebin=all tests/utils/test_logging.py
ํ ์คํธ ์์ฑ[[writing-tests]]
๐ค transformers ํ
์คํธ๋ ๋๋ถ๋ถ unittest
๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ์ง๋ง,
pytest
์์ ์คํ๋๋ฏ๋ก ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ๋ ์์คํ
์ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ง์๋๋ ๊ธฐ๋ฅ์ ๋ํด ์ฌ๊ธฐ์์ ํ์ธํ ์ ์์ง๋ง,
๊ธฐ์ตํด์ผ ํ ์ค์ํ ์ ์ ๋๋ถ๋ถ์ pytest
fixture๊ฐ ์๋ํ์ง ์๋๋ค๋ ๊ฒ์
๋๋ค.
ํ๋ผ๋ฏธํฐํ๋ ์๋ํ์ง ์์ง๋ง, ์ฐ๋ฆฌ๋ ๋น์ทํ ๋ฐฉ์์ผ๋ก ์๋ํ๋ parameterized
๋ชจ๋์ ์ฌ์ฉํฉ๋๋ค.
๋งค๊ฐ๋ณ์ํ[[parametrization]]
๋์ผํ ํ ์คํธ๋ฅผ ๋ค๋ฅธ ์ธ์๋ก ์ฌ๋ฌ ๋ฒ ์คํํด์ผ ํ๋ ๊ฒฝ์ฐ๊ฐ ์ข ์ข ์์ต๋๋ค. ํ ์คํธ ๋ด์์ ์ด ์์ ์ ์ํํ ์ ์์ง๋ง, ๊ทธ๋ ๊ฒ ํ๋ฉด ํ๋์ ์ธ์ ์ธํธ์ ๋ํด ํ ์คํธ๋ฅผ ์คํํ ์ ์์ต๋๋ค.
# test_this1.py
import unittest
from parameterized import parameterized
class TestMathUnitTest(unittest.TestCase):
@parameterized.expand(
[
("negative", -1.5, -2.0),
("integer", 1, 1.0),
("large fraction", 1.6, 1),
]
)
def test_floor(self, name, input, expected):
assert_equal(math.floor(input), expected)
์ด์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ด ํ
์คํธ๋ test_floor
์ ๋ง์ง๋ง 3๊ฐ ์ธ์๊ฐ
๋งค๊ฐ๋ณ์ ๋ชฉ๋ก์ ํด๋น ์ธ์์ ํ ๋น๋๋ ๊ฒ์ผ๋ก 3๋ฒ ์คํ๋ ๊ฒ์
๋๋ค.
๊ทธ๋ฆฌ๊ณ negative
๋ฐ integer
๋งค๊ฐ๋ณ์ ์งํฉ๋ง ์คํํ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์คํํ ์ ์์ต๋๋ค:
pytest -k "negative and integer" tests/test_mytest.py
๋๋ negative
ํ์ ํ
์คํธ๋ฅผ ์ ์ธํ ๋ชจ๋ ์๋ธ ํ
์คํธ๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์คํํ ์ ์์ต๋๋ค:
pytest -k "not negative" tests/test_mytest.py
์์์ ์ธ๊ธํ -k
ํํฐ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ ์ธ์๋,
๊ฐ ์๋ธ ํ
์คํธ์ ์ ํํ ์ด๋ฆ์ ํ์ธํ ํ์ ์ผ๋ถ ํน์ ์ ์ฒด ์๋ธ ํ
์คํธ๋ฅผ ์คํํ ์ ์์ต๋๋ค.
pytest test_this1.py --collect-only -q
๊ทธ๋ฆฌ๊ณ ๋ค์์ ๋ด์ฉ์ ํ์ธํ ์ ์์ ๊ฒ์ ๋๋ค:
test_this1.py::TestMathUnitTest::test_floor_0_negative
test_this1.py::TestMathUnitTest::test_floor_1_integer
test_this1.py::TestMathUnitTest::test_floor_2_large_fraction
2๊ฐ์ ํน์ ํ ์๋ธ ํ ์คํธ๋ง ์คํํ ์๋ ์์ต๋๋ค:
pytest test_this1.py::TestMathUnitTest::test_floor_0_negative test_this1.py::TestMathUnitTest::test_floor_1_integer
transformers
์ ๊ฐ๋ฐ์ ์ข
์์ฑ์ ์ด๋ฏธ ์๋ parameterized ๋ชจ๋์
unittests
์ pytest
ํ
์คํธ ๋ชจ๋์์ ์๋ํฉ๋๋ค.
๊ทธ๋ฌ๋ ํ
์คํธ๊ฐ unittest
๊ฐ ์๋ ๊ฒฝ์ฐ pytest.mark.parametrize
๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค(์ด๋ฏธ ์๋ ์ผ๋ถ ํ
์คํธ์์ ์ฌ์ฉ๋๋ ๊ฒฝ์ฐ๋ ์์ต๋๋ค.
์ฃผ๋ก examples
ํ์์ ์์ต๋๋ค).
๋ค์์ pytest
์ parametrize
๋ง์ปค๋ฅผ ์ฌ์ฉํ ๋์ผํ ์์
๋๋ค:
# test_this2.py
import pytest
@pytest.mark.parametrize(
"name, input, expected",
[
("negative", -1.5, -2.0),
("integer", 1, 1.0),
("large fraction", 1.6, 1),
],
)
def test_floor(name, input, expected):
assert_equal(math.floor(input), expected)
parameterized
์ ๋ง์ฐฌ๊ฐ์ง๋ก pytest.mark.parametrize
๋ฅผ ์ฌ์ฉํ๋ฉด
-k
ํํฐ๊ฐ ์๋ํ์ง ์๋ ๊ฒฝ์ฐ์๋ ์คํํ ์๋ธ ํ
์คํธ๋ฅผ ์ ํํ๊ฒ ์ง์ ํ ์ ์์ต๋๋ค.
๋จ, ์ด ๋งค๊ฐ๋ณ์ํ ํจ์๋ ์๋ธ ํ
์คํธ์ ์ด๋ฆ ์งํฉ์ ์ฝ๊ฐ ๋ค๋ฅด๊ฒ ์์ฑํฉ๋๋ค. ๋ค์๊ณผ ๊ฐ์ ๋ชจ์ต์
๋๋ค:
pytest test_this2.py --collect-only -q
๊ทธ๋ฆฌ๊ณ ๋ค์์ ๋ด์ฉ์ ํ์ธํ ์ ์์ ๊ฒ์ ๋๋ค:
test_this2.py::test_floor[integer-1-1.0]
test_this2.py::test_floor[negative--1.5--2.0]
test_this2.py::test_floor[large fraction-1.6-1]
ํน์ ํ ํ ์คํธ์ ๋ํด์๋ง ์คํํ ์๋ ์์ต๋๋ค:
pytest test_this2.py::test_floor[negative--1.5--2.0] test_this2.py::test_floor[integer-1-1.0]
์ด์ ์ ์์์ ๊ฐ์ด ์คํํ ์ ์์ต๋๋ค.
ํ์ผ ๋ฐ ๋๋ ํฐ๋ฆฌ[[files-and-directories]]
ํ
์คํธ์์ ์ข
์ข
ํ์ฌ ํ
์คํธ ํ์ผ๊ณผ ๊ด๋ จ๋ ์๋์ ์ธ ์์น๋ฅผ ์์์ผ ํ๋ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค.
ํ
์คํธ๊ฐ ์ฌ๋ฌ ๋๋ ํฐ๋ฆฌ์์ ํธ์ถ๋๊ฑฐ๋ ๊น์ด๊ฐ ๋ค๋ฅธ ํ์ ๋๋ ํฐ๋ฆฌ์ ์์ ์ ์๊ธฐ ๋๋ฌธ์ ๊ทธ ์์น๋ฅผ ์๋ ๊ฒ์ ๊ฐ๋จํ์ง ์์ต๋๋ค.
transformers.test_utils.TestCasePlus
๋ผ๋ ํฌํผ ํด๋์ค๋ ๋ชจ๋ ๊ธฐ๋ณธ ๊ฒฝ๋ก๋ฅผ ์ฒ๋ฆฌํ๊ณ ๊ฐ๋จํ ์ก์ธ์๋ฅผ ์ ๊ณตํ์ฌ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํฉ๋๋ค:
pathlib
๊ฐ์ฒด(์์ ํ ์ ํด์ง ๊ฒฝ๋ก)test_file_path
- ํ์ฌ ํ ์คํธ ํ์ผ ๊ฒฝ๋ก (์:__file__
)- test_file_dir` - ํ์ฌ ํ ์คํธ ํ์ผ์ด ํฌํจ๋ ๋๋ ํฐ๋ฆฌ
- tests_dir
-
tests` ํ ์คํธ ์ค์ํธ์ ๋๋ ํฐ๋ฆฌ - examples_dir
-
examples` ํ ์คํธ ์ค์ํธ์ ๋๋ ํฐ๋ฆฌ - repo_root_dir` - ์ ์ฅ์ ๋๋ ํฐ๋ฆฌ
- src_dir
-
src์ ๋๋ ํฐ๋ฆฌ(์:
transformers` ํ์ ๋๋ ํฐ๋ฆฌ๊ฐ ์๋ ๊ณณ)
๋ฌธ์์ด๋ก ๋ณํ๋ ๊ฒฝ๋ก---์์ ๋์ผํ์ง๋ง,
pathlib
๊ฐ์ฒด๊ฐ ์๋ ๋ฌธ์์ด๋ก ๊ฒฝ๋ก๋ฅผ ๋ฐํํฉ๋๋ค:test_file_path_str
test_file_dir_str
tests_dir_str
examples_dir_str
repo_root_dir_str
src_dir_str
์์ ๋ด์ฉ์ ์ฌ์ฉํ๋ ค๋ฉด ํ ์คํธ๊ฐ 'transformers.test_utils.TestCasePlus'์ ์๋ธํด๋์ค์ ์๋์ง ํ์ธํด์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
from transformers.testing_utils import TestCasePlus
class PathExampleTest(TestCasePlus):
def test_something_involving_local_locations(self):
data_dir = self.tests_dir / "fixtures/tests_samples/wmt_en_ro"
๋ง์ฝ pathlib
๋ฅผ ํตํด ๊ฒฝ๋ก๋ฅผ ์กฐ์ํ ํ์๊ฐ ์๊ฑฐ๋ ๊ฒฝ๋ก๋ฅผ ๋ฌธ์์ด๋ก๋ง ํ์๋ก ํ๋ ๊ฒฝ์ฐ์๋ pathlib
๊ฐ์ฒด์ str()
์ ํธ์ถํ๊ฑฐ๋ _str
๋ก ๋๋๋ ์ ๊ทผ์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
from transformers.testing_utils import TestCasePlus
class PathExampleTest(TestCasePlus):
def test_something_involving_stringified_locations(self):
examples_dir = self.examples_dir_str
์์ ํ์ผ ๋ฐ ๋๋ ํฐ๋ฆฌ[[temporary-files-and-directories]]
๊ณ ์ ํ ์์ ํ์ผ ๋ฐ ๋๋ ํฐ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๋ณ๋ ฌ ํ
์คํธ ์คํ์ ์์ด ํ์์ ์
๋๋ค.
์ด๋ ๊ฒ ํจ์ผ๋ก์จ ํ
์คํธ๋ค์ด ์๋ก์ ๋ฐ์ดํฐ๋ฅผ ๋ฎ์ด์ฐ์ง ์๊ฒ ํ ์ ์์ต๋๋ค. ๋ํ ์ฐ๋ฆฌ๋ ์์ฑ๋ ํ
์คํธ์ ์ข
๋ฃ ๋จ๊ณ์์ ์ด๋ฌํ ์์ ํ์ผ ๋ฐ ๋๋ ํฐ๋ฆฌ๋ฅผ ์ ๊ฑฐํ๊ณ ์ถ์ต๋๋ค.
๋ฐ๋ผ์ ์ด๋ฌํ ์๊ตฌ ์ฌํญ์ ์ถฉ์กฑ์์ผ์ฃผ๋ tempfile
๊ณผ ๊ฐ์ ํจํค์ง๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
๊ทธ๋ฌ๋ ํ ์คํธ๋ฅผ ๋๋ฒ๊น ํ ๋๋ ์์ ํ์ผ์ด๋ ๋๋ ํฐ๋ฆฌ์ ๋ค์ด๊ฐ๋ ๋ด์ฉ์ ํ์ธํ ์ ์์ด์ผ ํ๋ฉฐ, ์ฌ์คํ๋๋ ๊ฐ ํ ์คํธ๋ง๋ค ์์ ํ์ผ์ด๋ ๋๋ ํฐ๋ฆฌ์ ๊ฒฝ๋ก์ ๋ํด ๋ฌด์์ ๊ฐ์ด ์๋ ์ ํํ ๊ฐ์ ์๊ณ ์ถ์ ๊ฒ์ ๋๋ค.
transformers.test_utils.TestCasePlus
๋ผ๋ ๋์ฐ๋ฏธ ํด๋์ค๋ ์ด๋ฌํ ๋ชฉ์ ์ ๊ฐ์ฅ ์ ํฉํฉ๋๋ค.
์ด ํด๋์ค๋ unittest.TestCase
์ ํ์ ํด๋์ค์ด๋ฏ๋ก, ์ฐ๋ฆฌ๋ ์ด๊ฒ์ ํ
์คํธ ๋ชจ๋์์ ์ฝ๊ฒ ์์ํ ์ ์์ต๋๋ค.
๋ค์์ ํด๋น ํด๋์ค๋ฅผ ์ฌ์ฉํ๋ ์์์ ๋๋ค:
from transformers.testing_utils import TestCasePlus
class ExamplesTests(TestCasePlus):
def test_whatever(self):
tmp_dir = self.get_auto_remove_tmp_dir()
์ด ์ฝ๋๋ ๊ณ ์ ํ ์์ ๋๋ ํฐ๋ฆฌ๋ฅผ ์์ฑํ๊ณ tmp_dir
์ ํด๋น ์์น๋ก ์ค์ ํฉ๋๋ค.
- ๊ณ ์ ํ ์์ ๋๋ ํฐ๋ฆฌ๋ฅผ ์์ฑํฉ๋๋ค:
def test_whatever(self):
tmp_dir = self.get_auto_remove_tmp_dir()
tmp_dir
์๋ ์์ฑ๋ ์์ ๋๋ ํฐ๋ฆฌ์ ๊ฒฝ๋ก๊ฐ ํฌํจ๋ฉ๋๋ค.
์ด๋ ํ
์คํธ์ ์ข
๋ฃ ๋จ๊ณ์์ ์๋์ผ๋ก ์ ๊ฑฐ๋ฉ๋๋ค.
- ์ ํํ ๊ฒฝ๋ก๋ก ์์ ๋๋ ํฐ๋ฆฌ ์์ฑ ํ์ ํ ์คํธ ์์ ์ ์ ๋น์ด ์๋ ์ํ์ธ์ง ํ์ธํ๊ณ , ํ ์คํธ ํ์๋ ๋น์ฐ์ง ๋ง์ธ์.
def test_whatever(self):
tmp_dir = self.get_auto_remove_tmp_dir("./xxx")
์ด๊ฒ์ ๋๋ฒ๊น ํ ๋ ํน์ ๋๋ ํฐ๋ฆฌ๋ฅผ ๋ชจ๋ํฐ๋งํ๊ณ , ๊ทธ ๋๋ ํฐ๋ฆฌ์ ์ด์ ์ ์คํ๋ ํ ์คํธ๊ฐ ๋ฐ์ดํฐ๋ฅผ ๋จ๊ธฐ์ง ์๋๋ก ํ๋ ๋ฐ์ ์ ์ฉํฉ๋๋ค.
before
๋ฐafter
์ธ์๋ฅผ ์ง์ ์ค๋ฒ๋ผ์ด๋ฉํ์ฌ ๊ธฐ๋ณธ ๋์์ ๋ณ๊ฒฝํ ์ ์์ผ๋ฉฐ ๋ค์ ์ค ํ๋์ ๋์์ผ๋ก ์ด์ด์ง๋๋ค:before=True
: ํ ์คํธ ์์ ์ ์์ ๋๋ ํฐ๋ฆฌ๊ฐ ํญ์ ์ง์์ง๋๋ค.before=False
: ์์ ๋๋ ํฐ๋ฆฌ๊ฐ ์ด๋ฏธ ์กด์ฌํ๋ ๊ฒฝ์ฐ ๊ธฐ์กด ํ์ผ์ ๊ทธ๋๋ก ๋จ์ต๋๋ค.after=True
: ํ ์คํธ ์ข ๋ฃ ์ ์์ ๋๋ ํฐ๋ฆฌ๊ฐ ํญ์ ์ญ์ ๋ฉ๋๋ค.after=False
: ํ ์คํธ ์ข ๋ฃ ์ ์์ ๋๋ ํฐ๋ฆฌ๊ฐ ํญ์ ๊ทธ๋๋ก ์ ์ง๋ฉ๋๋ค.
rm -r
์ ํด๋นํ๋ ๋ช
๋ น์ ์์ ํ๊ฒ ์คํํ๊ธฐ ์ํด,
๋ช
์์ ์ธ tmp_dir
์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ํ๋ก์ ํธ ์ ์ฅ์ ์ฒดํฌ ์์์ ํ์ ๋๋ ํฐ๋ฆฌ๋ง ํ์ฉ๋ฉ๋๋ค.
๋ฐ๋ผ์ ์ค์๋ก /tmp
๊ฐ ์๋ ์ค์ํ ํ์ผ ์์คํ
์ ์ผ๋ถ๊ฐ ์ญ์ ๋์ง ์๋๋ก ํญ์ ./
๋ก ์์ํ๋ ๊ฒฝ๋ก๋ฅผ ์ ๋ฌํด์ผ ํฉ๋๋ค.
๊ฐ ํ ์คํธ๋ ์ฌ๋ฌ ๊ฐ์ ์์ ๋๋ ํฐ๋ฆฌ๋ฅผ ๋ฑ๋กํ ์ ์์ผ๋ฉฐ, ๋ณ๋๋ก ์์ฒญํ์ง ์๋ ํ ๋ชจ๋ ์๋์ผ๋ก ์ ๊ฑฐ๋ฉ๋๋ค.
์์ sys.path ์ค๋ฒ๋ผ์ด๋[[temporary-sys.path-override]]
sys.path
๋ฅผ ๋ค๋ฅธ ํ
์คํธ๋ก ์์๋ก ์ค๋ฒ๋ผ์ด๋ํ๊ธฐ ์ํด ์๋ฅผ ๋ค์ด ExtendSysPath
์ปจํ
์คํธ ๊ด๋ฆฌ์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
import os
from transformers.testing_utils import ExtendSysPath
bindir = os.path.abspath(os.path.dirname(__file__))
with ExtendSysPath(f"{bindir}/.."):
from test_trainer import TrainerIntegrationCommon # noqa
ํ ์คํธ ๊ฑด๋๋ฐ๊ธฐ[[skipping-tests]]
์ด๊ฒ์ ๋ฒ๊ทธ๊ฐ ๋ฐ๊ฒฌ๋์ด ์๋ก์ด ํ
์คํธ๊ฐ ์์ฑ๋์์ง๋ง ์์ง ๊ทธ ๋ฒ๊ทธ๊ฐ ์์ ๋์ง ์์ ๊ฒฝ์ฐ์ ์ ์ฉํฉ๋๋ค.
์ด ํ
์คํธ๋ฅผ ์ฃผ ์ ์ฅ์์ ์ปค๋ฐํ๋ ค๋ฉด make test
์ค์ ๊ฑด๋๋ฐ๋๋ก ํด์ผ ํฉ๋๋ค.
๋ฐฉ๋ฒ:
skip์ ํ ์คํธ๊ฐ ์ผ๋ถ ์กฐ๊ฑด์ด ์ถฉ์กฑ๋ ๊ฒฝ์ฐ์๋ง ํต๊ณผ๋ ๊ฒ์ผ๋ก ์์๋๊ณ , ๊ทธ๋ ์ง ์์ผ๋ฉด pytest๊ฐ ์ ์ฒด ํ ์คํธ๋ฅผ ๊ฑด๋๋ฐ์ด์ผ ํจ์ ์๋ฏธํฉ๋๋ค. ์ผ๋ฐ์ ์ธ ์๋ก๋ Windows๊ฐ ์๋ ํ๋ซํผ์์ Windows ์ ์ฉ ํ ์คํธ๋ฅผ ๊ฑด๋๋ฐ๊ฑฐ๋ ์ธ๋ถ ๋ฆฌ์์ค(์๋ฅผ ๋ค์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค)์ ์์กดํ๋ ํ ์คํธ๋ฅผ ๊ฑด๋๋ฐ๋ ๊ฒ์ด ์์ต๋๋ค.
xfail์ ํ ์คํธ๊ฐ ํน์ ํ ์ด์ ๋ก ์ธํด ์คํจํ ๊ฒ์ผ๋ก ์์ํ๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. ์ผ๋ฐ์ ์ธ ์๋ก๋ ์์ง ๊ตฌํ๋์ง ์์ ๊ธฐ๋ฅ์ด๋ ์์ง ์์ ๋์ง ์์ ๋ฒ๊ทธ์ ํ ์คํธ๊ฐ ์์ต๋๋ค.
xfail
๋ก ํ์๋ ํ ์คํธ๊ฐ ์์๋๋ก ์คํจํ์ง ์๊ณ ํต๊ณผ๋ ๊ฒฝ์ฐ, ์ด๊ฒ์ xpass์ด๋ฉฐ ํ ์คํธ ๊ฒฐ๊ณผ ์์ฝ์ ๊ธฐ๋ก๋ฉ๋๋ค.
๋ ๊ฐ์ง ์ค์ํ ์ฐจ์ด์ ์ค ํ๋๋ skip
์ ํ
์คํธ๋ฅผ ์คํํ์ง ์์ง๋ง xfail
์ ์คํํ๋ค๋ ๊ฒ์
๋๋ค.
๋ฐ๋ผ์ ์ค๋ฅ๊ฐ ์๋ ์ฝ๋๊ฐ ์ผ๋ถ ํ
์คํธ์ ์ํฅ์ ๋ฏธ์น ์ ์๋ ๊ฒฝ์ฐ xfail
์ ์ฌ์ฉํ์ง ๋ง์ธ์.
๊ตฌํ[[implementation]]
- ์ ์ฒด ํ ์คํธ๋ฅผ ๋ฌด์กฐ๊ฑด ๊ฑด๋๋ฐ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ํ ์ ์์ต๋๋ค:
@unittest.skip("this bug needs to be fixed")
def test_feature_x():
๋๋ pytest๋ฅผ ํตํด:
@pytest.mark.skip(reason="this bug needs to be fixed")
๋๋ xfail
๋ฐฉ์์ผ๋ก:
@pytest.mark.xfail
def test_feature_x():
- ํ ์คํธ ๋ด๋ถ์์ ๋ด๋ถ ํ์ธ์ ๋ฐ๋ผ ํ ์คํธ๋ฅผ ๊ฑด๋๋ฐ๋ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
def test_feature_x():
if not has_something():
pytest.skip("unsupported configuration")
๋๋ ๋ชจ๋ ์ ์ฒด:
import pytest
if not pytest.config.getoption("--custom-flag"):
pytest.skip("--custom-flag is missing, skipping tests", allow_module_level=True)
๋๋ xfail
๋ฐฉ์์ผ๋ก:
def test_feature_x():
pytest.xfail("expected to fail until bug XYZ is fixed")
- import๊ฐ missing๋ ๋ชจ๋์ด ์์ ๋ ๊ทธ ๋ชจ๋์ ๋ชจ๋ ํ ์คํธ๋ฅผ ๊ฑด๋๋ฐ๋ ๋ฐฉ๋ฒ:
docutils = pytest.importorskip("docutils", minversion="0.3")
- ์กฐ๊ฑด์ ๋ฐ๋ผ ํ ์คํธ๋ฅผ ๊ฑด๋๋ฐ๋ ๋ฐฉ๋ฒ:
@pytest.mark.skipif(sys.version_info < (3,6), reason="requires python3.6 or higher")
def test_feature_x():
๋๋:
@unittest.skipIf(torch_device == "cpu", "Can't do half precision")
def test_feature_x():
๋๋ ๋ชจ๋ ์ ์ฒด๋ฅผ ๊ฑด๋๋ฐ๋ ๋ฐฉ๋ฒ:
@pytest.mark.skipif(sys.platform == 'win32', reason="does not run on windows")
class TestClass():
def test_feature_x(self):
๋ณด๋ค ์์ธํ ์์ ๋ฐ ๋ฐฉ๋ฒ์ ์ฌ๊ธฐ์์ ํ์ธํ ์ ์์ต๋๋ค.
๋๋ฆฐ ํ ์คํธ[[slow-tests]]
ํ ์คํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ง์์ ์ผ๋ก ํ์ฅ๋๊ณ ์์ผ๋ฉฐ, ์ผ๋ถ ํ ์คํธ๋ ์คํํ๋ ๋ฐ ๋ช ๋ถ์ด ๊ฑธ๋ฆฝ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ฐ๋ฆฌ์๊ฒ๋ ํ ์คํธ ์ค์ํธ๊ฐ CI๋ฅผ ํตํด ์๋ฃ๋๊ธฐ๊น์ง ํ ์๊ฐ์ ๊ธฐ๋ค๋ฆด ์ฌ์ ๊ฐ ์์ต๋๋ค. ๋ฐ๋ผ์ ํ์ ํ ์คํธ๋ฅผ ์ํ ์ผ๋ถ ์์ธ๋ฅผ ์ ์ธํ๊ณ ๋๋ฆฐ ํ ์คํธ๋ ๋ค์๊ณผ ๊ฐ์ด ํ์ํด์ผ ํฉ๋๋ค.
from transformers.testing_utils import slow
@slow
def test_integration_foo():
@slow
๋ก ํ์๋ ํ
์คํธ๋ฅผ ์คํํ๋ ค๋ฉด RUN_SLOW=1
ํ๊ฒฝ ๋ณ์๋ฅผ ์ค์ ํ์ธ์. ์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
RUN_SLOW=1 pytest tests
@parameterized
์ ๊ฐ์ ๋ช ๊ฐ์ง ๋ฐ์ฝ๋ ์ดํฐ๋ ํ
์คํธ ์ด๋ฆ์ ๋ค์ ์์ฑํฉ๋๋ค.
๊ทธ๋ฌ๋ฏ๋ก @slow
์ ๋๋จธ์ง ๊ฑด๋๋ฐ๊ธฐ ๋ฐ์ฝ๋ ์ดํฐ @require_*
๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์๋๋๋ ค๋ฉด ๋ง์ง๋ง์ ๋์ด๋์ด์ผ ํฉ๋๋ค. ๋ค์์ ์ฌ๋ฐ๋ฅธ ์ฌ์ฉ ์์
๋๋ค.
@parameterized.expand(...)
@slow
def test_integration_foo():
์ด ๋ฌธ์์ ์ด๋ฐ๋ถ์ ์ค๋ช ๋ ๊ฒ์ฒ๋ผ ๋๋ฆฐ ํ ์คํธ๋ PR์ CI ํ์ธ์ด ์๋ ์์ฝ๋ ์ผ์ ๊ธฐ๋ฐ์ผ๋ก ์คํ๋ฉ๋๋ค. ๋ฐ๋ผ์ PR ์ ์ถ ์ค์ ์ผ๋ถ ๋ฌธ์ ๋ฅผ ๋์น ์ฑ๋ก ๋ณํฉ๋ ์ ์์ต๋๋ค. ์ด๋ฌํ ๋ฌธ์ ๋ค์ ๋ค์๋ฒ์ ์์ ๋ CI ์์ ์ค์ ๊ฐ์ง๋ฉ๋๋ค. ํ์ง๋ง PR์ ์ ์ถํ๊ธฐ ์ ์ ์์ ์ ์ปดํจํฐ์์ ๋๋ฆฐ ํ ์คํธ๋ฅผ ์คํํ๋ ๊ฒ ๋ํ ์ค์ํฉ๋๋ค.
๋๋ฆฐ ํ ์คํธ๋ก ํ์ํด์ผ ํ๋์ง ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ๋ ๋๋ต์ ์ธ ๊ฒฐ์ ๊ธฐ์ค์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
๋ง์ฝ ํ ์คํธ๊ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ด๋ถ ๊ตฌ์ฑ ์์ ์ค ํ๋์ ์ง์ค๋์ด ์๋ค๋ฉด(์: ๋ชจ๋ธ๋ง ํ์ผ, ํ ํฐํ ํ์ผ, ํ์ดํ๋ผ์ธ), ํด๋น ํ ์คํธ๋ฅผ ๋๋ฆฐ ํ ์คํธ ์ค์ํธ์์ ์คํํด์ผ ํฉ๋๋ค. ๋ง์ฝ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ค๋ฅธ ์ธก๋ฉด(์: ๋ฌธ์ ๋๋ ์์ )์ ์ง์ค๋์ด ์๋ค๋ฉด, ํด๋น ํ ์คํธ๋ฅผ ๋๋ฆฐ ํ ์คํธ ์ค์ํธ์์ ์คํํด์ผ ํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด ์ ๊ทผ ๋ฐฉ์์ ๋ณด์ํ๊ธฐ ์ํด ์์ธ๋ฅผ ๋ง๋ค์ด์ผ ํฉ๋๋ค.
- ๋ฌด๊ฑฐ์ด ๊ฐ์ค์น ์ธํธ๋ 50MB๋ณด๋ค ํฐ ๋ฐ์ดํฐ์ ์ ๋ค์ด๋ก๋ํด์ผ ํ๋ ๋ชจ๋ ํ ์คํธ(์: ๋ชจ๋ธ ํตํฉ ํ ์คํธ, ํ ํฌ๋์ด์ ํตํฉ ํ ์คํธ, ํ์ดํ๋ผ์ธ ํตํฉ ํ ์คํธ)๋ฅผ ๋๋ฆฐ ํ ์คํธ๋ก ์ค์ ํด์ผ ํฉ๋๋ค. ์๋ก์ด ๋ชจ๋ธ์ ์ถ๊ฐํ๋ ๊ฒฝ์ฐ ํตํฉ ํ ์คํธ์ฉ์ผ๋ก ๋ฌด์์ ๊ฐ์ค์น๋ก ์์ ๋ฒ์ ์ ๋ง๋ค์ด ํ๋ธ์ ์ ๋ก๋ํด์ผ ํฉ๋๋ค. ์ด ๋ด์ฉ์ ์๋ ๋จ๋ฝ์์ ์ค๋ช ๋ฉ๋๋ค.
- ํน๋ณํ ๋น ๋ฅด๊ฒ ์คํ๋๋๋ก ์ต์ ํ๋์ง ์์ ํ์ต์ ์ํํด์ผ ํ๋ ํ ์คํธ๋ ๋๋ฆฐ ํ ์คํธ๋ก ์ค์ ํด์ผ ํฉ๋๋ค.
- ๋๋ฆฌ์ง ์์์ผ ํ ํ
์คํธ ์ค ์ผ๋ถ๊ฐ ๊ทน๋๋ก ๋๋ฆฐ ๊ฒฝ์ฐ
์์ธ๋ฅผ ๋์
ํ๊ณ ์ด๋ฅผ
@slow
๋ก ์ค์ ํ ์ ์์ต๋๋ค. ๋์ฉ๋ ํ์ผ์ ๋์คํฌ์ ์ ์ฅํ๊ณ ๋ถ๋ฌ์ค๋ ์๋ ๋ชจ๋ธ๋ง ํ ์คํธ๋@slow
์ผ๋ก ํ์๋ ํ ์คํธ์ ์ข์ ์์ ๋๋ค. - CI์์ 1์ด ์ด๋ด์ ํ ์คํธ๊ฐ ์๋ฃ๋๋ ๊ฒฝ์ฐ(๋ค์ด๋ก๋ ํฌํจ)์๋ ๋๋ฆฐ ํ ์คํธ๊ฐ ์๋์ด์ผ ํฉ๋๋ค.
๋๋ฆฐ ํ
์คํธ๊ฐ ์๋ ๊ฒฝ์ฐ์๋ ๋ค์ํ ๋ด๋ถ๋ฅผ ์์ ํ ์ปค๋ฒํ๋ฉด์ ๋น ๋ฅด๊ฒ ์ ์ง๋์ด์ผ ํฉ๋๋ค.
์๋ฅผ ๋ค์ด, ๋ฌด์์ ๊ฐ์ค์น๋ฅผ ์ฌ์ฉํ์ฌ ํน๋ณํ ์์ฑ๋ ์์ ๋ชจ๋ธ๋ก ํ
์คํธํ๋ฉด ์๋นํ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ์ป์ ์ ์์ต๋๋ค.
์ด๋ฌํ ๋ชจ๋ธ์ ์ต์ํ์ ๋ ์ด์ด ์(์: 2), ์ดํ ํฌ๊ธฐ(์: 1000) ๋ฑ์ ์์๋ง ๊ฐ์ง๋๋ค. ๊ทธ๋ฐ ๋ค์ @slow
ํ
์คํธ๋ ๋ํ ๋๋ฆฐ ๋ชจ๋ธ์ ์ฌ์ฉํ์ฌ ์ ์ฑ์ ์ธ ํ
์คํธ๋ฅผ ์ํํ ์ ์์ต๋๋ค.
์ด๋ฌํ ์์ ๋ชจ๋ธ์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ํ์ธํ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด tiny ๋ชจ๋ธ์ ์ฐพ์๋ณด์ธ์.
grep tiny tests examples
๋ค์์ ์์ ๋ชจ๋ธstas/tiny-wmt19-en-de์ ๋ง๋ script ์์์ ๋๋ค. ํน์ ๋ชจ๋ธ์ ์ํคํ ์ฒ์ ๋ง๊ฒ ์ฝ๊ฒ ์กฐ์ ํ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด ๋์ฉ๋ ๋ชจ๋ธ์ ๋ค์ด๋ก๋ํ๋ ๊ฒฝ์ฐ ๋ฐํ์์ ์๋ชป ์ธก์ ํ๊ธฐ ์ฝ์ง๋ง,
๋ก์ปฌ์์ ํ
์คํธํ๋ฉด ๋ค์ด๋ก๋ํ ํ์ผ์ด ์บ์๋์ด ๋ค์ด๋ก๋ ์๊ฐ์ด ์ธก์ ๋์ง ์์ต๋๋ค.
๋์ CI ๋ก๊ทธ์ ์คํ ์๋ ๋ณด๊ณ ์๋ฅผ ํ์ธํ์ธ์(pytest --durations=0 tests
์ ์ถ๋ ฅ).
์ด ๋ณด๊ณ ์๋ ๋๋ฆฐ ์ด์๊ฐ์ผ๋ก ํ์๋์ง ์๊ฑฐ๋ ๋น ๋ฅด๊ฒ ๋ค์ ์์ฑํด์ผ ํ๋ ๋๋ฆฐ ์ด์๊ฐ์ ์ฐพ๋ ๋ฐ๋ ์ ์ฉํฉ๋๋ค. CI์์ ํ ์คํธ ์ค์ํธ๊ฐ ๋๋ ค์ง๊ธฐ ์์ํ๋ฉด ์ด ๋ณด๊ณ ์์ ๋งจ ์ ๋ชฉ๋ก์ ๊ฐ์ฅ ๋๋ฆฐ ํ ์คํธ๊ฐ ํ์๋ฉ๋๋ค.
stdout/stderr ์ถ๋ ฅ ํ ์คํธ[[testing-the-stdout/stderr-output]]
stdout
๋ฐ/๋๋ stderr
๋ก ์ฐ๋ ํจ์๋ฅผ ํ
์คํธํ๋ ค๋ฉด pytest
์ capsys ์์คํ
์ ์ฌ์ฉํ์ฌ ํด๋น ์คํธ๋ฆผ์ ์ก์ธ์คํ ์ ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ์ํํ ์ ์์ต๋๋ค.
import sys
def print_to_stdout(s):
print(s)
def print_to_stderr(s):
sys.stderr.write(s)
def test_result_and_stdout(capsys):
msg = "Hello"
print_to_stdout(msg)
print_to_stderr(msg)
out, err = capsys.readouterr() # ์บก์ฒ๋ ์ถ๋ ฅ ์คํธ๋ฆผ ์ฌ์ฉ
# ์ ํ ์ฌํญ: ์บก์ฒ๋ ์คํธ๋ฆผ ์ฌ์์ฑ
sys.stdout.write(out)
sys.stderr.write(err)
# ํ
์คํธ:
assert msg in out
assert msg in err
๊ทธ๋ฆฌ๊ณ , ๋ฌผ๋ก ๋๋ถ๋ถ์ ๊ฒฝ์ฐ์๋ stderr
๋ ์์ธ์ ์ผ๋ถ๋ก ์ ๊ณต๋ฉ๋๋ค.
๊ทธ๋ฌ๋ฏ๋ก ํด๋น ๊ฒฝ์ฐ์๋ try/except๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
def raise_exception(msg):
raise ValueError(msg)
def test_something_exception():
msg = "Not a good value"
error = ""
try:
raise_exception(msg)
except Exception as e:
error = str(e)
assert msg in error, f"{msg} is in the exception:\n{error}"
stdout
๋ฅผ ์บก์ฒํ๋ ๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ contextlib.redirect_stdout
๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์
๋๋ค.
from io import StringIO
from contextlib import redirect_stdout
def print_to_stdout(s):
print(s)
def test_result_and_stdout():
msg = "Hello"
buffer = StringIO()
with redirect_stdout(buffer):
print_to_stdout(msg)
out = buffer.getvalue()
# ์ ํ ์ฌํญ: ์บก์ฒ๋ ์คํธ๋ฆผ ์ฌ์์ฑ
sys.stdout.write(out)
# ํ
์คํธ:
assert msg in out
stdout
์บก์ฒ์ ๊ด๋ จ๋ ์ค์ํ ๋ฌธ์ ์ค ํ๋๋ ๋ณดํต print
์์ ์ด์ ์ ์ธ์๋ ๋ด์ฉ์ ์ฌ์ค์ ํ๋ \r
๋ฌธ์๊ฐ ํฌํจ๋ ์ ์๋ค๋ ๊ฒ์
๋๋ค.
pytest
์์๋ ๋ฌธ์ ๊ฐ ์์ง๋ง pytest -s
์์๋ ์ด๋ฌํ ๋ฌธ์๊ฐ ๋ฒํผ์ ํฌํจ๋๋ฏ๋ก
-s
๊ฐ ์๊ฑฐ๋ ์๋ ์ํ์์ ํ์คํธ๋ฅผ ์ํํ ์ ์์ผ๋ ค๋ฉด ์บก์ฒ๋ ์ถ๋ ฅ์ ๋ํด ์ถ๊ฐ์ ์ธ ์ ๋ฆฌ๊ฐ ํ์ํฉ๋๋ค.
์ด ๊ฒฝ์ฐ์๋ re.sub(r'~.*\r', '', buf, 0, re.M)
์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
ํ์ง๋ง ๋์ฐ๋ฏธ ์ปจํ
์คํธ ๊ด๋ฆฌ์ ๋ํผ๋ฅผ ์ฌ์ฉํ๋ฉด
์ถ๋ ฅ์ \r
์ด ํฌํจ๋์ด ์๋์ง์ ์ฌ๋ถ์ ๊ด๊ณ์์ด ๋ชจ๋ ๊ฒ์ ์๋์ผ๋ก ์ฒ๋ฆฌํ๋ฏ๋ก ํธ๋ฆฌํฉ๋๋ค.
from transformers.testing_utils import CaptureStdout
with CaptureStdout() as cs:
function_that_writes_to_stdout()
print(cs.out)
๋ค์์ ์ ์ฒด ํ ์คํธ ์์ ์ ๋๋ค.
from transformers.testing_utils import CaptureStdout
msg = "Secret message\r"
final = "Hello World"
with CaptureStdout() as cs:
print(msg + final)
assert cs.out == final + "\n", f"captured: {cs.out}, expecting {final}"
stderr
๋ฅผ ์บก์ฒํ๊ณ ์ถ๋ค๋ฉด, ๋์ CaptureStderr
ํด๋์ค๋ฅผ ์ฌ์ฉํ์ธ์.
from transformers.testing_utils import CaptureStderr
with CaptureStderr() as cs:
function_that_writes_to_stderr()
print(cs.err)
๋ ์คํธ๋ฆผ์ ๋์์ ์บก์ฒํด์ผ ํ๋ค๋ฉด, ๋ถ๋ชจ CaptureStd
ํด๋์ค๋ฅผ ์ฌ์ฉํ์ธ์.
from transformers.testing_utils import CaptureStd
with CaptureStd() as cs:
function_that_writes_to_stdout_and_stderr()
print(cs.err, cs.out)
๋ํ, ํ ์คํธ์ ๋๋ฒ๊น ์ ์ง์ํ๊ธฐ ์ํด ์ด๋ฌํ ์ปจํ ์คํธ ๊ด๋ฆฌ์๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ปจํ ์คํธ์์ ์ข ๋ฃํ ๋ ์บก์ฒ๋ ์คํธ๋ฆผ์ ์๋์ผ๋ก ๋ค์ ์คํํฉ๋๋ค.
๋ก๊ฑฐ ์คํธ๋ฆผ ์บก์ฒ[[capturing-logger-stream]]
๋ก๊ฑฐ ์ถ๋ ฅ์ ๊ฒ์ฆํด์ผ ํ๋ ๊ฒฝ์ฐ CaptureLogger
๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
from transformers import logging
from transformers.testing_utils import CaptureLogger
msg = "Testing 1, 2, 3"
logging.set_verbosity_info()
logger = logging.get_logger("transformers.models.bart.tokenization_bart")
with CaptureLogger(logger) as cl:
logger.info(msg)
assert cl.out, msg + "\n"
ํ๊ฒฝ ๋ณ์๋ฅผ ์ด์ฉํ์ฌ ํ ์คํธ[[testing-with-environment-variables]]
ํน์ ํ
์คํธ์ ํ๊ฒฝ ๋ณ์ ์ํฅ์ ๊ฒ์ฆํ๋ ค๋ฉด
transformers.testing_utils.mockenv
๋ผ๋ ๋์ฐ๋ฏธ ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
from transformers.testing_utils import mockenv
class HfArgumentParserTest(unittest.TestCase):
@mockenv(TRANSFORMERS_VERBOSITY="error")
def test_env_override(self):
env_level_str = os.getenv("TRANSFORMERS_VERBOSITY", None)
์ผ๋ถ ๊ฒฝ์ฐ์๋ ์ธ๋ถ ํ๋ก๊ทธ๋จ์ ํธ์ถํด์ผํ ์๋ ์๋๋ฐ, ์ด ๋์๋ ์ฌ๋ฌ ๊ฐ์ ๋ก์ปฌ ๊ฒฝ๋ก๋ฅผ ํฌํจํ๋ os.environ
์์ PYTHONPATH
์ ์ค์ ์ด ํ์ํฉ๋๋ค.
ํฌํผ ํด๋์ค transformers.test_utils.TestCasePlus
๊ฐ ๋์์ด ๋ฉ๋๋ค:
from transformers.testing_utils import TestCasePlus
class EnvExampleTest(TestCasePlus):
def test_external_prog(self):
env = self.get_env()
# ์ด์ `env`๋ฅผ ์ฌ์ฉํ์ฌ ์ธ๋ถ ํ๋ก๊ทธ๋จ ํธ์ถ
ํ
์คํธ ํ์ผ์ด tests
ํ
์คํธ ์ค์ํธ ๋๋ examples
์ ์๋์ง์ ๋ฐ๋ผ
env[PYTHONPATH]
๊ฐ ๋ ๋๋ ํฐ๋ฆฌ ์ค ํ๋๋ฅผ ํฌํจํ๋๋ก ์ค์ ๋๋ฉฐ,
ํ์ฌ ์ ์ฅ์์ ๋ํด ํ
์คํธ๊ฐ ์ํ๋๋๋ก src
๋๋ ํฐ๋ฆฌ๋ ํฌํจ๋ฉ๋๋ค.
ํ
์คํธ ํธ์ถ ์ด์ ์ ์ค์ ๋ ๊ฒฝ์ฐ์๋ env[PYTHONPATH]
๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํฉ๋๋ค.
์ด ํฌํผ ๋ฉ์๋๋ os.environ
๊ฐ์ฒด์ ์ฌ๋ณธ์ ์์ฑํ๋ฏ๋ก ์๋ณธ์ ๊ทธ๋๋ก ์ ์ง๋ฉ๋๋ค.
์ฌํ ๊ฐ๋ฅํ ๊ฒฐ๊ณผ ์ป๊ธฐ[[getting-reproducible-results]]
์ผ๋ถ ์ํฉ์์ ํ ์คํธ์์ ์์์ฑ์ ์ ๊ฑฐํ์ฌ ๋์ผํ๊ฒ ์ฌํ ๊ฐ๋ฅํ ๊ฒฐ๊ณผ๋ฅผ ์ป๊ณ ์ถ์ ์ ์์ต๋๋ค. ์ด๋ฅผ ์ํด์๋ ๋ค์๊ณผ ๊ฐ์ด ์๋๋ฅผ ๊ณ ์ ํด์ผ ํฉ๋๋ค.
seed = 42
# ํ์ด์ฌ RNG
import random
random.seed(seed)
# ํ์ดํ ์น RNG
import torch
torch.manual_seed(seed)
torch.backends.cudnn.deterministic = True
if torch.cuda.is_available():
torch.cuda.manual_seed_all(seed)
# ๋ํ์ด RNG
import numpy as np
np.random.seed(seed)
# ํ
์ํ๋ก RNG
tf.random.set_seed(seed)
ํ ์คํธ ๋๋ฒ๊น [[debugging tests]]
๊ฒฝ๊ณ ๊ฐ ์๋ ๊ณณ์์ ๋๋ฒ๊ฑฐ๋ฅผ ์์ํ๋ ค๋ฉด ๋ค์์ ์ํํ์ธ์.
pytest tests/utils/test_logging.py -W error::UserWarning --pdb
Github Actions ์ํฌํ๋ก์ฐ ์์ ์ฒ๋ฆฌ[[working-with-github-actions-workflows]]
์ ํ ํธ์ ์ํฌํ๋ก์ฐ CI ์์ ์ ํธ๋ฆฌ๊ฑฐํ๋ ค๋ฉด, ๋ค์์ ์ํํด์ผ ํฉ๋๋ค.
transformers
์๋ณธ์์ ์ ๋ธ๋์น๋ฅผ ๋ง๋ญ๋๋ค(ํฌํฌ๊ฐ ์๋๋๋ค!).- ๋ธ๋์น ์ด๋ฆ์
ci_
๋๋ci-
๋ก ์์ํด์ผ ํฉ๋๋ค(main
๋ ํธ๋ฆฌ๊ฑฐํ์ง๋งmain
์์๋ PR์ ํ ์ ์์ต๋๋ค). ๋ํ ํน์ ๊ฒฝ๋ก์ ๋ํด์๋ง ํธ๋ฆฌ๊ฑฐ๋๋ฏ๋ก ์ด ๋ฌธ์๊ฐ ์์ฑ๋ ํ์ ๋ณ๊ฒฝ๋ ๋ด์ฉ์ ์ฌ๊ธฐ์ *push:*์์ ํ์ธํ ์ ์์ต๋๋ค. - ์ด ๋ธ๋์น์์ PR์ ์์ฑํฉ๋๋ค
- ๊ทธ๋ฐ ๋ค์ ์ฌ๊ธฐ์์ ์์ ์ด ๋ํ๋๋์ง ํ์ธํ ์ ์์ต๋๋ค. ๋ฐฑ๋ก๊ทธ๊ฐ ์๋ ๊ฒฝ์ฐ, ๋ฐ๋ก ์คํ๋์ง ์์ ์๋ ์์ต๋๋ค.
์คํ์ ์ธ CI ๊ธฐ๋ฅ ํ ์คํธ[[testing-Experimental-CI-Features]]
CI ๊ธฐ๋ฅ์ ํ ์คํธํ๋ ๊ฒ์ ์ผ๋ฐ CI ์๋์ ๋ฐฉํด๊ฐ ๋ ์ ์๊ธฐ ๋๋ฌธ์ ์ ์ฌ์ ์ผ๋ก ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ ์๋ก์ด CI ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ ๊ฒฝ์ฐ ๋ค์๊ณผ ๊ฐ์ด ์ํํด์ผ ํฉ๋๋ค.
- ํ ์คํธํด์ผ ํ ๋ด์ฉ์ ํ ์คํธํ๋ ์๋ก์ด ์ ์ฉ ์์ ์ ์์ฑํฉ๋๋ค.
- ์๋ก์ด ์์ ์ ํญ์ ์ฑ๊ณตํด์ผ๋ง ๋ น์ โ๋ฅผ ๋ฐ์ ์ ์์ต๋๋ค(์๋์ ์์ธํ ๋ด์ฉ์ด ์์ต๋๋ค).
- ๋ค์ํ PR ์ ํ์ ๋ํ ํ์ธ์ ์ํด (์ฌ์ฉ์ ํฌํฌ ๋ธ๋์น, ํฌํฌ๋์ง ์์ ๋ธ๋์น, github.com UI ์ง์ ํ์ผ ํธ์ง์์ ์์ฑ๋ ๋ธ๋์น, ๊ฐ์ ํธ์ ๋ฑ PR์ ์ ํ์ ์์ฃผ ๋ค์ํฉ๋๋ค.) ๋ฉฐ์น ๋์ ์คํ ์์ ์ ๋ก๊ทธ๋ฅผ ๋ชจ๋ํฐ๋งํ๋ฉด์ ์คํํด๋ด ๋๋ค. (์๋์ ์ผ๋ก ํญ์ ๋ น์์ ํ์ํ๋ฏ๋ก ์์ ์ ์ฒด๊ฐ ๋ น์์ ์๋๋ผ๋ ์ ์ ์ ์ํฉ๋๋ค.)
- ๋ชจ๋ ๊ฒ์ด ์์ ์ ์ธ์ง ํ์ธํ ํ, ์๋ก์ด ๋ณ๊ฒฝ ์ฌํญ์ ๊ธฐ์กด ์์ ์ ๋ณํฉํฉ๋๋ค.
์ด๋ ๊ฒ ํ๋ฉด CI ๊ธฐ๋ฅ ์์ฒด์ ๋ํ ์คํ์ด ์ผ๋ฐ ์์ ํ๋ฆ์ ๋ฐฉํด๊ฐ ๋์ง ์์ต๋๋ค.
๊ทธ๋ฌ๋ ์๋ก์ด CI ๊ธฐ๋ฅ์ด ๊ฐ๋ฐ ์ค์ธ ๋์, ํญ์ ์ฑ๊ณตํ๋๋ก ํ ์ ์๋ ๋ฐฉ๋ฒ์ ๋ฌด์์ผ๊น์?
TravisCI์ ๊ฐ์ ์ผ๋ถ CI๋ ignore-step-failure
๋ฅผ ์ง์ํ๋ฉฐ ์ ์ฒด ์์
์ ์ฑ๊ณตํ ๊ฒ์ผ๋ก ๋ณด๊ณ ํ์ง๋ง,
ํ์ฌ ์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ๋ CircleCI์ Github Actions๋ ์ด๋ฅผ ์ง์ํ์ง ์์ต๋๋ค.
๋ฐ๋ผ์ ๋ค์๊ณผ ๊ฐ์ ํด๊ฒฐ์ฑ ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
- bash ์คํฌ๋ฆฝํธ์์ ๊ฐ๋ฅํ ๋ง์ ์ค๋ฅ๋ฅผ ์ต์ ํ๊ธฐ ์ํด ์คํ ๋ช
๋ น์ ์์ ๋ถ๋ถ์
set +euo pipefail
์ ์ถ๊ฐํฉ๋๋ค. - ๋ง์ง๋ง ๋ช
๋ น์ ๋ฐ๋์ ์ฑ๊ณตํด์ผ ํฉ๋๋ค.
echo "done"
๋๋true
๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค.
์์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- run:
name: run CI experiment
command: |
set +euo pipefail
echo "setting run-all-despite-any-errors-mode"
this_command_will_fail
echo "but bash continues to run"
# emulate another failure
false
# but the last command must be a success
echo "during experiment do not remove: reporting success to CI, even if there were failures"
๊ฐ๋จํ ๋ช ๋ น์ ๊ฒฝ์ฐ ๋ค์๊ณผ ๊ฐ์ด ์ํํ ์๋ ์์ต๋๋ค.
cmd_that_may_fail || true
๊ฒฐ๊ณผ์ ๋ง์กฑํ ํ์๋ ๋ฌผ๋ก , ์คํ์ ์ธ ๋จ๊ณ ๋๋ ์์
์ ์ผ๋ฐ ์์
์ ๋๋จธ์ง ๋ถ๋ถ๊ณผ ํตํฉํ๋ฉด์
set +euo pipefail
๋๋ ๊ธฐํ ์ถ๊ฐํ ์์๋ฅผ ์ ๊ฑฐํ์ฌ
์คํ ์์
์ด ์ผ๋ฐ CI ์๋์ ๋ฐฉํด๋์ง ์๋๋ก ํด์ผ ํฉ๋๋ค.
์ด ์ ๋ฐ์ ์ธ ๊ณผ์ ์ ์คํ ๋จ๊ณ๊ฐ PR์ ์ ๋ฐ์ ์ธ ์ํ์ ์ํฅ์ ์ฃผ์ง ์๊ณ ์คํจํ๋๋ก
allow-failure
์ ๊ฐ์ ๊ธฐ๋ฅ์ ์ค์ ํ ์ ์๋ค๋ฉด ํจ์ฌ ๋ ์ฌ์ ์ ๊ฒ์
๋๋ค.
๊ทธ๋ฌ๋ ์์์ ์ธ๊ธํ ๋ฐ์ ๊ฐ์ด CircleCI์ Github Actions๋ ํ์ฌ ์ด๋ฌํ ๊ธฐ๋ฅ๋ค ์ง์ํ์ง ์์ต๋๋ค.
์ด ๊ธฐ๋ฅ์ ์ง์์ ์ํ ํฌํ์ ์ฐธ์ฌํ๊ณ CI ๊ด๋ จ ์ค๋ ๋๋ค์์ ์ด๋ฌํ ์ํฉ์ ํ์ธํ ์๋ ์์ต๋๋ค.