Загрузка данных


Вот готовый набор тестов для `play` с использованием `pytest` и `unittest.mock` (мы контролируем случайный выбор компьютера через мок, чтобы проверить все комбинации детерминированно):

```python
import pytest
from unittest.mock import patch

from rock_paper_scissors import RockPaperScissors  # путь к вашему классу


CHOICES = ['rock', 'paper', 'scissors']

# Все 9 комбинаций: (выбор игрока, выбор компьютера, ожидаемый результат)
ALL_COMBINATIONS = [
    ('rock',     'rock',     'tie'),
    ('rock',     'paper',    'lose'),
    ('rock',     'scissors', 'win'),
    ('paper',    'rock',     'win'),
    ('paper',    'paper',    'tie'),
    ('paper',    'scissors', 'lose'),
    ('scissors', 'rock',     'lose'),
    ('scissors', 'paper',    'win'),
    ('scissors', 'scissors', 'tie'),
]


class TestPlayAllCombinations:
    @pytest.mark.parametrize("player, computer, expected", ALL_COMBINATIONS)
    def test_all_combinations(self, player, computer, expected):
        with patch.object(RockPaperScissors, '_get_computer_choice', return_value=computer):
            game = RockPaperScissors()
            assert game.play(player) == expected


class TestWinsAndLosses:
    @pytest.mark.parametrize("player, computer", [
        ('rock', 'scissors'),
        ('paper', 'rock'),
        ('scissors', 'paper'),
    ])
    def test_player_wins(self, player, computer):
        with patch.object(RockPaperScissors, '_get_computer_choice', return_value=computer):
            game = RockPaperScissors()
            assert game.play(player) == 'win'

    @pytest.mark.parametrize("player, computer", [
        ('rock', 'paper'),
        ('paper', 'scissors'),
        ('scissors', 'rock'),
    ])
    def test_player_loses(self, player, computer):
        with patch.object(RockPaperScissors, '_get_computer_choice', return_value=computer):
            game = RockPaperScissors()
            assert game.play(player) == 'lose'


class TestTies:
    @pytest.mark.parametrize("choice", CHOICES)
    def test_tie_when_choices_match(self, choice):
        with patch.object(RockPaperScissors, '_get_computer_choice', return_value=choice):
            game = RockPaperScissors()
            assert game.play(choice) == 'tie'


class TestInvalidInput:
    @pytest.mark.parametrize("bad_choice", [
        'ROCK',      # регистр
        'Rock',
        'камень',    # не английское слово
        'rockk',     # опечатка
        '',          # пустая строка
        ' ',
    ])
    def test_invalid_string_raises_value_error(self, bad_choice):
        game = RockPaperScissors()
        with pytest.raises(ValueError):
            game.play(bad_choice)

    @pytest.mark.parametrize("bad_choice", [
        None,
        123,
        3.14,
        ['rock'],
        {'choice': 'rock'},
        True,
    ])
    def test_non_string_raises_value_error(self, bad_choice):
        game = RockPaperScissors()
        with pytest.raises(ValueError):
            game.play(bad_choice)

    def test_invalid_choice_does_not_use_computer(self):
        """При неверном вводе компьютер вообще не должен ходить."""
        game = RockPaperScissors()
        with patch.object(RockPaperScissors, '_get_computer_choice') as mock_get:
            with pytest.raises(ValueError):
                game.play('lizard')
            mock_get.assert_not_called()


class TestComputerChoice:
    @pytest.mark.parametrize("mocked_choice", CHOICES)
    def test_computer_choice_always_valid(self, mocked_choice):
        with patch('random.choice', return_value=mocked_choice):
            game = RockPaperScissors()
            game.play('rock')  # не должно быть исключений

    def test_computer_choice_called_once(self):
        with patch.object(RockPaperScissors, '_get_computer_choice') as mock_get:
            mock_get.return_value = 'rock'
            game = RockPaperScissors()
            game.play('paper')
            mock_get.assert_called_once()
```

Пояснения:

- **`patch.object(..., '_get_computer_choice')`** — предполагается, что метод, генерирующий выбор компьютера (обычно `random.choice`), вынесен в отдельный метод класса. Замените имя на ваше. Если выбор делается прямо внутри `play`, замените на `patch('random.choice', return_value=...)`.
- **`@pytest.mark.parametrize`** покрывает все 9 комбинаций игрок/компьютер одним тестом — это и все победы, и все поражения, и все ничьи.
- **Проверки ошибок**: нестроковые типы (`None`, числа, списки), пустые строки, неправильные слова и опечатки — всё должно вызывать `ValueError`.
- **Тест на регистр** (`'ROCK'`, `'Rock'`) важен: по ТЗ допустимы только строчные `'rock'`, `'paper'`, `'scissors'`, значит они должны отклоняться.
- Если в вашей реализации регистр нечувствителен (например, через `.lower()`), просто перенесите эти значения из `TestInvalidInput` в `TestPlayAllCombinations`.

Запуск: `pytest -v test_rock_paper_scissors.py`