ttc-cases / pydevx-lindjacob

1 stars 0 forks source link

Setup unit tests #6

Closed github-actions[bot] closed 2 months ago

github-actions[bot] commented 2 months ago

[!NOTE] ☝️ Learning goals in this issue

  • Refactor to make your scripts testable
  • Setup a unit test
  • Import and export a class from a module
  • Run the unit test from terminal
  • Run the unit test from the test console

Start by setting up a development branch for this issue.

Something isn't quite right with the GPT generated code!

I asked Phind to help me:

create a basic unittest that will test the rps-game.py script

Its suggested a complete rewrite - and lost track op the purpose. >To create a basic unit test for the `rps-game.py` script, you'll first need to refactor the `Hand` class methods into functions that can be imported and tested independently. Then, you can write tests using Python's built-in `unittest` framework. > >### Step 1: Refactor `rps-game.py` > >First, let's refactor the `Hand` class to separate the logic into functions that can be easily tested. We'll move the `play` method outside of the `Hand` class and adjust it accordingly. > >**Refactored `rps-game.py`:** > >```python >import random > >def get_outcomes(): > return { > "paper": ["rock"], > "scissors": ["paper"], > "rock": ["scissors"] > } > >def determine_opponent(hand): > return random.choice(["paper", "scissors", "rock"]) > >def check_win(player_hand, opponent_hand): > outcomes = get_outcomes() > if opponent_hand in outcomes[player_hand]: > return True > elif player_hand == opponent_hand: > return False > else: > return False > >def play_game(player_hand, opponent_hand): > if check_win(player_hand, opponent_hand): > return f"{player_hand} wins against {opponent_hand}!" > elif player_hand == opponent_hand: > return f"It's a tie Both {player_hand} and {opponent_hand} played." > else: > return f"{opponent_hand} wins against {player_hand}." >```

But I stopped it, since it had completely gotten rid of my Hand class. So I clarified:

Please refactor the rps_game.py scripts with respect for the Hand class - I'd like to keep that

And this time it looks like we're on to something:

Certainly Let's refactor the rps_game.py script to maintain the Hand class while making it easier to test. We'll introduce a method within the Hand class to encapsulate the logic for determining the outcome of a game, which can then be tested separately.

Refactored rps_game.py:

import argparse
import random

class Hand:
   def __init__(self, name):
       self.name = name

   def play(self, opponent):
       outcomes = {
           "paper": ["rock"],
           "scissors": ["paper"],
           "rock": ["scissors"]
       }
       if opponent in outcomes[self.name]:
           return f"{self.name} wins against {opponent}!"
       elif self.name == opponent:
           return f"It's a tie Both {self.name} and {opponent} played."
       else:
           return f"{opponent} wins against {self.name}."

def choose_random_hand():
   return Hand(random.choice(["paper", "scissors", "rock"]))

if __name__ == "__main__":
   parser = argparse.ArgumentParser(description="Play a game of Rock, Paper, Scissors.")
   parser.add_argument("--hand", type=str, choices=["paper", "scissors", "rock"], help="Your hand ('paper', 'scissors', or 'rock')")

   args = parser.parse_args()

   script_hand = choose_random_hand()
   print(f"You chose: {args.hand}")
   print(f"The script chose: {script_hand.name}")
   print(script_hand.play(args.hand))

Explanation:

  • The Hand class now includes a play method that takes an opponent's hand as an argument. This method returns the outcome of the game between the two hands.
  • A new function choose_random_hand is introduced to select a random hand for the script, keeping the logic separate from the main execution flow.
  • The argparse part remains unchanged, allowing the user to input their choice via command-line arguments.

Testing the Hand Class:

With the Hand class refactored, we can now write tests specifically for the play method. Assuming you have the unittest module available, here's how you could structure a test case:

Test file: test_hand.py:

import unittest
from src.rps_game import Hand

class TestHand(unittest.TestCase):

   def setUp(self):
       self.paper = Hand('paper')
       self.scissors = Hand('scissors')
       self.rock = Hand('rock')

   def test_paper_vs_rock(self):
       outcome = self.paper.play("rock")
       self.assertEqual(outcome, "paper wins against rock!")

   def test_scissors_vs_paper(self):
       outcome = self.scissors.play("paper")
       self.assertEqual(outcome, "scissors wins against paper!")

   def test_rock_vs_scissors(self):
       outcome = self.rock.play("scissors")
       self.assertEqual(outcome, "rock wins against scissors!")

   def test_tie(self):
       outcome = self.rock.play("rock")
       self.assertEqual(outcome, "It's a tie Both rock and rock played.")

if __name__ == '__main__':
   unittest.main()

Running the Tests:

To run the tests, execute the following command in your terminal:

pipenv run python -m unittest tests/test_hand.py

This setup allows you to test the Hand class's behavior in isolation, ensuring that each hand behaves correctly according to the rules of Rock, Paper, Scissors.

Follow the instructions from Phind:

Replace the script with it's new content and add the unit test, put it in a tests folder

NOTE: Phind gets way more credit than justified here - I fixed quite a few skews, so I've cheated a bit and the output you see above on rps_game.py and test_hand.py are not exactly what Phind suggested. But close'ish.

ALSO NOTE: You need to have an empty file __init__.py in both the root directory and the src directory for this to work.

Wrap up your development branch and deliver to main