Often when writing tests we want to test the same behavior against many different parameters and check the results. Behave makes this easier to do by providing tools to create a tableized test instead of writing out each test separately. The next game logic to test is that the dealer knows the point value of its hand. Here is a test that checks several scenarios:
- Scenario Outline: Get hand total
- Given a <hand>
- When the dealer sums the cards
- Then the <total> is correct
- Examples: Hands
- | hand | total |
- | 5,7 | 12 |
- | 5,Q | 15 |
- | Q,Q,A | 21 |
- | Q,A | 21 |
- | A,A,A | 13 |
The steps will be similar to what we've seen before, but we'll now get to use the parameterized steps feature of Behave.
Here's how to implement the new "given" step:
- @given('a {hand}')
- def step_impl(context, hand):
- context.dealer = Dealer()
- context.dealer.hand = hand.split(',')
Just like before, we create a new Dealer object, but this time we manually set the dealer's cards instead of generating them randomly. Since the hand parameter is a simple string, we split the parameter to get a list.
Next, add the remaining steps:
- @when('the dealer sums the cards')
- def step_impl(context):
- context.dealer_total = context.dealer.get_hand_total()
- @then('the {total:d} is correct')
- def step_impl(context, total):
- assert (context.dealer_total == total)
There's many different approaches to summing values of cards, but here's one solution to find the total of the dealer's hand. Create this as a top-level function in the twentyone.py module:
- def _hand_total(hand):
- values = [None, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 10]
- value_map = {k: v for k, v in zip(_cards, values)}
- total = sum([value_map[card] for card in hand if card != 'A'])
- ace_count = hand.count('A')
- for i in range(ace_count, -1, -1):
- if i == 0:
- total = total + ace_count
- elif total + (i * 11) + (ace_count - i) <= 21:
- total = total + (i * 11) + ace_count - i
- break
- return total
We also need to give the dealer the ability to total its cards. Add this function to the Dealer class:
- def get_hand_total(self):
- return _hand_total(self.hand)
We'll add one more tableized test, this time to test that the dealer plays by the rules. Traditionally, the dealer must play "hit" until he or she has 17 or more points. Add this scenario outline to test that behavior:
- Scenario Outline: Dealer plays by the rules
- Given a hand <total>
- when the dealer determines a play
- then the <play> is correct
- Examples: Hands
- | total | play |
- | 10 | hit |
- | 15 | hit |
- | 16 | hit |
- | 17 | stand |
- | 18 | stand |
- | 19 | stand |
- | 20 | stand |
- | 21 | stand |
- | 22 | stand |
- @given('a dealer')
- def step_impl(context):
- context.dealer = Dealer()
- ## NEW STEP
- @given('a hand {total:d}')
- def step_impl(context, total):
- context.dealer = Dealer()
- context.total = total
- @given('a {hand}')
- def step_impl(context, hand):
- context.dealer = Dealer()
- context.dealer.hand = hand.split(',')
The new "when" step is not parameterized and can be placed anywhere, but, for readability, should be grouped with the other when steps:
- @when('the dealer determines a play')
- def step_impl(context):
- context.dealer_play = context.dealer.determine_play(context.total)
- def determine_play(self, total):
- if total < 17:
- return 'hit'
- else:
- return 'stand'
- @then('the dealer gives itself two cards')
- def step_impl(context):
- assert (len(context.dealer.hand) == 2)
- @then('the {total:d} is correct')
- def step_impl(context, total):
- assert (context.dealer_total == total)
- ## NEW STEP
- @then('the {play} is correct')
- def step_impl(context, play):
- assert (context.dealer_play == play)
We're going to add one final test that will tie together all of the code we've just written. We've proven to ourselves with tests that the dealer can deal itself cards, determine its hand total, and make a play separately, but there's no code to tie this together. Since we are emphasizing test-driven development, let's add a test for this behavior.
- Scenario: A Dealer can always play
- Given a dealer
- When the round starts
- Then the dealer chooses a play
- @then('the dealer gives itself two cards')
- def step_impl(context):
- assert (len(context.dealer.hand) == 2)
- #NEW STEP
- @then('the dealer chooses a play')
- def step_impl(context):
- assert (context.dealer.make_play() in ['stand', 'hit'])
- @then('the {total:d} is correct')
- def step_impl(context, total):
- assert (context.dealer_total == total)
- def make_play(self):
- return self.determine_play(self.get_hand_total())
If you've done everything correctly, running behave should display all of the tests and give a summary similar to this:
- 1 feature passed, 0 failed, 0 skipped
- 16 scenarios passed, 0 failed, 0 skipped
- 48 steps passed, 0 failed, 0 skipped, 0 undefined
- Took 0m0.007s
This tutorial walked you through setting up a new project with the Behave library and using test-driven development to build the code based off of behavioral tests.
If you would like to get experience writing more tests with this project, try implementing a Player class and player.feature that plays with some basic strategy.
Written by Phillip Johnson
If you found this post interesting, follow and support us.
Suggest for you:
Build Your First Python and Django Application
Zero to Hero with Python Professional Python Programmer Bundle
The Python Mega Course: Build 10 Python Applications
Complete Python Bootcamp (Hot)
Learning Python for Data Analysis and Visualization
No comments:
Post a Comment