5

I have a function that prints a somewhat random string to the console, for example:

from random import choice

def hello():
    print(f"Hello {choice(('Guido', 'Raymond'))}!")

Please note that my actual function is more complicated than this. The random part is a request to a database that can either succeed or fail. This means that I cannot initialize a seed to have a constant outcome.

What I have tried is to use the ellipsis, but I also need to add an ugly comment for doctest to recognize it.

def hello():
    """
    >>> hello()  # doctest: +ELLIPSIS
    Hello ...!
    """
    print(f"Hello {choice(('Guido', 'Raymond'))}!")

Is there a better strategy in this situation?

For example, instead of an ellipsis it would be great if I could test that the answer is one between Hello Guido! and Hello Raymond!.

edd313
  • 1,109
  • 7
  • 20
  • For something even uglier, you could [redirect stdout to a buffer](https://stackoverflow.com/a/22434594/11082165) and then only print a consistent subset of the output in the doctest – Brian61354270 Jul 25 '23 at 16:55
  • 1
    What exactly do you want to test? Like, are you intentionally ignoring the random part in this example? Or would you actually want to check that the random part is one of a set of values (`'Guido', 'Raymond'`), or that it follows a certain format (e.g. looks like a name)? – wjandrea Jul 25 '23 at 17:37
  • Good point, with the ellipsis I am already testing the structure, but I would prefer to check that the random part is either 'Guido' or 'Raymond' as you say. I added clarifications in the question. – edd313 Jul 25 '23 at 21:06
  • 3
    Docstrings are primarily documentation. If you need to make a function's docstring less clear as documentation to make it usable as a doctest, consider writing separate, non-doctest-based tests for that function. – user2357112 Jul 25 '23 at 22:10

1 Answers1

3

You could use regex: Hello followed by either Guido or Raymond followed by an exclamation point and newline: Hello (Guido|Raymond)!\n

However, capturing the stdout and running the regex is a lot of noise for a doctest. So instead, it'd be better to give an example in the docstring but skip testing it, and use a different test system, like pytest, which has a builtin way to capture stdout. For example:

from random import choice

def hello():
    """
    >>> hello()  # doctest: +SKIP
    Hello Raymond!
    """
    print(f"Hello {choice(('Guido', 'Raymond'))}!")
import re

def test_hello(capsys):
    hello()
    captured = capsys.readouterr()
    assert re.fullmatch(r'Hello (Guido|Raymond)!\n', captured.out)
wjandrea
  • 28,235
  • 9
  • 60
  • 81