Testing and continuous integration

August 20, 2018 at 1-2pm in Murray Learning Centre, UG07.

Stream of talk

Try this link

Getting set up

Make sure you have these two things on your computer:

Improving as a programmer

Preparing to use Github

Ready for poker

Getting more interactive

When we are writing tests, we often want to experiment with code, and look at variables. The best tool to do this is the IPython interactive Python prompt.

Now you will have a prompt of form In [1]: . Type 2 + 3 and press return. This should execute and return 5.

Start with a failing test

This is the key element of test first developement - write a failing test, then fix it.

Working out the design by testing

Now we have 52 “cards”, but the cards are Python’s special None value. We have to decide what a card should be.

Let’s say one card is a string, of form “7 of Spades” or “Jack of Hearts”.

Let’s also say that a new deck will be not-shuffled, so the first card will be the 2 of Hearts. Expand your test function like this:

def test_new_deck():
    # We will test the `make_new_deck` function
    deck = make_new_deck()
    assert len(deck) == 52
    first_card = deck[0]
    assert first_card == "2 of Hearts"

Run the file. It fails because the first card is currently None.

Experimenting as you go

Now we will write a proper function to generate the new deck of cards.

To do this, we would like to try various things at the interactive IPython prompt.

Try typing this at the IPython prompt:

'2 3 4 5 6 7 8 9 10 Jack Queen King Ace'.split()

You should see a length 13 list with the card values.

Try this:

'Hearts Diamonds Clubs Spades'.split()

This should be a four element list of suit names.

We can put these at the top of our poker.py file, like this:

VALUES = '2 3 4 5 6 7 8 9 10 Jack Queen King Ace'.split()
SUITS = 'Hearts Diamonds Clubs Spades'.split()

Run the file from IPython with:

run poker.py

Now check the variables look right by showing their contents. At the IPython prompt:

VALUES

and

SUITS

Let’s try making the first card in IPython.

VALUES[0] + ' of ' + SUITS[0]

To make the full deck of cards, we cycle through all the suits, then all the values, and combine the value with the suit, to make 52 cards:

def make_new_deck():
    cards = []
    for suit in SUITS:
        for value in VALUES:
            card = value + ' of ' + suit
            cards.append(card)
    return cards

Run the tests. Do they pass?

Extend the tests

We just did a lot of code, and our tests only test the first card. We should add some more tests to check our make_new_deck function is doing the right thing.

Add these lines to your test_new_deck function. Be careful to keep the same indentation as the lines above:

    assert deck[12] == "Ace of Hearts"
    assert deck[13] == "2 of Diamonds"
    assert deck[26] == "2 of Clubs"
    assert deck[51] == "Ace of Spades"

Making a poker hand

To make a poker hand, we shuffle the cards, and then give out five cards from the shuffled deck. What would a shuffled deck look like?

First we write a failing test.

A shuffled deck will virtually never have the same order as a fresh deck. Let’s put that in as a test, expecting it to fail one time in 52 x 51 x 50 x 49 etc, and that is basically never.

def test_shuffled():
    ordered_deck = make_new_deck()
    to_shuffle = make_new_deck()
    shuffled_deck = shuffled(to_shuffle)
    assert shuffled_deck != ordered_deck

You’ll also need to add this function call to your if __name__ block:

if __name__ == "__main__":
    print("Running tests")
    test_new_deck()
    test_shuffled()
    print("Finished")

Run the file. It fails. We are going to fix it.

First consider this really bad shuffle function:

def shuffled(deck):
    # A really bad shuffle function
    re_ordered = deck.copy()
    # Reverse the order
    re_ordered.reverse()
    return re_ordered

That would always return a deck with the ‘Ace of Spades’ first and the ‘2 of Hearts’ last. But our test would still pass. Put that function into poker.py.

Run the tests - they pass. Oops. We need to test more.

shuffled needs to do more than return something different from an ordered deck. It needs to return something different each time.

We add some new lines to test_shuffled that will do 1000 shuffles, and makes sure each one is different. We take the opportunity to fold in the check that the deck is not ordered.

def test_shuffled():
    decks = []
    # Put an ordered deck into the list of decks
    decks.append(make_new_deck())
    for i in range(1000):
        deck = make_new_deck()
        shuffled_deck = shuffled(deck)
        # Compare this deck to all previous ones.  Do any match?
        assert shuffled_deck not in decks
        decks.append(shuffled_deck)

Run the tests. They should fail.

How to make this work correctly? Let’s experiment in IPython:

import random

Use tab completion to explore the random module, by typing random. (notice appended period) and then pressing tab. We find random.shuffle. Here is a not-shuffled deck:

deck = make_new_deck()
# First 10 cards
deck[:10]

Now shuffle:

random.shuffle(deck)
# New order shows in first 10 cards
deck[:10]

We will use random.shuffle for our shuffled function.

Type import random at the top of poker.py. Replace the bad version of shuffled with this version:

def shuffled(deck):
    # A proper shuffle function
    re_ordered = deck.copy()
    random.shuffle(re_ordered)
    return re_ordered

Run the tests. They should pass.

A better way to run the tests

pytest is a Python package that makes it easier to write tests and run them.

So far we’ve been running the tests by running our Python module poker.py. We had to add a call to our test functions in the if __name__ block.

Pytest finds tests in .py files automatically, and then runs the tests.

If you installed Anaconda, you have Pytest installed already.

If not, open a terminal window, and type:

python -m pytest --version

If you get No module named pytest, then:

pip install --user pytest

Now check you can run the tests with:

python -m pytest poker.py

This is an efficient way to find tests and run them.

Commit your changes

Open Git (go back to git bash in Windows; open a new Terminal tab on Mac).

First check that Git knows your name and email. Type:

git config user.name

It should show your name. If it doesn’t show anything, type:

git config user.name "Your Name"

Now check:

git config user.email

If that shows nothing:

git config user.email "my.name@bham.ac.uk"

Now you are ready to prepare and make a new commit. Type:

git status

You should see that poker.py has been modified.

Put it in Git’s staging area ready for the next commit:

git add poker.py

Use git status to show that the changes in poker.py are now in the “Changes to be committed”.

Do the commit with:

git commit

Your text editor should open for you to type a commit message. Save the message to finish the commit.

Continuous integration

Notice the .travis.yml file in the repository. This is a configuration file for the free Travis-CI continuous integration service.

Now we ask Travis-CI to run our tests each time we do a new commit to the repository.

Go to the https://www.travis-ci.com front page. Click on the green button “Sign up with Github”. Click on the green button at the bottom of the page: “Authorize travis-pro”. Select the play-poker repository.

All done. The next time you send a commit to the play-poker repository, Travis-CI will run the tests for you.

Let’s do that:

git push origin master --set-upstream

Now go to the https://travis-ci.com site. You should see that Travis-CI is running or has run your tests for you.

Share