ahassoun's picture
Upload 3018 files
ee6e328

A newer version of the Gradio SDK is available: 5.5.0

Upgrade

ํ…Œ์ŠคํŠธ[[testing]]

๋จผ์ € ๐Ÿค— Transformers ๋ชจ๋ธ์ด ์–ด๋–ป๊ฒŒ ํ…Œ์ŠคํŠธ๋˜๋Š”์ง€ ์‚ดํŽด๋ณด๊ณ , ์ƒˆ๋กœ์šด ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑ ๋ฐ ๊ธฐ์กด ํ…Œ์ŠคํŠธ๋ฅผ ๊ฐœ์„ ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ด…์‹œ๋‹ค.

์ด ์ €์žฅ์†Œ์—๋Š” 2๊ฐœ์˜ ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค:

  1. tests - ์ผ๋ฐ˜ API์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ
  2. examples - API์˜ ์ผ๋ถ€๊ฐ€ ์•„๋‹Œ ๋‹ค์–‘ํ•œ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ

Transformers ํ…Œ์ŠคํŠธ ๋ฐฉ๋ฒ•[[how-transformers-are-tested]]

  1. PR์ด ์ œ์ถœ๋˜๋ฉด 9๊ฐœ์˜ CircleCi ์ž‘์—…์œผ๋กœ ํ…Œ์ŠคํŠธ๊ฐ€ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น PR์— ๋Œ€ํ•ด ์ƒˆ๋กœ์šด ์ปค๋ฐ‹์ด ์ƒ์„ฑ๋  ๋•Œ๋งˆ๋‹ค ํ…Œ์ŠคํŠธ๋Š” ๋‹ค์‹œ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค. ์ด ์ž‘์—…๋“ค์€ ์ด config ํŒŒ์ผ์— ์ •์˜๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ํ•„์š”ํ•˜๋‹ค๋ฉด ์‚ฌ์šฉ์ž์˜ ๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ ๋™์ผํ•˜๊ฒŒ ์žฌํ˜„ํ•ด ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    ์ด CI ์ž‘์—…์€ @slow ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

  2. 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 ์ž‘์—…์„ ํŠธ๋ฆฌ๊ฑฐํ•˜๋ ค๋ฉด, ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  1. transformers ์›๋ณธ์—์„œ ์ƒˆ ๋ธŒ๋žœ์น˜๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค(ํฌํฌ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค!).
  2. ๋ธŒ๋žœ์น˜ ์ด๋ฆ„์€ ci_ ๋˜๋Š” ci-๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(main๋„ ํŠธ๋ฆฌ๊ฑฐํ•˜์ง€๋งŒ main์—์„œ๋Š” PR์„ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค). ๋˜ํ•œ ํŠน์ • ๊ฒฝ๋กœ์— ๋Œ€ํ•ด์„œ๋งŒ ํŠธ๋ฆฌ๊ฑฐ๋˜๋ฏ€๋กœ ์ด ๋ฌธ์„œ๊ฐ€ ์ž‘์„ฑ๋œ ํ›„์— ๋ณ€๊ฒฝ๋œ ๋‚ด์šฉ์€ ์—ฌ๊ธฐ์˜ *push:*์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  3. ์ด ๋ธŒ๋žœ์น˜์—์„œ PR์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค
  4. ๊ทธ๋Ÿฐ ๋‹ค์Œ ์—ฌ๊ธฐ์—์„œ ์ž‘์—…์ด ๋‚˜ํƒ€๋‚˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐฑ๋กœ๊ทธ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ, ๋ฐ”๋กœ ์‹คํ–‰๋˜์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

์‹คํ—˜์ ์ธ 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๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

์˜ˆ์‹œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

- 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 ๊ด€๋ จ ์Šค๋ ˆ๋“œ๋“ค์—์„œ ์ด๋Ÿฌํ•œ ์ƒํ™ฉ์„ ํ™•์ธํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.