Skip to content

Adopt xdoctest as primary doctest runner? #4295

@Erotemic

Description

@Erotemic

This is based on a discussion from #4169

The basic question is might networkx benefit from the syntax flexibility provided by xdoctest?

My "The case for xdoctest in networkx" pitch is largely copied from the aforementioned thread:

I do think using xdoctest would be an overall improvement, and it would make writing / maintaining doctests much easier. I recently had to modify a PR to conform to the more restrictive builtin-doctest syntax. In the future I --- and perhaps others --- would prefer to utilize xdoctest syntax.

The main feature I'm making use of is "new-style got/want" tests, where print statements don't need to be broken up. For instance, the following works with xdoctest:

    Example
    -------
    >>> open_to_close = {'{': '}', '(': ')', '[': ']'}
    >>> seq = '({[[]]})[[][]]{{}}'
    >>> all_decomp = generate_all_decomp(seq, open_to_close)
    >>> node, *decomp = all_decomp[seq]
    >>> pop_open, pop_close, head, tail, head_tail = decomp
    >>> print('node = {!r}'.format(node))
    >>> print('pop_open = {!r}'.format(pop_open))
    >>> print('pop_close = {!r}'.format(pop_close))
    >>> print('head = {!r}'.format(head))
    >>> print('tail = {!r}'.format(tail))
    >>> print('head_tail = {!r}'.format(head_tail))
    node = '('
    pop_open = '('
    pop_close = ')'
    head = '{[[]]}'
    tail = '[[][]]{{}}'
    head_tail = '{[[]]}[[][]]{{}}'

However, to refactor that to work with the builtin doctest module would require following each print statement with the text it produced:

    Example
    -------
    >>> open_to_close = {'{': '}', '(': ')', '[': ']'}
    >>> seq = '({[[]]})[[][]]{{}}'
    >>> all_decomp = generate_all_decomp(seq, open_to_close)
    >>> node, *decomp = all_decomp[seq]
    >>> pop_open, pop_close, head, tail, head_tail = decomp
    >>> print('node = {!r}'.format(node))
    node = '('
    >>> print('pop_open = {!r}'.format(pop_open))
    pop_open = '('
    >>> print('pop_close = {!r}'.format(pop_close))
    pop_close = ')'
    >>> print('head = {!r}'.format(head))
    head = '{[[]]}'
    >>> print('tail = {!r}'.format(tail))
    tail = '[[][]]{{}}'
    >>> print('head_tail = {!r}'.format(head_tail))
    head_tail = '{[[]]}[[][]]{{}}'

Personally, I think the former is far more readable (you see a block of code that produces something and then you see the output as one single chunk, versus forcing humans to work like a REPL).

There are also minor issues with trailing whitespace causing got/want errors in the original doctest, the fact that any non-captured variable must provided with a "want" string, and the general issue of forcing the programmer to distinguish between lines that start a statement versus are continuations of previous statements.

For reference xdoctest is small, has no minimal dependencies, and is 100% compatible with the current structure of networks (in fact networkx is one of the main test-cases I used to ensure backwards compatibility when I wrote xdoctest), running pytest --xdoctest-modules --xdoctest-global-exec "import networkx as nx" will run all of the existing tests correctly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    DiscussionIssues for discussion and decision making

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions