Skip to main content
Include pytest tutorial
Source Link

Edit: Demonstrate how to use tests

This part covers another way of ensuring your example battles run correctly in a systematic way using pytest. It illustrates why some other the other answers' suggestions to get the printing out of the function makes it easier to code.

Step 1.

Have your main function just return a_scr and d_scr, and put the printing logic in the main script:

# riskgame.py
def risk_game(attacker, defender):
    a = sorted(attacker, reverse=True)
    b = sorted(defender, reverse=True)

    a_scr = sum([i > j for i, j in zip(a,b)])
    d_scr = sum([i <= j for i, j in zip(a,b)])

    return a_scr, d_scr

if __name__ == "__main__":
    a_scr, d_scr = risk_game([6,2,6], [6, 6])

    if d_scr == 0:
        print('Defender loses %i armies.' %a_scr)
    elif a_scr == 0:
        print('Attacker loses %i armies.' %d_scr)
    else:
        print('Attacker loses %i army and defender loses %i army.' %(a_scr, d_scr))

When you execute the script, you get the same behavior as before:

$ python riskgame.py 
Attacker loses 2 armies.

Step 2.

In the same directory create test_wins_and_losses.py and create some tests:

# test_wins_and_losses.py
from riskgame import risk_game
  
def test_d_wins():
    a_scr, d_scr = risk_game([1,2,6], [1, 5])
    assert a_scr == 2
    assert d_scr == 0

def test_a_wins():
    a_scr, d_scr = risk_game([6,2,6], [6, 6])
    assert a_scr == 0
    assert d_scr == 2

def test_equal():
    a_scr, d_scr = risk_game([1,4,1], [1, 2])
    assert a_scr == 1
    assert d_scr == 1

Notice I used the same values you put in the original post, but you could have any example games you want in there. Ideally, you would have lots, covering as many use cases as you can.

Step 3.

Install pytest if you haven't already.

$ pip install pytest

Step 4.

Run it!

$ pytest
============================= test session starts ==============================
platform linux -- Python 3.7.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1
rootdir: /tmp/risk_game
collected 3 items                                                              

test_wins_and_losses.py ...                                              [100%]

============================== 3 passed in 0.02s ===============================

The idea is now you can change your code, and every time you do you can just type pytest at the command line to confirm everything is still functioning the way you expect. For example, if we make the mistake I made earlier and change the line to

d_scr = sum([i < j for i, j in zip(a,b)])

and run the tests, we get:

$ pytest
==================================================================== test session starts =====================================================================
platform linux -- Python 3.7.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1
rootdir: /tmp/risk_game
collected 3 items                                                                                                                                            

test_wins_and_losses.py .FF                                                                                                                            [100%]

========================================================================== FAILURES ==========================================================================
________________________________________________________________________ test_a_wins _________________________________________________________________________

    def test_a_wins():
        a_scr, d_scr = risk_game([6,2,6], [6, 6])
        assert a_scr == 0
>       assert d_scr == 2
E       assert 0 == 2

test_wins_and_losses.py:11: AssertionError
_________________________________________________________________________ test_equal _________________________________________________________________________

    def test_equal():
        a_scr, d_scr = risk_game([1,4,1], [1, 2])
        assert a_scr == 1
>       assert d_scr == 1
E       assert 0 == 1

test_wins_and_losses.py:16: AssertionError
================================================================== short test summary info ===================================================================
FAILED test_wins_and_losses.py::test_a_wins - assert 0 == 2
FAILED test_wins_and_losses.py::test_equal - assert 0 == 1
================================================================ 2 failed, 1 passed in 0.09s ================================================================

Happy testing!

Edit: Demonstrate how to use tests

This part covers another way of ensuring your example battles run correctly in a systematic way using pytest. It illustrates why some other the other answers' suggestions to get the printing out of the function makes it easier to code.

Step 1.

Have your main function just return a_scr and d_scr, and put the printing logic in the main script:

# riskgame.py
def risk_game(attacker, defender):
    a = sorted(attacker, reverse=True)
    b = sorted(defender, reverse=True)

    a_scr = sum([i > j for i, j in zip(a,b)])
    d_scr = sum([i <= j for i, j in zip(a,b)])

    return a_scr, d_scr

if __name__ == "__main__":
    a_scr, d_scr = risk_game([6,2,6], [6, 6])

    if d_scr == 0:
        print('Defender loses %i armies.' %a_scr)
    elif a_scr == 0:
        print('Attacker loses %i armies.' %d_scr)
    else:
        print('Attacker loses %i army and defender loses %i army.' %(a_scr, d_scr))

When you execute the script, you get the same behavior as before:

$ python riskgame.py 
Attacker loses 2 armies.

Step 2.

In the same directory create test_wins_and_losses.py and create some tests:

# test_wins_and_losses.py
from riskgame import risk_game
  
def test_d_wins():
    a_scr, d_scr = risk_game([1,2,6], [1, 5])
    assert a_scr == 2
    assert d_scr == 0

