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


Вот полный набор тестов для `RockPaperScissors.play`. Проблема в том, что компьютер выбирает случайно, поэтому в тестах его выбор мокается через `random.choice` — иначе нельзя гарантировать все комбинации.

**Файл `rock_paper_scissors.py` (тестируемый класс):**

```python
import random


class RockPaperScissors:
    CHOICES = ('rock', 'paper', 'scissors')
    BEATS = {'rock': 'scissors', 'paper': 'rock', 'scissors': 'paper'}

    def play(self, player_choice):
        if not isinstance(player_choice, str):
            raise ValueError("Выбор должен быть строкой")
        player_choice = player_choice.lower()
        if player_choice not in self.CHOICES:
            raise ValueError("Допустимые значения: 'rock', 'paper', 'scissors'")

        computer_choice = random.choice(self.CHOICES)

        if player_choice == computer_choice:
            return 'tie'
        if self.BEATS[player_choice] == computer_choice:
            return 'win'
        return 'lose'
```

**Файл `test_rock_paper_scissors.py`:**

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


@pytest.fixture
def game():
    return RockPaperScissors()


class TestPlayValidInput:
    """Проверка всех комбинаций результатов."""

    # Параметризация: (выбор игрока, выбор компьютера, ожидаемый результат)
    @pytest.mark.parametrize("player, computer, expected", [
        # Победы игрока (комбинации: камень > ножницы, бумага > камень, ножницы > бумага)
        ('rock', 'scissors', 'win'),
        ('paper', 'rock', 'win'),
        ('scissors', 'paper', 'win'),
        # Поражения игрока
        ('rock', 'paper', 'lose'),
        ('paper', 'scissors', 'lose'),
        ('scissors', 'rock', 'lose'),
        # Ничьи (3 комбинации)
        ('rock', 'rock', 'tie'),
        ('paper', 'paper', 'tie'),
        ('scissors', 'scissors', 'tie'),
    ])
    def test_all_combinations(self, game, player, computer, expected):
        """Каждая пара ходов даёт корректный результат: win/lose/tie."""
        with patch('random.choice', return_value=computer):
            assert game.play(player) == expected

    def test_all_combinations_covered(self, game):
        """Полный перебор 3×3 комбинаций через random.seed."""
        results = set()
        for player in game.CHOICES:
            for computer in game.CHOICES:
                with patch('random.choice', return_value=computer):
                    results.add(game.play(player))
        assert results == {'win', 'lose', 'tie'}


class TestPlayInvalidInput:
    """Ошибки при неправильном выборе — ValueError."""

    @pytest.mark.parametrize("bad_input", [
        'Rocky',          # опечатка / несуществующий выбор
        'камень',          # нелатиница
        'rock paper',      # несколько слов
        '',                # пустая строка
        ' rock',           # пробелы — если класс их не тримит
        'rock ',           # замыкающий пробел
        123,               # не строка
        None,              # None
        True,              # bool (подкласс int!)
        [],                # список
        {},                # словарь
    ])
    def test_invalid_choice_raises_value_error(self, game, bad_input):
        with pytest.raises(ValueError):
            game.play(bad_input)

    def test_valid_values_do_not_raise(self, game):
        """Все три допустимых значения не вызывают исключений."""
        for choice in game.CHOICES:
            with patch('random.choice', return_value=choice):
                game.play(choice)  # не должно быть ValueError


class TestRandomness:
    """Проверка, что компьютер действительно выбирает случайно."""

    def test_random_choice_is_used(self, game):
        with patch('random.choice') as mock_choice:
            mock_choice.return_value = 'rock'
            game.play('rock')
            mock_choice.assert_called_once_with(game.CHOICES)
```

**Что здесь проверяется:**
- **Все 9 комбинаций** (3×3) через `@pytest.mark.parametrize` — победы, поражения и ничьи покрыты явно.
- **Некорректный ввод** — нестроковые типы (`123`, `None`, список), опечатки, кириллица, пустая строка. Обрати внимание на `True` — это подкласс `int`, и `isinstance(True, str)` корректно отсекает его.
- **Мокирование** через `patch('random.choice', ...)` — без этого результат непредсказуем.
- Трим пробелов зависит от реализации: если твой класс делает `.strip()`, убери `' rock'` и `'rock '` из списка невалидных значений и перенеси в валидные.

Если у тебя метод генерации случайного выбора называется иначе (например, внутренний метод класса), поправь путь в `patch` — скажи код реализации, адаптирую тесты.