้กน็›ฎๆ–‡ไปถๅคน

ๆ–‡ไปถ
wehub-resource-sync e06fe8e8c6
Secret Leaks / trufflehog (push) Failing after 1s
Build documentation / build (push) Failing after 1s
Build documentation / build_other_lang (push) Failing after 0s
CodeQL Security Analysis / CodeQL Analysis (push) Failing after 0s
PR CI / pr-ci (push) Failing after 1s
Slow tests on important models (on Push - A10) / Get all modified files (push) Failing after 1s
Slow tests on important models (on Push - A10) / Model CI (push) Has been skipped
Self-hosted runner (benchmark) / Benchmark (aws-g5-4xlarge-cache) (push) Has been cancelled
New model PR merged notification / Notify new model (push) Has been cancelled
Update Transformers metadata / build_and_package (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 11:57:37 +08:00

1272 ่กŒ
50 KiB
Markdown

<!--Copyright 2020 The HuggingFace Team. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
โš ๏ธ Note that this file is in Markdown but contain specific syntax for our doc-builder (similar to MDX) that may not be
rendered properly in your Markdown viewer.
-->
# ํ…Œ์ŠคํŠธ[[testing]]
๋จผ์ € ๐Ÿค— Transformers ๋ชจ๋ธ์ด ์–ด๋–ป๊ฒŒ ํ…Œ์ŠคํŠธ๋˜๋Š”์ง€ ์‚ดํŽด๋ณด๊ณ , ์ƒˆ๋กœ์šด ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑ ๋ฐ ๊ธฐ์กด ํ…Œ์ŠคํŠธ๋ฅผ ๊ฐœ์„ ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ด…์‹œ๋‹ค.
์ด ์ €์žฅ์†Œ์—๋Š” 2๊ฐœ์˜ ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค:
1. `tests` - ์ผ๋ฐ˜ API์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ
2. `examples` - API์˜ ์ผ๋ถ€๊ฐ€ ์•„๋‹Œ ๋‹ค์–‘ํ•œ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ
## Transformers ํ…Œ์ŠคํŠธ ๋ฐฉ๋ฒ•[[how-transformers-are-tested]]
1. PR์ด ์ œ์ถœ๋˜๋ฉด 9๊ฐœ์˜ CircleCi ์ž‘์—…์œผ๋กœ ํ…Œ์ŠคํŠธ๊ฐ€ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น PR์— ๋Œ€ํ•ด ์ƒˆ๋กœ์šด ์ปค๋ฐ‹์ด ์ƒ์„ฑ๋  ๋•Œ๋งˆ๋‹ค ํ…Œ์ŠคํŠธ๋Š” ๋‹ค์‹œ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค. ์ด ์ž‘์—…๋“ค์€
์ด [config ํŒŒ์ผ](https://github.com/huggingface/transformers/tree/main/.circleci/config.yml)์— ์ •์˜๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ํ•„์š”ํ•˜๋‹ค๋ฉด
์‚ฌ์šฉ์ž์˜ ๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ ๋™์ผํ•˜๊ฒŒ ์žฌํ˜„ํ•ด ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ด CI ์ž‘์—…์€ `@slow` ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
2. [github actions](https://github.com/huggingface/transformers/actions)์— ์˜ํ•ด ์‹คํ–‰๋˜๋Š” ์ž‘์—…์€ 3๊ฐœ์ž…๋‹ˆ๋‹ค:
- [torch hub integration](https://github.com/huggingface/transformers/tree/main/.github/workflows/github-torch-hub.yml):
torch hub integration์ด ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
- [self-hosted (push)](https://github.com/huggingface/transformers/tree/main/.github/workflows/self-push.yml): `main` ๋ธŒ๋žœ์น˜์—์„œ ์ปค๋ฐ‹์ด ์—…๋ฐ์ดํŠธ๋œ ๊ฒฝ์šฐ์—๋งŒ GPU๋ฅผ ์ด์šฉํ•œ ๋น ๋ฅธ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
์ด๋Š” `src`, `tests`, `.github` ํด๋” ์ค‘ ํ•˜๋‚˜์— ์ฝ”๋“œ๊ฐ€ ์—…๋ฐ์ดํŠธ๋œ ๊ฒฝ์šฐ์—๋งŒ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
(model card, notebook, ๊ธฐํƒ€ ๋“ฑ๋“ฑ์„ ์ถ”๊ฐ€ํ•œ ๊ฒฝ์šฐ ์‹คํ–‰๋˜์ง€ ์•Š๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด์„œ์ž…๋‹ˆ๋‹ค)
- [self-hosted runner](https://github.com/huggingface/transformers/tree/main/.github/workflows/self-scheduled.yml): `tests` ๋ฐ `examples`์—์„œ
GPU๋ฅผ ์ด์šฉํ•œ ์ผ๋ฐ˜ ํ…Œ์ŠคํŠธ, ๋А๋ฆฐ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
```bash
RUN_SLOW=1 pytest tests/
RUN_SLOW=1 pytest examples/
```
๊ฒฐ๊ณผ๋Š” [์—ฌ๊ธฐ](https://github.com/huggingface/transformers/actions)์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
## ํ…Œ์ŠคํŠธ ์‹คํ–‰[[running-tests]]
### ์‹คํ–‰ํ•  ํ…Œ์ŠคํŠธ ์„ ํƒ[[choosing-which-tests-to-run]]
์ด ๋ฌธ์„œ๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.
๋ชจ๋“  ๋‚ด์šฉ์„ ์ฝ์€ ํ›„์—๋„, ๋” ์ž์„ธํ•œ ๋‚ด์šฉ์ด ํ•„์š”ํ•˜๋‹ค๋ฉด [์—ฌ๊ธฐ](https://docs.pytest.org/en/latest/usage.html)์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋‹ค์Œ์€ ๊ฐ€์žฅ ์œ ์šฉํ•œ ํ…Œ์ŠคํŠธ ์‹คํ–‰ ๋ฐฉ๋ฒ• ๋ช‡ ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค.
๋ชจ๋‘ ์‹คํ–‰:
```console
pytest
```
๋˜๋Š”:
```bash
make test
```
ํ›„์ž๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์˜๋ฉ๋‹ˆ๋‹ค:
```bash
python -m pytest -n auto --dist=loadfile -s -v ./tests/
```
์œ„์˜ ๋ช…๋ น์–ด๋Š” pytest์—๊ฒŒ ์•„๋ž˜์˜ ๋‚ด์šฉ์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค:
- ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ CPU ์ฝ”์–ด ์ˆ˜๋งŒํผ ํ…Œ์ŠคํŠธ ํ”„๋กœ์„ธ์Šค๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. (RAM์ด ์ถฉ๋ถ„ํ•˜์ง€ ์•Š๋‹ค๋ฉด, ํ…Œ์ŠคํŠธ ํ”„๋กœ์„ธ์Šค ์ˆ˜๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!)
- ๋™์ผํ•œ ํŒŒ์ผ์˜ ๋ชจ๋“  ํ…Œ์ŠคํŠธ๋Š” ๋™์ผํ•œ ํ…Œ์ŠคํŠธ ํ”„๋กœ์„ธ์Šค์—์„œ ์‹คํ–‰๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
- ์ถœ๋ ฅ์„ ์บก์ฒ˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
- ์ž์„ธํ•œ ๋ชจ๋“œ๋กœ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
### ๋ชจ๋“  ํ…Œ์ŠคํŠธ ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ[[getting-the-list-of-all-tests]]
ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ์˜ ๋ชจ๋“  ํ…Œ์ŠคํŠธ:
```bash
pytest --collect-only -q
```
์ง€์ •๋œ ํ…Œ์ŠคํŠธ ํŒŒ์ผ์˜ ๋ชจ๋“  ํ…Œ์ŠคํŠธ:
```bash
pytest tests/test_optimization.py --collect-only -q
```
### ํŠน์ • ํ…Œ์ŠคํŠธ ๋ชจ๋“ˆ ์‹คํ–‰[[run-a-specific-test-module]]
๊ฐœ๋ณ„ ํ…Œ์ŠคํŠธ ๋ชจ๋“ˆ ์‹คํ–‰ํ•˜๊ธฐ:
```bash
pytest tests/utils/test_logging.py
```
### ํŠน์ • ํ…Œ์ŠคํŠธ ์‹คํ–‰[[run-specific-tests]]
๋Œ€๋ถ€๋ถ„์˜ ํ…Œ์ŠคํŠธ ๋‚ด๋ถ€์—์„œ๋Š” unittest๊ฐ€ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ํŠน์ • ํ•˜์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ ค๋ฉด ํ•ด๋‹น ํ…Œ์ŠคํŠธ๋ฅผ ํฌํ•จํ•˜๋Š” unittest ํด๋ž˜์Šค์˜ ์ด๋ฆ„์„ ์•Œ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
```bash
pytest tests/test_optimization.py::OptimizationTest::test_adam_w
```
์œ„์˜ ๋ช…๋ น์–ด์˜ ์˜๋ฏธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:
- `tests/test_optimization.py` - ํ…Œ์ŠคํŠธ๊ฐ€ ์žˆ๋Š” ํŒŒ์ผ
- `OptimizationTest` - ํด๋ž˜์Šค์˜ ์ด๋ฆ„
- `test_adam_w` - ํŠน์ • ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜์˜ ์ด๋ฆ„
ํŒŒ์ผ์— ์—ฌ๋Ÿฌ ํด๋ž˜์Šค๊ฐ€ ํฌํ•จ๋œ ๊ฒฝ์šฐ, ํŠน์ • ํด๋ž˜์Šค์˜ ํ…Œ์ŠคํŠธ๋งŒ ์‹คํ–‰ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:
```bash
pytest tests/test_optimization.py::OptimizationTest
```
์ด ๋ช…๋ น์–ด๋Š” ํ•ด๋‹น ํด๋ž˜์Šค ๋‚ด๋ถ€์˜ ๋ชจ๋“  ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
์•ž์—์„œ ์–ธ๊ธ‰ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ `OptimizationTest` ํด๋ž˜์Šค์— ํฌํ•จ๋œ ํ…Œ์ŠคํŠธ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
```bash
pytest tests/test_optimization.py::OptimizationTest --collect-only -q
```
ํ‚ค์›Œ๋“œ ํ‘œํ˜„์‹์„ ์‚ฌ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
`adam`์ด๋ผ๋Š” ์ด๋ฆ„์„ ํฌํ•จํ•˜๋Š” ํ…Œ์ŠคํŠธ๋งŒ ์‹คํ–‰ํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:
```bash
pytest -k adam tests/test_optimization.py
```
๋…ผ๋ฆฌ ์—ฐ์‚ฐ์ž `and`์™€ `or`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“  ํ‚ค์›Œ๋“œ๊ฐ€ ์ผ์น˜ํ•ด์•ผ ํ•˜๋Š”์ง€ ๋˜๋Š” ์–ด๋А ํ•˜๋‚˜๊ฐ€ ์ผ์น˜ํ•ด์•ผ ํ•˜๋Š”์ง€๋ฅผ ๋‚˜ํƒ€๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
`not`์€ ๋ถ€์ •ํ•  ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
`adam`์ด๋ผ๋Š” ์ด๋ฆ„์„ ํฌํ•จํ•˜์ง€ ์•Š๋Š” ๋ชจ๋“  ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:
```bash
pytest -k "not adam" tests/test_optimization.py
```
๋‘ ๊ฐ€์ง€ ํŒจํ„ด์„ ํ•˜๋‚˜๋กœ ๊ฒฐํ•ฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค:
```bash
pytest -k "ada and not adam" tests/test_optimization.py
```
์˜ˆ๋ฅผ ๋“ค์–ด `test_adafactor`์™€ `test_adam_w`๋ฅผ ๋ชจ๋‘ ์‹คํ–‰ํ•˜๋ ค๋ฉด ๋‹ค์Œ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
```bash
pytest -k "test_adam_w or test_adam_w" tests/test_optimization.py
```
์—ฌ๊ธฐ์„œ `or`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์— ์œ ์˜ํ•˜์„ธ์š”. ๋‘ ํ‚ค์›Œ๋“œ ์ค‘ ํ•˜๋‚˜๊ฐ€ ์ผ์น˜ํ•˜๋„๋ก ํ•˜๊ธฐ ์œ„ํ•œ ๋ชฉ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
๋‘ ํŒจํ„ด์ด ๋ชจ๋‘ ํฌํ•จ๋˜์–ด์•ผ ํ•˜๋Š” ํ…Œ์ŠคํŠธ๋งŒ ์‹คํ–‰ํ•˜๋ ค๋ฉด, `and`๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:
```bash
pytest -k "test and ada" tests/test_optimization.py
```
### `accelerate` ํ…Œ์ŠคํŠธ ์‹คํ–‰[[run-`accelerate`-tests]]
๋ชจ๋ธ์—์„œ `accelerate` ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•ด์•ผ ํ•  ๋•Œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด์„œ๋Š” ๋ช…๋ น์–ด์— `-m accelerate_tests`๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด, `OPT`์—์„œ ์ด๋Ÿฌํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:
```bash
RUN_SLOW=1 pytest -m accelerate_tests tests/models/opt/test_modeling_opt.py
```
### ๋ฌธ์„œ ํ…Œ์ŠคํŠธ ์‹คํ–‰[[run-documentation-tests]]
์˜ˆ์‹œ ๋ฌธ์„œ๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ์ง€ ํ…Œ์ŠคํŠธํ•˜๋ ค๋ฉด `doctests`๊ฐ€ ํ†ต๊ณผํ•˜๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด, [`WhisperModel.forward`'s docstring](https://github.com/huggingface/transformers/blob/main/src/transformers/models/whisper/modeling_whisper.py#L1017-L1035)๋ฅผ ์‚ฌ์šฉํ•ด ๋ด…์‹œ๋‹ค:
```python
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 ์˜ˆ์ œ๋ฅผ ์ž๋™์œผ๋กœ ํ…Œ์ŠคํŠธํ•˜๋ ค๋ฉด ๋‹ค์Œ ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค:
```bash
pytest --doctest-modules <path_to_file_or_dir>
```
ํŒŒ์ผ์˜ ํ™•์žฅ์ž๊ฐ€ markdown์ธ ๊ฒฝ์šฐ `--doctest-glob="*.md"` ์ธ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
### ์ˆ˜์ •๋œ ํ…Œ์ŠคํŠธ๋งŒ ์‹คํ–‰[[run-only-modified-tests]]
์ˆ˜์ •๋œ ํŒŒ์ผ ๋˜๋Š” ํ˜„์žฌ ๋ธŒ๋žœ์น˜ (Git ๊ธฐ์ค€)์™€ ๊ด€๋ จ๋œ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ ค๋ฉด [pytest-picked](https://github.com/anapaulagomes/pytest-picked)์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ด๋Š” ๋ณ€๊ฒฝํ•œ ๋‚ด์šฉ์ด ํ…Œ์ŠคํŠธ์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์•˜๋Š”์ง€ ๋น ๋ฅด๊ฒŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ์ข‹์€ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.
```bash
pip install pytest-picked
```
```bash
pytest --picked
```
์ˆ˜์ •๋˜์—ˆ์ง€๋งŒ, ์•„์ง ์ปค๋ฐ‹๋˜์ง€ ์•Š์€ ๋ชจ๋“  ํŒŒ์ผ ๋ฐ ํด๋”์—์„œ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
### ์†Œ์Šค ์ˆ˜์ • ์‹œ ์‹คํŒจํ•œ ํ…Œ์ŠคํŠธ ์ž๋™ ์žฌ์‹คํ–‰[[automatically-rerun-failed-tests-on-source-modification]]
[pytest-xdist](https://github.com/pytest-dev/pytest-xdist)๋Š” ๋ชจ๋“  ์‹คํŒจํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ๊ฐ์ง€ํ•˜๊ณ ,
ํŒŒ์ผ์„ ์ˆ˜์ •ํ•œ ํ›„์— ํŒŒ์ผ์„ ๊ณ„์† ์žฌ์‹คํ–‰ํ•˜์—ฌ ํ…Œ์ŠคํŠธ๊ฐ€ ์„ฑ๊ณตํ•  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋งค์šฐ ์œ ์šฉํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
๋”ฐ๋ผ์„œ ์ˆ˜์ •ํ•œ ๋‚ด์šฉ์„ ํ™•์ธํ•œ ํ›„ pytest๋ฅผ ๋‹ค์‹œ ์‹œ์ž‘ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
๋ชจ๋“  ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผ๋  ๋•Œ๊นŒ์ง€ ์ด ๊ณผ์ •์„ ๋ฐ˜๋ณตํ•œ ํ›„ ๋‹ค์‹œ ์ „์ฒด ์‹คํ–‰์ด ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค.
```bash
pip install pytest-xdist
```
์žฌ๊ท€์  ๋ชจ๋“œ์˜ ์‚ฌ์šฉ: `pytest -f` ๋˜๋Š” `pytest --looponfail`
ํŒŒ์ผ์˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์€ `looponfailroots` ๋ฃจํŠธ ๋””๋ ‰ํ„ฐ๋ฆฌ์™€ ํ•ด๋‹น ๋‚ด์šฉ์„ (์žฌ๊ท€์ ์œผ๋กœ) ํ™•์ธํ•˜์—ฌ ๊ฐ์ง€๋ฉ๋‹ˆ๋‹ค.
์ด ๊ฐ’์˜ ๊ธฐ๋ณธ๊ฐ’์ด ์ž‘๋™ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ,
`setup.cfg`์˜ ์„ค์ • ์˜ต์…˜์„ ๋ณ€๊ฒฝํ•˜์—ฌ ํ”„๋กœ์ ํŠธ์—์„œ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
```ini
[tool:pytest]
looponfailroots = transformers tests
```
๋˜๋Š” `pytest.ini`/`tox.ini`` ํŒŒ์ผ:
```ini
[pytest]
looponfailroots = transformers tests
```
์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ini-file์˜ ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ƒ๋Œ€์ ์œผ๋กœ ์ง€์ •๋œ ๊ฐ ๋””๋ ‰ํ„ฐ๋ฆฌ์—์„œ ํŒŒ์ผ ๋ณ€๊ฒฝ ์‚ฌํ•ญ๋งŒ ์ฐพ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
์ด ๊ธฐ๋Šฅ์„ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ๋Š” ๊ตฌํ˜„ ๋ฐฉ๋ฒ•์ธ [pytest-watch](https://github.com/joeyespo/pytest-watch)๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
### ํŠน์ • ํ…Œ์ŠคํŠธ ๋ชจ๋“ˆ ๊ฑด๋„ˆ๋›ฐ๊ธฐ[[skip-a-test-module]]
๋ชจ๋“  ํ…Œ์ŠคํŠธ ๋ชจ๋“ˆ์„ ์‹คํ–‰ํ•˜๋˜ ํŠน์ • ๋ชจ๋“ˆ์„ ์ œ์™ธํ•˜๋ ค๋ฉด, ์‹คํ–‰ํ•  ํ…Œ์ŠคํŠธ ๋ชฉ๋ก์„ ๋ช…์‹œ์ ์œผ๋กœ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด, `test_modeling_*.py` ํ…Œ์ŠคํŠธ๋ฅผ ์ œ์™ธํ•œ ๋ชจ๋“  ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ ค๋ฉด ๋‹ค์Œ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
```bash
pytest *ls -1 tests/*py | grep -v test_modeling*
```
### ์ƒํƒœ ์ดˆ๊ธฐํ™”[[clearing state]]
CI ๋นŒ๋“œ ๋ฐ (์†๋„์— ๋Œ€ํ•œ) ๊ฒฉ๋ฆฌ๊ฐ€ ์ค‘์š”ํ•œ ๊ฒฝ์šฐ, ์บ์‹œ๋ฅผ ์ง€์›Œ์•ผ ํ•ฉ๋‹ˆ๋‹ค:
```bash
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](https://github.com/ESSS/pytest-replay)๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋™์ผํ•œ ์ˆœ์„œ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ๋‹ค์‹œ ์‹คํ–‰ํ•ด์„œ
์‹คํŒจํ•˜๋Š” ์‹œํ€€์Šค๋ฅผ ์ตœ์†Œํ™”ํ•˜๋Š” ๋ฐ์— ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.
### ํ…Œ์ŠคํŠธ ์ˆœ์„œ์™€ ๋ฐ˜๋ณต[[test-order-and-repetition]]
์ž ์žฌ์ ์ธ ์ข…์†์„ฑ ๋ฐ ์ƒํƒœ ๊ด€๋ จ ๋ฒ„๊ทธ(tear down)๋ฅผ ๊ฐ์ง€ํ•˜๊ธฐ ์œ„ํ•ด
ํ…Œ์ŠคํŠธ๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ, ์—ฐ์†์œผ๋กœ, ๋ฌด์ž‘์œ„๋กœ ๋˜๋Š” ์„ธํŠธ๋กœ ๋ฐ˜๋ณตํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ  ์ง์ ‘์ ์ธ ์—ฌ๋Ÿฌ ๋ฒˆ์˜ ๋ฐ˜๋ณต์€ DL์˜ ๋ฌด์ž‘์œ„์„ฑ์— ์˜ํ•ด ๋ฐœ๊ฒฌ๋˜๋Š” ์ผ๋ถ€ ๋ฌธ์ œ๋ฅผ ๊ฐ์ง€ํ•˜๋Š” ๋ฐ์—๋„ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
#### ํ…Œ์ŠคํŠธ๋ฅผ ๋ฐ˜๋ณต[[repeat-tests]]
- [pytest-flakefinder](https://github.com/dropbox/pytest-flakefinder):
```bash
pip install pytest-flakefinder
```
๋ชจ๋“  ํ…Œ์ŠคํŠธ๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค(๊ธฐ๋ณธ๊ฐ’์€ 50๋ฒˆ):
```bash
pytest --flake-finder --flake-runs=5 tests/test_failing_test.py
```
<Tip>
์ด ํ”Œ๋Ÿฌ๊ทธ์ธ์€ `pytest-xdist`์˜ `-n` ํ”Œ๋ž˜๊ทธ์™€ ํ•จ๊ป˜ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
</Tip>
<Tip>
`pytest-repeat`๋ผ๋Š” ๋˜ ๋‹ค๋ฅธ ํ”Œ๋Ÿฌ๊ทธ์ธ๋„ ์žˆ์ง€๋งŒ `unittest`์™€ ํ•จ๊ป˜ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
</Tip>
#### ํ…Œ์ŠคํŠธ๋ฅผ ์ž„์˜์˜ ์ˆœ์„œ๋กœ ์‹คํ–‰[[run-tests-in-a-random-order]]
```bash
pip install pytest-random-order
```
์ค‘์š”: `pytest-random-order`๊ฐ€ ์„ค์น˜๋˜๋ฉด ํ…Œ์ŠคํŠธ๊ฐ€ ์ž๋™์œผ๋กœ ์ž„์˜์˜ ์ˆœ์„œ๋กœ ์„ž์ž…๋‹ˆ๋‹ค.
๊ตฌ์„ฑ ๋ณ€๊ฒฝ์ด๋‚˜ ์ปค๋งจ๋“œ ๋ผ์ธ ์˜ต์…˜์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
์•ž์„œ ์„ค๋ช…ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ ์ด๋ฅผ ํ†ตํ•ด ํ•œ ํ…Œ์ŠคํŠธ์˜ ์ƒํƒœ๊ฐ€ ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ์˜ ์ƒํƒœ์— ์˜ํ–ฅ์„ ๋ฏธ์น˜๋Š” ๊ฒฐํ•ฉ๋œ ํ…Œ์ŠคํŠธ๋ฅผ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
`pytest-random-order`๊ฐ€ ์„ค์น˜๋˜๋ฉด ํ•ด๋‹น ์„ธ์…˜์—์„œ ์‚ฌ์šฉ๋œ ๋žœ๋ค ์‹œ๋“œ๊ฐ€ ์ถœ๋ ฅ๋˜๋ฉฐ ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:
```bash
pytest tests
[...]
Using --random-order-bucket=module
Using --random-order-seed=573663
```
๋”ฐ๋ผ์„œ ํŠน์ • ์‹œํ€€์Šค๊ฐ€ ์‹คํŒจํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ์ •ํ™•ํ•œ ์‹œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์žฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:
```bash
pytest --random-order-seed=573663
[...]
Using --random-order-bucket=module
Using --random-order-seed=573663
```
์ •ํ™•ํžˆ ๋™์ผํ•œ ํ…Œ์ŠคํŠธ ๋ชฉ๋ก(๋˜๋Š” ๋ชฉ๋ก์ด ์—†์Œ)์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์—๋งŒ ์ •ํ™•ํ•œ ์ˆœ์„œ๋ฅผ ์žฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
๋ชฉ๋ก์„ ์ˆ˜๋™์œผ๋กœ ์ขํžˆ๊ธฐ ์‹œ์ž‘ํ•˜๋ฉด ๋” ์ด์ƒ ์‹œ๋“œ์— ์˜์กดํ•  ์ˆ˜ ์—†๊ณ  ์‹คํŒจํ–ˆ๋˜ ์ •ํ™•ํ•œ ์ˆœ์„œ๋กœ ์ˆ˜๋™์œผ๋กœ ๋ชฉ๋ก์„ ๋‚˜์—ดํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  `--random-order-bucket=none`์„ ์‚ฌ์šฉํ•˜์—ฌ pytest์—๊ฒŒ ์ˆœ์„œ๋ฅผ ์ž„์˜๋กœ ์„ค์ •ํ•˜์ง€ ์•Š๋„๋ก ์•Œ๋ ค์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:
```bash
pytest --random-order-bucket=none tests/test_a.py tests/test_c.py tests/test_b.py
```
๋ชจ๋“  ํ…Œ์ŠคํŠธ์— ๋Œ€ํ•ด ์„ž๊ธฐ๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:
```bash
pytest --random-order-bucket=none
```
๊ธฐ๋ณธ์ ์œผ๋กœ `--random-order-bucket=module`์ด ๋‚ด์žฌ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ, ๋ชจ๋“ˆ ์ˆ˜์ค€์—์„œ ํŒŒ์ผ์„ ์„ž์Šต๋‹ˆ๋‹ค.
๋˜ํ•œ `class`, `package`, `global` ๋ฐ `none` ์ˆ˜์ค€์—์„œ๋„ ์„ž์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ž์„ธํ•œ ๋‚ด์šฉ์€ ํ•ด๋‹น [๋ฌธ์„œ](https://github.com/jbasko/pytest-random-order)๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.
๋˜ ๋‹ค๋ฅธ ๋ฌด์ž‘์œ„ํ™”์˜ ๋Œ€์•ˆ์€ [`pytest-randomly`](https://github.com/pytest-dev/pytest-randomly)์ž…๋‹ˆ๋‹ค.
์ด ๋ชจ๋“ˆ์€ ๋งค์šฐ ์œ ์‚ฌํ•œ ๊ธฐ๋Šฅ/์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์ง€๋งŒ, `pytest-random-order`์— ์žˆ๋Š” ๋ฒ„ํ‚ท ๋ชจ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค.
์„ค์น˜ ํ›„์—๋Š” ์ž๋™์œผ๋กœ ์ ์šฉ๋˜๋Š” ๋ฌธ์ œ๋„ ๋™์ผํ•˜๊ฒŒ ๊ฐ€์ง‘๋‹ˆ๋‹ค.
### ์™ธ๊ด€๊ณผ ๋А๋‚Œ์„ ๋ณ€๊ฒฝ[[look-and-feel-variations]
#### pytest-sugar ์‚ฌ์šฉ[[pytest-sugar]]
[pytest-sugar](https://github.com/Frozenball/pytest-sugar)๋Š” ํ…Œ์ŠคํŠธ๊ฐ€ ๋ณด์—ฌ์ง€๋Š” ํ˜•ํƒœ๋ฅผ ๊ฐœ์„ ํ•˜๊ณ ,
์ง„ํ–‰ ์ƒํ™ฉ ๋ฐ”๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉฐ, ์‹คํŒจํ•œ ํ…Œ์ŠคํŠธ์™€ ๊ฒ€์ฆ์„ ์ฆ‰์‹œ ํ‘œ์‹œํ•˜๋Š” ํ”Œ๋Ÿฌ๊ทธ์ธ์ž…๋‹ˆ๋‹ค. ์„ค์น˜ํ•˜๋ฉด ์ž๋™์œผ๋กœ ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค.
```bash
pip install pytest-sugar
```
pytest-sugar ์—†์ด ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:
```bash
pytest -p no:sugar
```
๋˜๋Š” ์ œ๊ฑฐํ•˜์„ธ์š”.
#### ๊ฐ ํ•˜์œ„ ํ…Œ์ŠคํŠธ ์ด๋ฆ„๊ณผ ์ง„ํ–‰ ์ƒํ™ฉ ๋ณด๊ณ [[report-each-sub-test-name-and-its-progress]]
`pytest`๋ฅผ ํ†ตํ•ด ๋‹จ์ผ ๋˜๋Š” ๊ทธ๋ฃน์˜ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ฒฝ์šฐ(`pip install pytest-pspec` ์ดํ›„):
```bash
pytest --pspec tests/test_optimization.py
```
#### ์‹คํŒจํ•œ ํ…Œ์ŠคํŠธ ์ฆ‰์‹œ ํ‘œ์‹œ[[instantly-shows-failed-tests]]
[pytest-instafail](https://github.com/pytest-dev/pytest-instafail)์€ ํ…Œ์ŠคํŠธ ์„ธ์…˜์˜ ๋๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ 
์‹คํŒจ ๋ฐ ์˜ค๋ฅ˜๋ฅผ ์ฆ‰์‹œ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.
```bash
pip install pytest-instafail
```
```bash
pytest --instafail
```
### GPU ์‚ฌ์šฉ ์—ฌ๋ถ€[[to-GPU-or-not-to-GPU]]
GPU๊ฐ€ ํ™œ์„ฑํ™”๋œ ํ™˜๊ฒฝ์—์„œ, CPU ์ „์šฉ ๋ชจ๋“œ๋กœ ํ…Œ์ŠคํŠธํ•˜๋ ค๋ฉด `CUDA_VISIBLE_DEVICES=""`๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค:
```bash
CUDA_VISIBLE_DEVICES="" pytest tests/utils/test_logging.py
```
๋˜๋Š” ๋‹ค์ค‘ GPU๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ `pytest`์—์„œ ์‚ฌ์šฉํ•  GPU๋ฅผ ์ง€์ •ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด, GPU `0` ๋ฐ `1`์ด ์žˆ๋Š” ๊ฒฝ์šฐ ๋‹ค์Œ์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
```bash
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_xla` - `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๊ฐ€ ์„ค์น˜๋˜์–ด ์žˆ์„ ๋•Œ์—๋งŒ ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š” ํ…Œ์ŠคํŠธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:
```python no-style
@require_torch_multi_gpu
def test_example_with_multi_gpu():
```
์ด๋Ÿฌํ•œ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” ์ค‘์ฒฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด, ๋А๋ฆฐ ํ…Œ์ŠคํŠธ๋กœ ์ง„ํ–‰๋˜๊ณ  pytorch์—์„œ ์ ์–ด๋„ ํ•˜๋‚˜์˜ GPU๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
```python no-style
@require_torch_gpu
@slow
def test_example_slow_on_gpu():
```
`@parametrized`์™€ ๊ฐ™์€ ์ผ๋ถ€ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” ํ…Œ์ŠคํŠธ ์ด๋ฆ„์„ ๋‹ค์‹œ ์ž‘์„ฑํ•˜๊ธฐ ๋•Œ๋ฌธ์— `@require_*` ์Šคํ‚ต ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž‘๋™ํ•˜๋ ค๋ฉด ํ•ญ์ƒ ๋งจ ๋งˆ์ง€๋ง‰์— ๋‚˜์—ด๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
๋‹ค์Œ์€ ์˜ฌ๋ฐ”๋ฅธ ์‚ฌ์šฉ ์˜ˆ์ž…๋‹ˆ๋‹ค:
```python no-style
@parameterized.expand(...)
@require_torch_multi_gpu
def test_integration_foo():
```
`@pytest.mark.parametrize`์—๋Š” ์ด๋Ÿฌํ•œ ์ˆœ์„œ ๋ฌธ์ œ๋Š” ์—†์œผ๋ฏ€๋กœ ์ฒ˜์Œ ํ˜น์€ ๋งˆ์ง€๋ง‰์— ์œ„์น˜์‹œํ‚ฌ ์ˆ˜ ์žˆ๊ณ  ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ์—๋„ ์ž˜ ์ž‘๋™ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ unittest๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ์—๋งŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.
ํ…Œ์ŠคํŠธ ๋‚ด๋ถ€์—์„œ ๋‹ค์Œ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
- ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ GPU ์ˆ˜:
```python
from transformers.testing_utils import get_gpu_count
n_gpu = get_gpu_count() #torch์™€ tf์™€ ํ•จ๊ป˜ ์ž‘๋™
```
### ๋ถ„์‚ฐ ํ›ˆ๋ จ[[distributed-training]]
`pytest`๋Š” ๋ถ„์‚ฐ ํ›ˆ๋ จ์„ ์ง์ ‘์ ์œผ๋กœ ๋‹ค๋ฃจ์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค.
์ด๋ฅผ ์‹œ๋„ํ•˜๋ฉด ํ•˜์œ„ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๊ณ  `pytest`๋ผ๊ณ  ์ƒ๊ฐํ•˜๊ธฐ์— ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ๋ฅผ ๋ฐ˜๋ณตํ•ด์„œ ์‹คํ–‰ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
๊ทธ๋Ÿฌ๋‚˜ ์ผ๋ฐ˜ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ƒ์„ฑํ•œ ๋‹ค์Œ ์—ฌ๋Ÿฌ ์›Œ์ปค๋ฅผ ์ƒ์„ฑํ•˜๊ณ  IO ํŒŒ์ดํ”„๋ฅผ ๊ด€๋ฆฌํ•˜๋„๋ก ํ•˜๋ฉด ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.
๋‹ค์Œ์€ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํ…Œ์ŠคํŠธ์ž…๋‹ˆ๋‹ค:
- [test_trainer_distributed.py](https://github.com/huggingface/transformers/tree/main/tests/trainer/distributed/test_trainer_distributed.py)
- [test_deepspeed.py](https://github.com/huggingface/transformers/tree/main/tests/deepspeed/test_deepspeed.py)
์‹คํ–‰ ์ง€์ ์œผ๋กœ ๋ฐ”๋กœ ์ด๋™ํ•˜๋ ค๋ฉด, ํ•ด๋‹น ํ…Œ์ŠคํŠธ์—์„œ `execute_subprocess_async` ํ˜ธ์ถœ์„ ๊ฒ€์ƒ‰ํ•˜์„ธ์š”.
์ด๋Ÿฌํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ ค๋ฉด ์ ์–ด๋„ 2๊ฐœ์˜ GPU๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
```bash
CUDA_VISIBLE_DEVICES=0,1 RUN_SLOW=1 pytest -sv tests/test_trainer_distributed.py
```
### ์ถœ๋ ฅ ์บก์ฒ˜[[output-capture]]
ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์ค‘ `stdout` ๋ฐ `stderr`๋กœ ์ „์†ก๋œ ๋ชจ๋“  ์ถœ๋ ฅ์ด ์บก์ฒ˜๋ฉ๋‹ˆ๋‹ค.
ํ…Œ์ŠคํŠธ๋‚˜ ์„ค์ • ๋ฉ”์†Œ๋“œ๊ฐ€ ์‹คํŒจํ•˜๋ฉด ์บก์ฒ˜๋œ ์ถœ๋ ฅ์€ ์ผ๋ฐ˜์ ์œผ๋กœ ์‹คํŒจ ์ถ”์  ์ •๋ณด์™€ ํ•จ๊ป˜ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.
์ถœ๋ ฅ ์บก์ฒ˜๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ณ  `stdout` ๋ฐ `stderr`๋ฅผ ์ •์ƒ์ ์œผ๋กœ ๋ฐ›์œผ๋ ค๋ฉด `-s` ๋˜๋Š” `--capture=no`๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”:
```bash
pytest -s tests/utils/test_logging.py
```
ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๋ฅผ JUnit ํ˜•์‹์˜ ์ถœ๋ ฅ์œผ๋กœ ๋ณด๋‚ด๋ ค๋ฉด ๋‹ค์Œ์„ ์‚ฌ์šฉํ•˜์„ธ์š”:
```bash
py.test tests --junitxml=result.xml
```
### ์ƒ‰์ƒ ์กฐ์ ˆ[[color-control]]
์ƒ‰์ƒ์ด ์—†๊ฒŒ ํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •ํ•˜์„ธ์š”(์˜ˆ๋ฅผ ๋“ค์–ด ํฐ์ƒ‰ ๋ฐฐ๊ฒฝ์— ๋…ธ๋ž€์ƒ‰ ๊ธ€์”จ๋Š” ๊ฐ€๋…์„ฑ์ด ์ข‹์ง€ ์•Š์Šต๋‹ˆ๋‹ค):
```bash
pytest --color=no tests/utils/test_logging.py
```
### online pastebin service์— ํ…Œ์ŠคํŠธ ๋ณด๊ณ ์„œ ์ „์†ก[[sending test report to online pastebin service]]
๊ฐ ํ…Œ์ŠคํŠธ ์‹คํŒจ์— ๋Œ€ํ•œ URL์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค:
```bash
pytest --pastebin=failed tests/utils/test_logging.py
```
์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฐ ์‹คํŒจ์— ๋Œ€ํ•œ URL์„ ์ œ๊ณตํ•˜๋Š” remote Paste service์— ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์ •๋ณด๋ฅผ ์ œ์ถœํ•ฉ๋‹ˆ๋‹ค.
์ผ๋ฐ˜์ ์ธ ํ…Œ์ŠคํŠธ๋ฅผ ์„ ํƒํ•  ์ˆ˜๋„ ์žˆ๊ณ  ํ˜น์€ ํŠน์ • ์‹คํŒจ๋งŒ ๋ณด๋‚ด๋ ค๋ฉด `-x`์™€ ๊ฐ™์ด ์ถ”๊ฐ€ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
์ „์ฒด ํ…Œ์ŠคํŠธ ์„ธ์…˜ ๋กœ๊ทธ์— ๋Œ€ํ•œ URL์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค:
```bash
pytest --pastebin=all tests/utils/test_logging.py
```
## ํ…Œ์ŠคํŠธ ์ž‘์„ฑ[[writing-tests]]
๐Ÿค— transformers ํ…Œ์ŠคํŠธ๋Š” ๋Œ€๋ถ€๋ถ„ `unittest`๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜์ง€๋งŒ,
`pytest`์—์„œ ์‹คํ–‰๋˜๋ฏ€๋กœ ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ๋‘ ์‹œ์Šคํ…œ์˜ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ง€์›๋˜๋Š” ๊ธฐ๋Šฅ์— ๋Œ€ํ•ด [์—ฌ๊ธฐ](https://docs.pytest.org/en/stable/unittest.html)์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์ง€๋งŒ,
๊ธฐ์–ตํ•ด์•ผ ํ•  ์ค‘์š”ํ•œ ์ ์€ ๋Œ€๋ถ€๋ถ„์˜ `pytest` fixture๊ฐ€ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
ํŒŒ๋ผ๋ฏธํ„ฐํ™”๋„ ์ž‘๋™ํ•˜์ง€ ์•Š์ง€๋งŒ, ์šฐ๋ฆฌ๋Š” ๋น„์Šทํ•œ ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™ํ•˜๋Š” `parameterized` ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
### ๋งค๊ฐœ๋ณ€์ˆ˜ํ™”[[parametrization]]
๋™์ผํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ๋‹ค๋ฅธ ์ธ์ˆ˜๋กœ ์—ฌ๋Ÿฌ ๋ฒˆ ์‹คํ–‰ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ข…์ข… ์žˆ์Šต๋‹ˆ๋‹ค.
ํ…Œ์ŠคํŠธ ๋‚ด์—์„œ ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๊ทธ๋ ‡๊ฒŒ ํ•˜๋ฉด ํ•˜๋‚˜์˜ ์ธ์ˆ˜ ์„ธํŠธ์— ๋Œ€ํ•ด ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
```python
# 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` ๋งค๊ฐœ๋ณ€์ˆ˜ ์ง‘ํ•ฉ๋งŒ ์‹คํ–‰ํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
```bash
pytest -k "negative and integer" tests/test_mytest.py
```
๋˜๋Š” `negative` ํ•˜์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ œ์™ธํ•œ ๋ชจ๋“  ์„œ๋ธŒ ํ…Œ์ŠคํŠธ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
```bash
pytest -k "not negative" tests/test_mytest.py
```
์•ž์—์„œ ์–ธ๊ธ‰ํ•œ `-k` ํ•„ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ ์™ธ์—๋„,
๊ฐ ์„œ๋ธŒ ํ…Œ์ŠคํŠธ์˜ ์ •ํ™•ํ•œ ์ด๋ฆ„์„ ํ™•์ธํ•œ ํ›„์— ์ผ๋ถ€ ํ˜น์€ ์ „์ฒด ์„œ๋ธŒ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
```bash
pytest test_this1.py --collect-only -q
```
๊ทธ๋ฆฌ๊ณ  ๋‹ค์Œ์˜ ๋‚ด์šฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค:
```bash
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๊ฐœ์˜ ํŠน์ •ํ•œ ์„œ๋ธŒ ํ…Œ์ŠคํŠธ๋งŒ ์‹คํ–‰ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค:
```bash
pytest test_this1.py::TestMathUnitTest::test_floor_0_negative test_this1.py::TestMathUnitTest::test_floor_1_integer
```
`transformers`์˜ ๊ฐœ๋ฐœ์ž ์ข…์†์„ฑ์— ์ด๋ฏธ ์žˆ๋Š” [parameterized](https://pypi.org/project/parameterized/) ๋ชจ๋“ˆ์€
`unittests`์™€ `pytest` ํ…Œ์ŠคํŠธ ๋ชจ๋‘์—์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋Ÿฌ๋‚˜ ํ…Œ์ŠคํŠธ๊ฐ€ `unittest`๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ `pytest.mark.parametrize`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(์ด๋ฏธ ์žˆ๋Š” ์ผ๋ถ€ ํ…Œ์ŠคํŠธ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๊ฒฝ์šฐ๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
์ฃผ๋กœ `examples` ํ•˜์œ„์— ์žˆ์Šต๋‹ˆ๋‹ค).
๋‹ค์Œ์€ `pytest`์˜ `parametrize` ๋งˆ์ปค๋ฅผ ์‚ฌ์šฉํ•œ ๋™์ผํ•œ ์˜ˆ์ž…๋‹ˆ๋‹ค:
```python
# 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` ํ•„ํ„ฐ๊ฐ€ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ์—๋„ ์‹คํ–‰ํ•  ์„œ๋ธŒ ํ…Œ์ŠคํŠธ๋ฅผ ์ •ํ™•ํ•˜๊ฒŒ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋‹จ, ์ด ๋งค๊ฐœ๋ณ€์ˆ˜ํ™” ํ•จ์ˆ˜๋Š” ์„œ๋ธŒ ํ…Œ์ŠคํŠธ์˜ ์ด๋ฆ„ ์ง‘ํ•ฉ์„ ์•ฝ๊ฐ„ ๋‹ค๋ฅด๊ฒŒ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ชจ์Šต์ž…๋‹ˆ๋‹ค:
```bash
pytest test_this2.py --collect-only -q
```
๊ทธ๋ฆฌ๊ณ  ๋‹ค์Œ์˜ ๋‚ด์šฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค:
```bash
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]
```
ํŠน์ •ํ•œ ํ…Œ์ŠคํŠธ์— ๋Œ€ํ•ด์„œ๋งŒ ์‹คํ–‰ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค:
```bash
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'์˜ ์„œ๋ธŒํด๋ž˜์Šค์— ์žˆ๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:
```python
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`๋กœ ๋๋‚˜๋Š” ์ ‘๊ทผ์ž๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:
```python
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`์˜ ํ•˜์œ„ ํด๋ž˜์Šค์ด๋ฏ€๋กœ, ์šฐ๋ฆฌ๋Š” ์ด๊ฒƒ์„ ํ…Œ์ŠคํŠธ ๋ชจ๋“ˆ์—์„œ ์‰ฝ๊ฒŒ ์ƒ์†ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋‹ค์Œ์€ ํ•ด๋‹น ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค:
```python
from transformers.testing_utils import TestCasePlus
class ExamplesTests(TestCasePlus):
def test_whatever(self):
tmp_dir = self.get_auto_remove_tmp_dir()
```
์ด ์ฝ”๋“œ๋Š” ๊ณ ์œ ํ•œ ์ž„์‹œ ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  `tmp_dir`์„ ํ•ด๋‹น ์œ„์น˜๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
- ๊ณ ์œ ํ•œ ์ž„์‹œ ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค:
```python
def test_whatever(self):
tmp_dir = self.get_auto_remove_tmp_dir()
```
`tmp_dir`์—๋Š” ์ƒ์„ฑ๋œ ์ž„์‹œ ๋””๋ ‰ํ„ฐ๋ฆฌ์˜ ๊ฒฝ๋กœ๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.
์ด๋Š” ํ…Œ์ŠคํŠธ์˜ ์ข…๋ฃŒ ๋‹จ๊ณ„์—์„œ ์ž๋™์œผ๋กœ ์ œ๊ฑฐ๋ฉ๋‹ˆ๋‹ค.
- ์„ ํƒํ•œ ๊ฒฝ๋กœ๋กœ ์ž„์‹œ ๋””๋ ‰ํ„ฐ๋ฆฌ ์ƒ์„ฑ ํ›„์— ํ…Œ์ŠคํŠธ ์‹œ์ž‘ ์ „์— ๋น„์–ด ์žˆ๋Š” ์ƒํƒœ์ธ์ง€ ํ™•์ธํ•˜๊ณ , ํ…Œ์ŠคํŠธ ํ›„์—๋Š” ๋น„์šฐ์ง€ ๋งˆ์„ธ์š”.
```python
def test_whatever(self):
tmp_dir = self.get_auto_remove_tmp_dir("./xxx")
```
์ด๊ฒƒ์€ ๋””๋ฒ„๊น…ํ•  ๋•Œ ํŠน์ • ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ ,
๊ทธ ๋””๋ ‰ํ„ฐ๋ฆฌ์— ์ด์ „์— ์‹คํ–‰๋œ ํ…Œ์ŠคํŠธ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋‚จ๊ธฐ์ง€ ์•Š๋„๋ก ํ•˜๋Š” ๋ฐ์— ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
- `before` ๋ฐ `after` ์ธ์ˆ˜๋ฅผ ์ง์ ‘ ์˜ค๋ฒ„๋ผ์ด๋”ฉํ•˜์—ฌ ๊ธฐ๋ณธ ๋™์ž‘์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ
๋‹ค์Œ ์ค‘ ํ•˜๋‚˜์˜ ๋™์ž‘์œผ๋กœ ์ด์–ด์ง‘๋‹ˆ๋‹ค:
- `before=True`: ํ…Œ์ŠคํŠธ ์‹œ์ž‘ ์‹œ ์ž„์‹œ ๋””๋ ‰ํ„ฐ๋ฆฌ๊ฐ€ ํ•ญ์ƒ ์ง€์›Œ์ง‘๋‹ˆ๋‹ค.
- `before=False`: ์ž„์‹œ ๋””๋ ‰ํ„ฐ๋ฆฌ๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ ๊ธฐ์กด ํŒŒ์ผ์€ ๊ทธ๋Œ€๋กœ ๋‚จ์Šต๋‹ˆ๋‹ค.
- `after=True`: ํ…Œ์ŠคํŠธ ์ข…๋ฃŒ ์‹œ ์ž„์‹œ ๋””๋ ‰ํ„ฐ๋ฆฌ๊ฐ€ ํ•ญ์ƒ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค.
- `after=False`: ํ…Œ์ŠคํŠธ ์ข…๋ฃŒ ์‹œ ์ž„์‹œ ๋””๋ ‰ํ„ฐ๋ฆฌ๊ฐ€ ํ•ญ์ƒ ๊ทธ๋Œ€๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.
<Tip>
`rm -r`์— ํ•ด๋‹นํ•˜๋Š” ๋ช…๋ น์„ ์•ˆ์ „ํ•˜๊ฒŒ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด,
๋ช…์‹œ์ ์ธ `tmp_dir`์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ํ”„๋กœ์ ํŠธ ์ €์žฅ์†Œ ์ฒดํฌ ์•„์›ƒ์˜ ํ•˜์œ„ ๋””๋ ‰ํ„ฐ๋ฆฌ๋งŒ ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค.
๋”ฐ๋ผ์„œ ์‹ค์ˆ˜๋กœ `/tmp`๊ฐ€ ์•„๋‹Œ ์ค‘์š”ํ•œ ํŒŒ์ผ ์‹œ์Šคํ…œ์˜ ์ผ๋ถ€๊ฐ€ ์‚ญ์ œ๋˜์ง€ ์•Š๋„๋ก ํ•ญ์ƒ `./`๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ฒฝ๋กœ๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
</Tip>
<Tip>
๊ฐ ํ…Œ์ŠคํŠธ๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ž„์‹œ ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ,
๋ณ„๋„๋กœ ์š”์ฒญํ•˜์ง€ ์•Š๋Š” ํ•œ ๋ชจ๋‘ ์ž๋™์œผ๋กœ ์ œ๊ฑฐ๋ฉ๋‹ˆ๋‹ค.
</Tip>
### ์ž„์‹œ sys.path ์˜ค๋ฒ„๋ผ์ด๋“œ[[temporary-sys.path-override]]
`sys.path`๋ฅผ ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ๋กœ ์ž„์‹œ๋กœ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜๊ธฐ ์œ„ํ•ด ์˜ˆ๋ฅผ ๋“ค์–ด `ExtendSysPath` ์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ์ž๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:
```python
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]]
- ์ „์ฒด ํ…Œ์ŠคํŠธ๋ฅผ ๋ฌด์กฐ๊ฑด ๊ฑด๋„ˆ๋›ฐ๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
```python no-style
@unittest.skip(reason="this bug needs to be fixed")
def test_feature_x():
```
๋˜๋Š” pytest๋ฅผ ํ†ตํ•ด:
```python no-style
@pytest.mark.skip(reason="this bug needs to be fixed")
```
๋˜๋Š” `xfail` ๋ฐฉ์‹์œผ๋กœ:
```python no-style
@pytest.mark.xfail
def test_feature_x():
```
- ํ…Œ์ŠคํŠธ ๋‚ด๋ถ€์—์„œ ๋‚ด๋ถ€ ํ™•์ธ์— ๋”ฐ๋ผ ํ…Œ์ŠคํŠธ๋ฅผ ๊ฑด๋„ˆ๋›ฐ๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:
```python
def test_feature_x():
if not has_something():
pytest.skip("unsupported configuration")
```
๋˜๋Š” ๋ชจ๋“ˆ ์ „์ฒด:
```python
import pytest
if not pytest.config.getoption("--custom-flag"):
pytest.skip("--custom-flag is missing, skipping tests", allow_module_level=True)
```
๋˜๋Š” `xfail` ๋ฐฉ์‹์œผ๋กœ:
```python
def test_feature_x():
pytest.xfail("expected to fail until bug XYZ is fixed")
```
- import๊ฐ€ missing๋œ ๋ชจ๋“ˆ์ด ์žˆ์„ ๋•Œ ๊ทธ ๋ชจ๋“ˆ์˜ ๋ชจ๋“  ํ…Œ์ŠคํŠธ๋ฅผ ๊ฑด๋„ˆ๋›ฐ๋Š” ๋ฐฉ๋ฒ•:
```python
docutils = pytest.importorskip("docutils", minversion="0.3")
```
- ์กฐ๊ฑด์— ๋”ฐ๋ผ ํ…Œ์ŠคํŠธ๋ฅผ ๊ฑด๋„ˆ๋›ฐ๋Š” ๋ฐฉ๋ฒ•:
```python no-style
@pytest.mark.skipif(sys.version_info < (3,6), reason="requires python3.6 or higher")
def test_feature_x():
```
๋˜๋Š”:
```python no-style
@unittest.skipIf(torch_device == "cpu", "Can't do half precision")
def test_feature_x():
```
๋˜๋Š” ๋ชจ๋“ˆ ์ „์ฒด๋ฅผ ๊ฑด๋„ˆ๋›ฐ๋Š” ๋ฐฉ๋ฒ•:
```python no-style
@pytest.mark.skipif(sys.platform == 'win32', reason="does not run on windows")
class TestClass():
def test_feature_x(self):
```
๋ณด๋‹ค ์ž์„ธํ•œ ์˜ˆ์ œ ๋ฐ ๋ฐฉ๋ฒ•์€ [์—ฌ๊ธฐ](https://docs.pytest.org/en/latest/skipping.html)์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
### ๋А๋ฆฐ ํ…Œ์ŠคํŠธ[[slow-tests]]
ํ…Œ์ŠคํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์ง€์†์ ์œผ๋กœ ํ™•์žฅ๋˜๊ณ  ์žˆ์œผ๋ฉฐ, ์ผ๋ถ€ ํ…Œ์ŠคํŠธ๋Š” ์‹คํ–‰ํ•˜๋Š” ๋ฐ ๋ช‡ ๋ถ„์ด ๊ฑธ๋ฆฝ๋‹ˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ  ์šฐ๋ฆฌ์—๊ฒŒ๋Š” ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ๊ฐ€ CI๋ฅผ ํ†ตํ•ด ์™„๋ฃŒ๋˜๊ธฐ๊นŒ์ง€ ํ•œ ์‹œ๊ฐ„์„ ๊ธฐ๋‹ค๋ฆด ์—ฌ์œ ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
๋”ฐ๋ผ์„œ ํ•„์ˆ˜ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ์ผ๋ถ€ ์˜ˆ์™ธ๋ฅผ ์ œ์™ธํ•˜๊ณ  ๋А๋ฆฐ ํ…Œ์ŠคํŠธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ‘œ์‹œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
```python no-style
from transformers.testing_utils import slow
@slow
def test_integration_foo():
```
`@slow`๋กœ ํ‘œ์‹œ๋œ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ ค๋ฉด `RUN_SLOW=1` ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜์„ธ์š”. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:
```bash
RUN_SLOW=1 pytest tests
```
`@parameterized`์™€ ๊ฐ™์€ ๋ช‡ ๊ฐ€์ง€ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” ํ…Œ์ŠคํŠธ ์ด๋ฆ„์„ ๋‹ค์‹œ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋Ÿฌ๋ฏ€๋กœ `@slow`์™€ ๋‚˜๋จธ์ง€ ๊ฑด๋„ˆ๋›ฐ๊ธฐ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ `@require_*`๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž‘๋™๋˜๋ ค๋ฉด ๋งˆ์ง€๋ง‰์— ๋‚˜์—ด๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ์˜ฌ๋ฐ”๋ฅธ ์‚ฌ์šฉ ์˜ˆ์ž…๋‹ˆ๋‹ค.
```python no-style
@parameterized.expand(...)
@slow
def test_integration_foo():
```
์ด ๋ฌธ์„œ์˜ ์ดˆ๋ฐ˜๋ถ€์— ์„ค๋ช…๋œ ๊ฒƒ์ฒ˜๋Ÿผ ๋А๋ฆฐ ํ…Œ์ŠคํŠธ๋Š” PR์˜ CI ํ™•์ธ์ด ์•„๋‹Œ ์˜ˆ์•ฝ๋œ ์ผ์ • ๊ธฐ๋ฐ˜์œผ๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
๋”ฐ๋ผ์„œ PR ์ œ์ถœ ์ค‘์— ์ผ๋ถ€ ๋ฌธ์ œ๋ฅผ ๋†“์นœ ์ฑ„๋กœ ๋ณ‘ํ•ฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋“ค์€ ๋‹ค์Œ๋ฒˆ์˜ ์˜ˆ์ •๋œ CI ์ž‘์—… ์ค‘์— ๊ฐ์ง€๋ฉ๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ PR์„ ์ œ์ถœํ•˜๊ธฐ ์ „์— ์ž์‹ ์˜ ์ปดํ“จํ„ฐ์—์„œ ๋А๋ฆฐ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ ๋˜ํ•œ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.
๋А๋ฆฐ ํ…Œ์ŠคํŠธ๋กœ ํ‘œ์‹œํ•ด์•ผ ํ•˜๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ๋Œ€๋žต์ ์ธ ๊ฒฐ์ • ๊ธฐ์ค€์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.
๋งŒ์•ฝ ํ…Œ์ŠคํŠธ๊ฐ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๋‚ด๋ถ€ ๊ตฌ์„ฑ ์š”์†Œ ์ค‘ ํ•˜๋‚˜์— ์ง‘์ค‘๋˜์–ด ์žˆ๋‹ค๋ฉด(์˜ˆ: ๋ชจ๋ธ๋ง ํŒŒ์ผ, ํ† ํฐํ™” ํŒŒ์ผ, ํŒŒ์ดํ”„๋ผ์ธ),
ํ•ด๋‹น ํ…Œ์ŠคํŠธ๋ฅผ ๋А๋ฆฐ ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ์—์„œ ์‹คํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
๋งŒ์•ฝ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๋‹ค๋ฅธ ์ธก๋ฉด(์˜ˆ: ๋ฌธ์„œ ๋˜๋Š” ์˜ˆ์ œ)์— ์ง‘์ค‘๋˜์–ด ์žˆ๋‹ค๋ฉด,
ํ•ด๋‹น ํ…Œ์ŠคํŠธ๋ฅผ ๋А๋ฆฐ ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ์—์„œ ์‹คํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด ์ ‘๊ทผ ๋ฐฉ์‹์„ ๋ณด์™„ํ•˜๊ธฐ ์œ„ํ•ด ์˜ˆ์™ธ๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
- ๋ฌด๊ฑฐ์šด ๊ฐ€์ค‘์น˜ ์„ธํŠธ๋‚˜ 50MB๋ณด๋‹ค ํฐ ๋ฐ์ดํ„ฐ์…‹์„ ๋‹ค์šด๋กœ๋“œํ•ด์•ผ ํ•˜๋Š” ๋ชจ๋“  ํ…Œ์ŠคํŠธ(์˜ˆ: ๋ชจ๋ธ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ, ํ† ํฌ๋‚˜์ด์ € ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ, ํŒŒ์ดํ”„๋ผ์ธ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ)๋ฅผ
๋А๋ฆฐ ํ…Œ์ŠคํŠธ๋กœ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์ƒˆ๋กœ์šด ๋ชจ๋ธ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒฝ์šฐ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ์šฉ์œผ๋กœ ๋ฌด์ž‘์œ„ ๊ฐ€์ค‘์น˜๋กœ ์ž‘์€ ๋ฒ„์ „์„ ๋งŒ๋“ค์–ด ํ—ˆ๋ธŒ์— ์—…๋กœ๋“œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์ด ๋‚ด์šฉ์€ ์•„๋ž˜ ๋‹จ๋ฝ์—์„œ ์„ค๋ช…๋ฉ๋‹ˆ๋‹ค.
- ํŠน๋ณ„ํžˆ ๋น ๋ฅด๊ฒŒ ์‹คํ–‰๋˜๋„๋ก ์ตœ์ ํ™”๋˜์ง€ ์•Š์€ ํ•™์Šต์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•˜๋Š” ํ…Œ์ŠคํŠธ๋Š” ๋А๋ฆฐ ํ…Œ์ŠคํŠธ๋กœ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
- ๋А๋ฆฌ์ง€ ์•Š์•„์•ผ ํ•  ํ…Œ์ŠคํŠธ ์ค‘ ์ผ๋ถ€๊ฐ€ ๊ทน๋„๋กœ ๋А๋ฆฐ ๊ฒฝ์šฐ
์˜ˆ์™ธ๋ฅผ ๋„์ž…ํ•˜๊ณ  ์ด๋ฅผ `@slow`๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ์„ ๋””์Šคํฌ์— ์ €์žฅํ•˜๊ณ  ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ž๋™ ๋ชจ๋ธ๋ง ํ…Œ์ŠคํŠธ๋Š” `@slow`์œผ๋กœ ํ‘œ์‹œ๋œ ํ…Œ์ŠคํŠธ์˜ ์ข‹์€ ์˜ˆ์ž…๋‹ˆ๋‹ค.
- CI์—์„œ 1์ดˆ ์ด๋‚ด์— ํ…Œ์ŠคํŠธ๊ฐ€ ์™„๋ฃŒ๋˜๋Š” ๊ฒฝ์šฐ(๋‹ค์šด๋กœ๋“œ ํฌํ•จ)์—๋Š” ๋А๋ฆฐ ํ…Œ์ŠคํŠธ๊ฐ€ ์•„๋‹ˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
๋А๋ฆฐ ํ…Œ์ŠคํŠธ๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ์—๋Š” ๋‹ค์–‘ํ•œ ๋‚ด๋ถ€๋ฅผ ์™„์ „ํžˆ ์ปค๋ฒ„ํ•˜๋ฉด์„œ ๋น ๋ฅด๊ฒŒ ์œ ์ง€๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฌด์ž‘์œ„ ๊ฐ€์ค‘์น˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŠน๋ณ„ํžˆ ์ƒ์„ฑ๋œ ์ž‘์€ ๋ชจ๋ธ๋กœ ํ…Œ์ŠคํŠธํ•˜๋ฉด ์ƒ๋‹นํ•œ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ด๋Ÿฌํ•œ ๋ชจ๋ธ์€ ์ตœ์†Œํ•œ์˜ ๋ ˆ์ด์–ด ์ˆ˜(์˜ˆ: 2), ์–ดํœ˜ ํฌ๊ธฐ(์˜ˆ: 1000) ๋“ฑ์˜ ์š”์†Œ๋งŒ ๊ฐ€์ง‘๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ `@slow` ํ…Œ์ŠคํŠธ๋Š” ๋Œ€ํ˜• ๋А๋ฆฐ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ •์„ฑ์ ์ธ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ด๋Ÿฌํ•œ ์ž‘์€ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ™•์ธํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด *tiny* ๋ชจ๋ธ์„ ์ฐพ์•„๋ณด์„ธ์š”.
```bash
grep tiny tests examples
```
๋‹ค์Œ์€ ์ž‘์€ ๋ชจ๋ธ[stas/tiny-wmt19-en-de](https://huggingface.co/stas/tiny-wmt19-en-de)์„ ๋งŒ๋“ 
[script](https://github.com/huggingface/transformers/tree/main/scripts/fsmt/fsmt-make-tiny-model.py) ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.
ํŠน์ • ๋ชจ๋ธ์˜ ์•„ํ‚คํ…์ฒ˜์— ๋งž๊ฒŒ ์‰ฝ๊ฒŒ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด ๋Œ€์šฉ๋Ÿ‰ ๋ชจ๋ธ์„ ๋‹ค์šด๋กœ๋“œํ•˜๋Š” ๊ฒฝ์šฐ ๋Ÿฐํƒ€์ž„์„ ์ž˜๋ชป ์ธก์ •ํ•˜๊ธฐ ์‰ฝ์ง€๋งŒ,
๋กœ์ปฌ์—์„œ ํ…Œ์ŠคํŠธํ•˜๋ฉด ๋‹ค์šด๋กœ๋“œํ•œ ํŒŒ์ผ์ด ์บ์‹œ๋˜์–ด ๋‹ค์šด๋กœ๋“œ ์‹œ๊ฐ„์ด ์ธก์ •๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
๋Œ€์‹  CI ๋กœ๊ทธ์˜ ์‹คํ–‰ ์†๋„ ๋ณด๊ณ ์„œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”(`pytest --durations=0 tests`์˜ ์ถœ๋ ฅ).
์ด ๋ณด๊ณ ์„œ๋Š” ๋А๋ฆฐ ์ด์ƒ๊ฐ’์œผ๋กœ ํ‘œ์‹œ๋˜์ง€ ์•Š๊ฑฐ๋‚˜ ๋น ๋ฅด๊ฒŒ ๋‹ค์‹œ ์ž‘์„ฑํ•ด์•ผ ํ•˜๋Š” ๋А๋ฆฐ ์ด์ƒ๊ฐ’์„ ์ฐพ๋Š” ๋ฐ๋„ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
CI์—์„œ ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ๊ฐ€ ๋А๋ ค์ง€๊ธฐ ์‹œ์ž‘ํ•˜๋ฉด ์ด ๋ณด๊ณ ์„œ์˜ ๋งจ ์œ„ ๋ชฉ๋ก์— ๊ฐ€์žฅ ๋А๋ฆฐ ํ…Œ์ŠคํŠธ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.
### stdout/stderr ์ถœ๋ ฅ ํ…Œ์ŠคํŠธ[[testing-the-stdout/stderr-output]]
`stdout` ๋ฐ/๋˜๋Š” `stderr`๋กœ ์“ฐ๋Š” ํ•จ์ˆ˜๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋ ค๋ฉด `pytest`์˜ [capsys ์‹œ์Šคํ…œ](https://docs.pytest.org/en/latest/capture.html)์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๋‹น ์ŠคํŠธ๋ฆผ์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
```python
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๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
```python
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`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
```python
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`์ด ํฌํ•จ๋˜์–ด ์žˆ๋Š”์ง€์˜ ์—ฌ๋ถ€์— ๊ด€๊ณ„์—†์ด ๋ชจ๋“  ๊ฒƒ์„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋ฏ€๋กœ ํŽธ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
```python
from transformers.testing_utils import CaptureStdout
with CaptureStdout() as cs:
function_that_writes_to_stdout()
print(cs.out)
```
๋‹ค์Œ์€ ์ „์ฒด ํ…Œ์ŠคํŠธ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค.
```python
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` ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
```python
from transformers.testing_utils import CaptureStderr
with CaptureStderr() as cs:
function_that_writes_to_stderr()
print(cs.err)
```
๋‘ ์ŠคํŠธ๋ฆผ์„ ๋™์‹œ์— ์บก์ฒ˜ํ•ด์•ผ ํ•œ๋‹ค๋ฉด, ๋ถ€๋ชจ `CaptureStd` ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
```python
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`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
```python
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`๋ผ๋Š” ๋„์šฐ๋ฏธ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
```python
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`๊ฐ€ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค:
```python
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]]
์ผ๋ถ€ ์ƒํ™ฉ์—์„œ ํ…Œ์ŠคํŠธ์—์„œ ์ž„์˜์„ฑ์„ ์ œ๊ฑฐํ•˜์—ฌ ๋™์ผํ•˜๊ฒŒ ์žฌํ˜„ ๊ฐ€๋Šฅํ•œ ๊ฒฐ๊ณผ๋ฅผ ์–ป๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ด๋ฅผ ์œ„ํ•ด์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‹œ๋“œ๋ฅผ ๊ณ ์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
```python
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]]
๊ฒฝ๊ณ ๊ฐ€ ์žˆ๋Š” ๊ณณ์—์„œ ๋””๋ฒ„๊ฑฐ๋ฅผ ์‹œ์ž‘ํ•˜๋ ค๋ฉด ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•˜์„ธ์š”.
```bash
pytest tests/utils/test_logging.py -W error::UserWarning --pdb
```
## Github Actions ์›Œํฌํ”Œ๋กœ์šฐ ์ž‘์—… ์ฒ˜๋ฆฌ[[working-with-github-actions-workflows]]
์…€ํ”„ ํ‘ธ์‹œ ์›Œํฌํ”Œ๋กœ์šฐ CI ์ž‘์—…์„ ํŠธ๋ฆฌ๊ฑฐํ•˜๋ ค๋ฉด, ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
1. `transformers` ์›๋ณธ์—์„œ ์ƒˆ ๋ธŒ๋žœ์น˜๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค(ํฌํฌ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค!).
2. ๋ธŒ๋žœ์น˜ ์ด๋ฆ„์€ `ci_` ๋˜๋Š” `ci-`๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(`main`๋„ ํŠธ๋ฆฌ๊ฑฐํ•˜์ง€๋งŒ `main`์—์„œ๋Š” PR์„ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค).
๋˜ํ•œ ํŠน์ • ๊ฒฝ๋กœ์— ๋Œ€ํ•ด์„œ๋งŒ ํŠธ๋ฆฌ๊ฑฐ๋˜๋ฏ€๋กœ ์ด ๋ฌธ์„œ๊ฐ€ ์ž‘์„ฑ๋œ ํ›„์— ๋ณ€๊ฒฝ๋œ ๋‚ด์šฉ์€
[์—ฌ๊ธฐ](https://github.com/huggingface/transformers/blob/main/.github/workflows/self-push.yml)์˜ *push:*์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
3. ์ด ๋ธŒ๋žœ์น˜์—์„œ PR์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค
4. ๊ทธ๋Ÿฐ ๋‹ค์Œ [์—ฌ๊ธฐ](https://github.com/huggingface/transformers/actions/workflows/self-push.yml)์—์„œ ์ž‘์—…์ด ๋‚˜ํƒ€๋‚˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋ฐฑ๋กœ๊ทธ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ, ๋ฐ”๋กœ ์‹คํ–‰๋˜์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
## ์‹คํ—˜์ ์ธ CI ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ[[testing-Experimental-CI-Features]]
CI ๊ธฐ๋Šฅ์„ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒƒ์€ ์ผ๋ฐ˜ CI ์ž‘๋™์— ๋ฐฉํ•ด๊ฐ€ ๋  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ž ์žฌ์ ์œผ๋กœ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋”ฐ๋ผ์„œ ์ƒˆ๋กœ์šด CI ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒฝ์šฐ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
1. ํ…Œ์ŠคํŠธํ•ด์•ผ ํ•  ๋‚ด์šฉ์„ ํ…Œ์ŠคํŠธํ•˜๋Š” ์ƒˆ๋กœ์šด ์ „์šฉ ์ž‘์—…์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
2. ์ƒˆ๋กœ์šด ์ž‘์—…์€ ํ•ญ์ƒ ์„ฑ๊ณตํ•ด์•ผ๋งŒ ๋…น์ƒ‰ โœ“๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(์•„๋ž˜์— ์ž์„ธํ•œ ๋‚ด์šฉ์ด ์žˆ์Šต๋‹ˆ๋‹ค).
3. ๋‹ค์–‘ํ•œ PR ์œ ํ˜•์— ๋Œ€ํ•œ ํ™•์ธ์„ ์œ„ํ•ด
(์‚ฌ์šฉ์ž ํฌํฌ ๋ธŒ๋žœ์น˜, ํฌํฌ๋˜์ง€ ์•Š์€ ๋ธŒ๋žœ์น˜, github.com UI ์ง์ ‘ ํŒŒ์ผ ํŽธ์ง‘์—์„œ ์ƒ์„ฑ๋œ ๋ธŒ๋žœ์น˜, ๊ฐ•์ œ ํ‘ธ์‹œ ๋“ฑ PR์˜ ์œ ํ˜•์€ ์•„์ฃผ ๋‹ค์–‘ํ•ฉ๋‹ˆ๋‹ค.)
๋ฉฐ์น  ๋™์•ˆ ์‹คํ—˜ ์ž‘์—…์˜ ๋กœ๊ทธ๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋ฉด์„œ ์‹คํ–‰ํ•ด๋ด…๋‹ˆ๋‹ค.
(์˜๋„์ ์œผ๋กœ ํ•ญ์ƒ ๋…น์ƒ‰์„ ํ‘œ์‹œํ•˜๋ฏ€๋กœ ์ž‘์—… ์ „์ฒด๊ฐ€ ๋…น์ƒ‰์€ ์•„๋‹ˆ๋ผ๋Š” ์ ์— ์œ ์˜ํ•ฉ๋‹ˆ๋‹ค.)
4. ๋ชจ๋“  ๊ฒƒ์ด ์•ˆ์ •์ ์ธ์ง€ ํ™•์ธํ•œ ํ›„, ์ƒˆ๋กœ์šด ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๊ธฐ์กด ์ž‘์—…์— ๋ณ‘ํ•ฉํ•ฉ๋‹ˆ๋‹ค.
์ด๋ ‡๊ฒŒ ํ•˜๋ฉด CI ๊ธฐ๋Šฅ ์ž์ฒด์— ๋Œ€ํ•œ ์‹คํ—˜์ด ์ผ๋ฐ˜ ์ž‘์—… ํ๋ฆ„์— ๋ฐฉํ•ด๊ฐ€ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
๊ทธ๋Ÿฌ๋‚˜ ์ƒˆ๋กœ์šด CI ๊ธฐ๋Šฅ์ด ๊ฐœ๋ฐœ ์ค‘์ธ ๋™์•ˆ, ํ•ญ์ƒ ์„ฑ๊ณตํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ผ๊นŒ์š”?
TravisCI์™€ ๊ฐ™์€ ์ผ๋ถ€ CI๋Š” `ignore-step-failure`๋ฅผ ์ง€์›ํ•˜๋ฉฐ ์ „์ฒด ์ž‘์—…์„ ์„ฑ๊ณตํ•œ ๊ฒƒ์œผ๋กœ ๋ณด๊ณ ํ•˜์ง€๋งŒ,
ํ˜„์žฌ ์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” CircleCI์™€ Github Actions๋Š” ์ด๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
๋”ฐ๋ผ์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ•ด๊ฒฐ์ฑ…์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
1. bash ์Šคํฌ๋ฆฝํŠธ์—์„œ ๊ฐ€๋Šฅํ•œ ๋งŽ์€ ์˜ค๋ฅ˜๋ฅผ ์–ต์ œํ•˜๊ธฐ ์œ„ํ•ด ์‹คํ–‰ ๋ช…๋ น์˜ ์‹œ์ž‘ ๋ถ€๋ถ„์— `set +euo pipefail`์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
2. ๋งˆ์ง€๋ง‰ ๋ช…๋ น์€ ๋ฐ˜๋“œ์‹œ ์„ฑ๊ณตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. `echo "done"` ๋˜๋Š” `true`๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
์˜ˆ์‹œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.
```yaml
- 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"
```
๊ฐ„๋‹จํ•œ ๋ช…๋ น์˜ ๊ฒฝ์šฐ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜ํ–‰ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
```bash
cmd_that_may_fail || true
```
๊ฒฐ๊ณผ์— ๋งŒ์กฑํ•œ ํ›„์—๋Š” ๋ฌผ๋ก , ์‹คํ—˜์ ์ธ ๋‹จ๊ณ„ ๋˜๋Š” ์ž‘์—…์„ ์ผ๋ฐ˜ ์ž‘์—…์˜ ๋‚˜๋จธ์ง€ ๋ถ€๋ถ„๊ณผ ํ†ตํ•ฉํ•˜๋ฉด์„œ
`set +euo pipefail` ๋˜๋Š” ๊ธฐํƒ€ ์ถ”๊ฐ€ํ•œ ์š”์†Œ๋ฅผ ์ œ๊ฑฐํ•˜์—ฌ
์‹คํ—˜ ์ž‘์—…์ด ์ผ๋ฐ˜ CI ์ž‘๋™์— ๋ฐฉํ•ด๋˜์ง€ ์•Š๋„๋ก ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์ด ์ „๋ฐ˜์ ์ธ ๊ณผ์ •์€ ์‹คํ—˜ ๋‹จ๊ณ„๊ฐ€ PR์˜ ์ „๋ฐ˜์ ์ธ ์ƒํƒœ์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๊ณ  ์‹คํŒจํ•˜๋„๋ก
`allow-failure`์™€ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ํ›จ์”ฌ ๋” ์‰ฌ์› ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
๊ทธ๋Ÿฌ๋‚˜ ์•ž์—์„œ ์–ธ๊ธ‰ํ•œ ๋ฐ”์™€ ๊ฐ™์ด CircleCI์™€ Github Actions๋Š” ํ˜„์žฌ ์ด๋Ÿฌํ•œ ๊ธฐ๋Šฅ๋“ค ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
์ด ๊ธฐ๋Šฅ์˜ ์ง€์›์„ ์œ„ํ•œ ํˆฌํ‘œ์— ์ฐธ์—ฌํ•˜๊ณ  CI ๊ด€๋ จ ์Šค๋ ˆ๋“œ๋“ค์—์„œ ์ด๋Ÿฌํ•œ ์ƒํ™ฉ์„ ํ™•์ธํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
- [Github Actions:](https://github.com/actions/toolkit/issues/399)
- [CircleCI:](https://ideas.circleci.com/ideas/CCI-I-344)