1

I am new here and new to programming (I have only been programming for 2 weeks). Does anyone have any suggestions on how to test a function that takes no parameters. I have tested functions that takes parameters but not one that takes no parameters.

On the test function I try passing an arguments expected as the letter 'a' in the valid_letter() function. But it gives the error that 'a' is not defined.

def valid_letter():
    '''Function valid_letter
       Parameters None
       continuously asks for a valid letter
       if invalid data is provided
    '''
    while True:
        column = input("What column do you wish to select from a to g? ")
        if column != "a" and column != "b" and\
           column != "c" and column != "d" and\
           column != "e" and column != "f" and\
           column != "g" and column != "h":
           print("Your input is invalid")
           continue
        else:
         return column
         break  

def check_valid_column(expect):
    answer = valid_letter()
    print("Input: {}".format(answer))
    print("Expected: {}, Actual {}".format(expect, answer))

def main():

   check_valid_column(a)
main()
WunielH
  • 11
  • 3
  • Welcome to SO! could you supply us with the expected output? and possibly clarify what you want to do here? – Ironkey Oct 03 '20 at 17:41
  • My guess is that you intended to do `check_valid_column("a")`, so with the character "a", not the variable `a`. – trincot Oct 03 '20 at 17:42
  • You can rewrite that `if` clause as `if column not in ["a", "b", "c", "d", "e", "f", "g", "h"]:`. Also, no need for `break` after `return column`. And no need for `continue` in this case as it's already happening implicitly. – jarmod Oct 03 '20 at 17:46
  • see if this helps: https://stackoverflow.com/questions/6271947/how-can-i-simulate-input-to-stdin-for-pyunit – dishant makwana Oct 03 '20 at 17:58
  • @trincot thank you. That was why it was not working, I was not passing it the letter as a string. – WunielH Oct 03 '20 at 18:09
  • Side note: "Long lines can be broken over multiple lines by wrapping expressions in parentheses. These should be used in preference to using a backslash for line continuation" ([PEP 8](https://www.python.org/dev/peps/pep-0008/)). So instead of `if column != "a" and column != "b" and\ ... column != "h":` you could have `if (column != "a" and column != "b" and ... column != "h"):` – Cristian Ciupitu Oct 07 '20 at 10:43

3 Answers3

1

(Note: the original function was broken due to indentation so while I was fixing it I took the liberty of rewriting it to be simpler. It should still behave exactly the same as your intended original implementation and the testing strategy, which is the real focus of the question, is exactly the same regardless of implementation details.)

One way to do this is with patch:

def valid_letter() -> str:
    '''
    Prompts the user for a column between 'a' and 'g'.
    Continuously asks for a valid letter if invalid data is provided.
    '''
    while True:
        column = input("What column do you wish to select from a to g? ")
        if ord(column) in range(ord('a'), ord('g') + 1):
            return column
        print("Your input is invalid")


from unittest.mock import Mock, patch


def test_valid_letter() -> None:
    with patch('builtins.input', new=Mock(return_value='a')):
        assert valid_letter() == 'a'
    with patch('builtins.input', new=Mock(side_effect=['z', 'q', 'g', 'c'])):
        assert valid_letter() == 'g'


test_valid_letter()

The patch statements in the test replace the builtin input function with a Mock object that returns a particular argument. In the first test it simply returns 'a', and so we assert that valid_letter() will return that same value. In the second test it returns successive values from the list each time it's called; we assert that valid_letter() will continue calling it in a loop until it reaches 'g'.

Another method would be via dependency injection:

from typing import Callable
from unittest.mock import Mock


def valid_letter(input_func: Callable[[str], str]) -> str:
    '''
    Prompts the user for a column between 'a' and 'g', using input_func.
    Continuously asks for a valid letter if invalid data is provided.
    '''
    while True:
        column = input_func("What column do you wish to select from a to g? ")
        if ord(column) in range(ord('a'), ord('g') + 1):
            return column
        print("Your input is invalid")


def test_valid_letter() -> None:
    assert valid_letter(Mock(return_value='a')) == 'a'
    assert valid_letter(Mock(side_effect=['z', 'q', 'g', 'c'])) == 'g'


test_valid_letter()

In this example, rather than having valid_letter call the builtin input function, it accepts an arbitrary input function that the caller supplies. If you call it like:

valid_letter(input)

then it behaves exactly like the original, but the caller can also pass in arbitrary replacements without having to use patch. That makes testing a bit easier, and it also allows for the possibility of a caller wrapping or replacing input to allow for a different UI style -- for example, if this function were being used in a GUI app the caller could pass in an input_func that prompts the user via a dialog box instead of the terminal.

The same testing/injection logic applies to the print function -- you might find it useful to have your test validate that print is called each time an invalid value is entered, and another caller might find it useful to use an alternative output function.

Samwise
  • 68,105
  • 3
  • 30
  • 44
  • This is a new programmer. IMO, it's quite inappropriate to add type information to his code. That's only going to confuse the heck out of him/her. - And you're throwing a lambda at him/her too. That's going to be way over the OPs head. I'd suggest that you stick with the basics when helping someone who says outright that they are a new programmer. – CryptoFool Oct 03 '20 at 18:17
  • Personally, when I was learning I found it most useful to have examples of code written the way it'd be written in the "real world" following good practices; toy classroom examples that teach bad habits just slow you down in the long run IMO, so I avoid "dumbing it down" for new programmers and focus on giving working examples. If they care to learn more, now they have stuff they can google, and if not, they can either copy+paste or just discard the parts they don't need. Luckily there is a very wide range of examples out there and so everyone is able to seek out what works best for them! – Samwise Oct 03 '20 at 18:21
0

The function will work with no parameters, and will still as for user input since you have input() in your called function.

llub888
  • 1
  • 1
0

The way you call valid_letter() is fine. That is not the issue. But you intended to verify that the input was "a" (which apparently represents the correct answer).

So then you should not do check_valid_column(a) as a is not a defined variable, but check_valid_column("a"), so with the character "a".

trincot
  • 317,000
  • 35
  • 244
  • 286