#1
  1. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Jan 2013
    Posts
    78
    Rep Power
    2

    Simple cointoss game done with TDD


    I made a simple cointoss game today using unittests.
    I've managed to unittests all but one function, the fMain function.

    The problem is that the fMain function depends on alot of functions(which are unittested by themselves) and also the random module to make random coin tosses, how could I unittests the fMain function in this code?

    And also, are such simple programs meant to get this complicated and long when making them using unittests or is it just me overdoing it/polishing it too much?

    Code:
    from __future__ import print_function
    import random
    
    def fTest(xTestString, xExpectedOutput=True):
        "Evaluates sTestString against xExpectedOutput."
        #Runs xTestString and stores the output
        try: 
            xOutput = eval(str(xTestString))
        except Exception as e:
            xOutput = e.__class__
        
        #Prints out the result of the run
        if (xOutput == xExpectedOutput) or (xTestString == xExpectedOutput):
            print("SUCCESSFUL TEST: '", str(xTestString)[:47], "'", sep="")
        else:
            print("FAILED TEST: '", str(xTestString)[:45], "'", sep="")
            print("\tExpected: '",str(xExpectedOutput), "'\n",
                  "\tReceived: '", str(xOutput), "'", sep="")
    
    def fUnittests():
        "Unittests functions in this module"
        print("\n"*40, "Starting unittests")
        #testing fsTossWinner
        sExpected = "Player1 gets heads, Player2 gets tails. Player1 wins!"
        fTest('fsTossWinner("Player1", "Player2", "heads", "tails")', sExpected)
        sExpected = "Player1 gets tails, Player2 gets heads. Player2 wins!"
        fTest('fsTossWinner("Player1", "Player2", "tails", "heads")', sExpected)
        sExpected = "Player1 gets tails, Player2 gets tails. It's a tie!"
        fTest('fsTossWinner("Player1", "Player2", "tails", "tails")', sExpected)
        sExpected = "Player1 gets heads, Player2 gets heads. It's a tie!"
        fTest('fsTossWinner("Player1", "Player2", "heads", "heads")', sExpected)
        sExpected = "P1 gets heads, P2 gets tails. P1 wins!"
        fTest('fsTossWinner("P1", "P2", "heads", "tails")', sExpected)
        
        #Testing fsScore
        sExpected = ("Player1 has 10 points, Player2 has 15 points,\n"+
        "Player2 is in the lead!")
        fTest('fsScore("Player1", "Player2", 10, 15)', sExpected)
        sExpected = ("Player1 has 11 points, Player2 has 9 points,\n"+
        "Player1 is in the lead!")
        fTest('fsScore("Player1", "Player2", 11, 9)', sExpected)
        sExpected = ("P1 has 5 points, P2 has 5 points,\n"+
        "it's a tie!")
        fTest('fsScore("P1", "P2", 5, 5)', sExpected)
        
        #Testing fsWinner
        sExpected = "Congratulations Player1, you have won the match"
        fTest('fsWinner("Player1", "Player2", 10, 9)', sExpected)
        sExpected = "Congratulations P2, you have won the match"
        fTest('fsWinner("P1", "P2", 0, 10)', sExpected)
        
        #Testing ftiTossWinner
        fTest('ftiTossWinner("heads", "tails", 0, 0)', (1,0))
        fTest('ftiTossWinner("tails", "heads", 0, 0)', (0,1))
        fTest('ftiTossWinner("heads", "heads", 0, 0)', (0,0))
        fTest('ftiTossWinner("tails", "tails", 0, 0)', (0,0))
        print("Unittests completed\n")
    
    def fMain():
        """Starts a game with two players and does not end until one player
        has reached iMaxPoints points. The game consists of both players
        tossing coins, if only one of them gets heads, that player gains
        a point, if not no player gains a point."""
        #Sets up the variables for the game
        iMaxPoints = 5
        iP1Points, iP2Points = 0, 0
        sP1Name, sP2Name = "Leon", "Petter"
        
        while max(iP1Points, iP2Points) < iMaxPoints:
            #Tosses coins for P1 and P2
            sP1Toss = random.choice(["heads", "tails"])
            sP2Toss = random.choice(["heads", "tails"])
            
            #Gives out points if there is a winner of the toss,
            #   then prints the winner
            iP1Points, iP2Points = ftiTossWinner(sP1Toss, sP2Toss,
                                                 iP1Points, iP2Points)
            print(fsTossWinner(sP1Name, sP2Name, sP1Toss, sP2Toss))
            
            #Prints the score
            print(fsScore(sP1Name, sP2Name, iP1Points, iP2Points))
            print("\n")
            
        
        #Prints the winner
        print(fsWinner(sP1Name, sP2Name, iP1Points, iP2Points))
            
    def fsWinner(sPlayer1, sPlayer2, iPlayer1Points, iPlayer2Points):
        "Returns the text to be displayed when a player has won"
        if iPlayer1Points > iPlayer2Points:
            sWinner = sPlayer1
        else:
            sWinner = sPlayer2
        return("Congratulations " + sWinner + ", you have won the match")
    
    def ftiTossWinner(sP1Toss, sP2Toss, iP1Points, iP2Points):
        "Returns the new scores after a cointoss had been made"
        if sP1Toss == sP2Toss:
            return(iP1Points, iP2Points)
        elif sP1Toss == "heads":
            return(iP1Points+1, iP2Points)
        else:
            return(iP1Points, iP2Points+1)
    
    def fsTossWinner(sPlayer1, sPlayer2, sPlayer1Coin, sPlayer2Coin):
        "Returns the text to be displayed when a cointoss has been made"
        if sPlayer1Coin == sPlayer2Coin:
            sEndText = "It's a tie!"
        elif sPlayer1Coin == "heads":
            sEndText = sPlayer1 + " wins!"
        else:
            sEndText = sPlayer2 + " wins!"
    
        return(sPlayer1 + " gets " + sPlayer1Coin 
        + ", " + sPlayer2 + " gets " + sPlayer2Coin
        + ". " + sEndText)
    
    def fsScore(sPlayer1, sPlayer2, iPlayer1Points, iPlayer2Points):
        "Returns the text to be displayed when a coinmatch has been won"
        if iPlayer1Points == iPlayer2Points:
            sEndText = "it's a tie!"
        elif iPlayer1Points > iPlayer2Points:
            sEndText = sPlayer1 + " is in the lead!"
        else:
            sEndText = sPlayer2 + " is in the lead!"
        return(sPlayer1 + " has " + str(iPlayer1Points) + " points, " +
        sPlayer2 + " has " + str(iPlayer2Points) + " points,\n" +
        sEndText)
    
    fUnittests()
    fMain()
  2. #2
  3. Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Sep 2013
    Location
    Saint-Petersburg, Russia
    Posts
    237
    Rep Power
    29
    The problem is that the fMain function depends on alot of functions(which are unittested by themselves) and also the random module to make random coin tosses, how could I unittests the fMain function in this code?
    You need not "unit-testing" for full functionality of big and complex function. "unit-test" should test only some small unit of functionality.

    If you want anyway to have some unit-test on this function, you can write the test with mocks which will check that fMain calls all dependent methods on the mocks (but not on real objects - since they are unit-tested separately as you told).

    I'm sorry for I am not acquainted with instruments for unit-testing in python - I'm java developer - but the matter is similar. I believe mocks could be created with tools from standard library in Python:
    http://docs.python.org/dev/library/unittest.mock

    And also, are such simple programs meant to get this complicated and long when making them using unittests or is it just me overdoing it/polishing it too much?
    Well, unit-tests could often be bigger than source code, however in your case I fear here is some other problem. I fear your code is not well-designed to be testable and tests themselves are not written greatly. As a result you test your output too much but it is not really what is most important to test

    We usually try to write separate test method for each method of program logic.
    Last edited by rodiongork; November 13th, 2013 at 06:15 AM.
  4. #3
  5. Contributing User
    Devshed Demi-God (4500 - 4999 posts)

    Join Date
    Aug 2011
    Posts
    4,904
    Rep Power
    481
    I think your program, though verbose, is quite readable. You've decided to avoid the unittest module---good idea!

    To test a random number generator you can
    - seed it to a known state;
    - pass as an argument the rng.

    Code:
    class rng:
        '''
            starts in a known state and the random numbers are easily determined in advance.
        '''
        def __init__(self):
            self.state = 0
    
        def choice(self,choices):
            self.state += 1
            return choices[self.state % len(choices)]
    or you could subclass random according to the documents.
    Last edited by b49P23TIvg; November 13th, 2013 at 09:32 AM.
    [code]Code tags[/code] are essential for python code and Makefiles!
  6. #4
  7. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Jan 2013
    Posts
    78
    Rep Power
    2
    Aaah, thanks guys. I'll try to find me a couple of seeds that works with getting both players to win. And I might also try to mock the random.choice function. Already rewritten it abit so fMain is now called fsMain, so its now a function which returns a string, and that string is the winner of the match.

    I'm not happy with the unittesting module because it takes up alot of horisontal space and it doesn't really give me any feedback on what went wrong, and ofc importing big modules is not a good thing in general.(unless I have to)
  8. #5
  9. Contributing User
    Devshed Demi-God (4500 - 4999 posts)

    Join Date
    Aug 2011
    Posts
    4,904
    Rep Power
    481
    I'm not happy with the unittesting module because it is hard to use, or it could be that I don't understand it, or it could be that I started to use it before it provided tolerant comparisons causing us write a lot of code.
    [code]Code tags[/code] are essential for python code and Makefiles!

IMN logo majestic logo threadwatch logo seochat tools logo