Как сделать тесты для моей текстовой игры на Питоне?
У меня есть своя игра на питоне, текстовая. И чем больше я в неё добавляю контента, тем сложнее мне её тестить. Я решил задуматься над автоматическими тестами, но я ничего не знаю про них. Буду рад если дадите ссылки на литературу на эту тему или расскажите мне про это.
(Если вам нужна моя игра, то вот репозиторий https://github.com/Saharoc-game/Batle-boss)
Ответы (1 шт):
Автор решения: Oleg
→ Ссылка
Как уже написали в комментариях вы можете применить Unit тестирование для своих классов, вот пример unit теста для вашего класса Boss:
class TestBoss(unittest.TestCase):
# Тест для метода __init__ (конструктора)
# Используем @patch для временной подмены random.choice и random.randint
@patch('__main__.random.randint') # Путь к функции randint, которую мокируем
@patch('__main__.random.choice') # Путь к функции choice, которую мокируем
def test_init(self, mock_choice, mock_randint):
# Указываем, какие значения должны возвращать мокированные функции
# при их вызове во время этого теста
mock_choice.return_value = 55 # Boss.hp должен стать 55
mock_randint.return_value = 4 # Boss.magic должен стать 4
boss = Boss()
# Проверяем, что атрибуты установились в соответствии с возвращаемыми значениями моков
self.assertEqual(boss.hp, 55)
self.assertEqual(boss.magic, 4)
# Проверяем, что hp_max равен начальному hp
self.assertEqual(boss.hp_max, 55)
# Можно также проверить, что функции random были вызваны
mock_choice.assert_called_once_with([50, 55, 60])
mock_randint.assert_called_once_with(3, 4)
# Тест для метода attack
# Мокируем только random.randint(0, 5)
@patch('__main__.random.randint')
def test_attack(self, mock_randint):
# Создаем экземпляр босса для теста (init тоже будет использовать рандом, но это не
# мешает тесту attack, так как мы мокируем только тот randint, что внутри attack)
# Если бы нам нужно было жестко контролировать init тоже, мы бы мокировали и там.
# Для простоты теста attack, мы этого не делаем.
boss = Boss()
# --- Тестовый случай 1: Нет брони, нет убитых боссов ---
# Мокируем random.randint(0, 5) так, чтобы он вернул 3
mock_randint.return_value = 3 # x = 3
bosses_killed = 0
armor_defense = 0
# Ожидаемый урон: ((3 - 3 * (0 / 100)) + 0) // 1 = (3 - 0 + 0) // 1 = 3
expected_damage = 3
actual_damage = boss.attack(bosses_killed, armor_defense)
self.assertEqual(actual_damage, expected_damage, "Тест attack 1 не пройден")
# Проверяем, что randint был вызван с нужными аргументами
mock_randint.assert_called_with(0, 5)
# Важно: reset_mock() сбрасывает счетчики вызовов и возвращаемые значения мока
# перед следующим тестовым случаем, если мок используется несколько раз в одном тесте.
# В данном случае, mock_randint используется один раз на вызов attack.
# Если бы мы вызывали attack несколько раз в этом тесте, это было бы полезно.
# Для clarity, лучше разделить на отдельные тесты, как ниже.
@patch('__main__.random.randint')
def test_attack_with_armor(self, mock_randint):
boss = Boss()
# --- Тестовый случай 2: Есть броня, нет убитых боссов ---
mock_randint.return_value = 4 # x = 4
bosses_killed = 0
armor_defense = 50 # 50% защиты
# Ожидаемый урон: ((4 - 4 * (50 / 100)) + 0) // 1 = (4 - 4 * 0.5 + 0) // 1 = (4 - 2) // 1 = 2
expected_damage = 2
actual_damage = boss.attack(bosses_killed, armor_defense)
self.assertEqual(actual_damage, expected_damage, "Тест attack 2 не пройден")
mock_randint.assert_called_with(0, 5)
if __name__ == '__main__':
unittest.main(argv=['first-arg-is-ignored'], exit=False)