def test_a_wins():
    a_scr, d_scr = risk_game([6,2,6], [6, 6])
    assert a_scr == 0
    assert d_scr == 2

def test_equal():
    a_scr, d_scr = risk_game([1,4,1], [1, 2])
    assert a_scr == 1
    assert d_scr == 1

Notice I used the same values you put in the original post, but you could have any example games you want in there. Ideally, you would have lots, covering as many use cases as you can.

Step 3.

Install pytest if you haven't already.

$ pip install pytest

Step 4.

Run it!

$ pytest
============================= test session starts ==============================
platform linux -- Python 3.7.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1
rootdir: /tmp/risk_game
collected 3 items                                                              

test_wins_and_losses.py ...                                              [100%]

============================== 3 passed in 0.02s ===============================

The idea is now you can change your code, and every time you do you can just type pytest at the command line to confirm everything is still functioning the way you expect. For example, if we make the mistake I made earlier and change the line to

d_scr = sum([i < j for i, j in zip(a,b)])

and run the tests, we get:

$ pytest
==================================================================== test session starts =====================================================================
platform linux -- Python 3.7.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1
rootdir: /tmp/risk_game
collected 3 items                                                                                                                                            

test_wins_and_losses.py .FF                                                                                                                            [100%]

========================================================================== FAILURES ==========================================================================
________________________________________________________________________ test_a_wins _________________________________________________________________________

    def test_a_wins():
        a_scr, d_scr = risk_game([6,2,6], [6, 6])
        assert a_scr == 0
>       assert d_scr == 2
E       assert 0 == 2

test_wins_and_losses.py:11: AssertionError
_________________________________________________________________________ test_equal _________________________________________________________________________

    def test_equal():
        a_scr, d_scr = risk_game([1,4,1], [1, 2])
        assert a_scr == 1
>       assert d_scr == 1
E       assert 0 == 1

test_wins_and_losses.py:16: AssertionError
================================================================== short test summary info ===================================================================
FAILED test_wins_and_losses.py::test_a_wins - assert 0 == 2
FAILED test_wins_and_losses.py::test_equal - assert 0 == 1
================================================================ 2 failed, 1 passed in 0.09s ================================================================

Happy testing!

added 869 characters in body
Source Link

I'm not sure if I'm supposed to make a whole answer for a tiny suggestion, or if this should be a comment. If it's the latter, could an admin tell me and I'll delete this and put it in a comment?

If this were a real PR, I would highlight one of the *loose variables and ask Should this read lose?

I really like your second approach. While reading it, I felt like the next line of code was doing exactly what I was anticipating it would do, and I didn't have to use as much mental memory while reading it like I did your first approach, keeping track of all those variables that were set to zero. One way you might consider improving it is to remove the a_scr and d_scr initializations and use a sum() to calculate them, such as:

def RiskGame(attacker, defender):
    a = sorted(attacker, reverse=True)
    b = sorted(defender, reverse=True)
    
    a_scr = sum([i > j for i, j in zip(a,b)])
    d_scr = sum([i < j for i, j in zip(a,b)])

    if d_scr == 0:
        return 'Defender loses %i armies.' %a_scr
    elif a_scr == 0:
        return 'Attacker loses %i armies.' %d_scr
    else:
        return 'Attacker loses %i army and defender loses %i army.' %(a_scr, d_scr)

RiskGame([1,2,6], [1, 5])

I have similar reservations about the Pascal casing of the functions and returning strings instead of values, but these are covered nicely in the other answers.

I'm not sure if I'm supposed to make a whole answer for a tiny suggestion, or if this should be a comment. If it's the latter, could an admin tell me and I'll delete this and put it in a comment?

If this were a real PR, I would highlight one of the *loose variables and ask Should this read lose?

If this were a real PR, I would highlight one of the *loose variables and ask Should this read lose?

I really like your second approach. While reading it, I felt like the next line of code was doing exactly what I was anticipating it would do, and I didn't have to use as much mental memory while reading it like I did your first approach, keeping track of all those variables that were set to zero. One way you might consider improving it is to remove the a_scr and d_scr initializations and use a sum() to calculate them, such as:

def RiskGame(attacker, defender):
    a = sorted(attacker, reverse=True)
    b = sorted(defender, reverse=True)
    
    a_scr = sum([i > j for i, j in zip(a,b)])
    d_scr = sum([i < j for i, j in zip(a,b)])

    if d_scr == 0:
        return 'Defender loses %i armies.' %a_scr
    elif a_scr == 0:
        return 'Attacker loses %i armies.' %d_scr
    else:
        return 'Attacker loses %i army and defender loses %i army.' %(a_scr, d_scr)

RiskGame([1,2,6], [1, 5])

I have similar reservations about the Pascal casing of the functions and returning strings instead of values, but these are covered nicely in the other answers.

Source Link

I'm not sure if I'm supposed to make a whole answer for a tiny suggestion, or if this should be a comment. If it's the latter, could an admin tell me and I'll delete this and put it in a comment?

If this were a real PR, I would highlight one of the *loose variables and ask Should this read lose?