Skip to content

Relative vs absolute imports in tests #6452

Description

@drdavella

This discussion has been moved from #6451.

Right now, it appears that nearly all tests in astropy do something like the following at the top:

from ....extern.six.moves
from ....io import fits
from ....utils.data import conf, get_pkg_data_filename

In general, it is considered good practice to use absolute imports in unit tests so that the tests actually run against installed code when possible. This is important since it means the tests run against the same code that users will see, and it can help identify packing issues before distributing.

This proposal is primarily motivated by this section of the pytest documentation.

However, there are other considerations (#6451 (comment)) that may prevent this from being practical for astropy. In summary, more investigation/discussion is needed.

Activity

  1. pllim commented on Aug 17, 2017

    @pllim
    Member

    For example, let's say we remove somefunction() from astropy.somepackage but forgot to remove test_somefunction() that imports somefunction. But we also have an older version of astropy with somefunction still in it. Will the test somehow runs somefunction from the older copy and thus erroneously pass? The correct behavior is for the test function to fail with ImportError, so we know that we need to remove the test as well.

    I don't have time to investigate this scenario in detail, so I'll just throw it out here for your consideration. I ran into problems like this in the past but I don't recall enough to give you complete description of it.

  2. drdavella commented on Aug 17, 2017

    @drdavella
    ContributorAuthor

    It's a good question, and I think the answers to some of these kinds of questions will have to come from some empirical testing.

    This is hedging a bit, but one of the reasons I brought it up in #6451 is because the answer to some of these questions might depend on whether we do some restructuring to allow pytest to run directly.

  3. MSeifert04 commented on Aug 17, 2017

    @MSeifert04
    Contributor

    Will the test somehow runs somefunction from the older copy and thus erroneously pass?

    Of course if you use absolute imports and run pytest astropy (it won't pass for python setup.py test) it will test against the first discovered installed astropy version (which could be one that had still somefunction).

    I regularly use running pytest directly if I want to know if the just installed package passes the tests (or if something went wrong) in my projects. However given that we have astropy.test() that's not really necessary for astropy.

  4. taldcroft commented on Aug 18, 2017

    @taldcroft
    Member

    Since astropy bundles tests, it seems to me safer to use relative imports. Then I can be absolutely positively sure that when I run tests (either installed or from the git source) that there is concordance between the tests and the code. With absolute imports it seems possible that when I think I am testing my git source that I am accidentally running against installed astropy. Is that possible in practice?

  5. astrofrog commented on Aug 22, 2017

    @astrofrog
    Member

    Will the test somehow runs somefunction from the older copy and thus erroneously pass?

    I've just tested this, and (at least on Python 3), the answer is no. Check out the following example:

    (start in empty directory)
    $ mkdir -p astropy/table/tests
    $ touch astropy/__init__.py
    $ touch astropy/table/__init__.py
    $ touch astropy/table/tests/__init__.py
    $ pico astr
    $ echo "from astropy.table import Table" >> astropy/table/tests/test_table.py
    $ pytest astropy
    ============================= test session starts ==============================
    platform darwin -- Python 3.6.2, pytest-3.1.3, py-1.4.34, pluggy-0.4.0
    rootdir: /Users/tom/tmp/test, inifile:
    plugins: cov-2.5.1, mpl-0.8, xdist-1.18.2
    collected 0 items / 1 errors 
    
    ==================================== ERRORS ====================================
    ______________ ERROR collecting astropy/table/tests/test_table.py ______________
    ImportError while importing test module '/Users/tom/tmp/test/astropy/table/tests/test_table.py'.
    Hint: make sure your test modules/packages have valid Python names.
    Traceback:
    astropy/table/tests/test_table.py:1: in <module>
        from astropy.table import Table
    E   ImportError: cannot import name 'Table'
    !!!!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!!!
    =========================== 1 error in 0.15 seconds ============================
    

    At least with Python 3, I think there is no danger of importing another installation?

    EDIT: tested Python 2.7 and the test also fails.

  6. astrofrog commented on Aug 22, 2017

    @astrofrog
    Member

    I think it would be helpful if someone could come up with an example where it does matter though!

  7. astrojuanlu commented on Aug 23, 2017

    @astrojuanlu
    Member

    With absolute imports it seems possible that when I think I am testing my git source that I am accidentally running against installed astropy. Is that possible in practice?

    Wouldn't pip install --editable /astropy/git/source solve this problem? This way installed code is the git source.

  8. taldcroft commented on Aug 23, 2017

    @taldcroft
    Member

    @astrofrog - I tried a couple of simple cases (just running like normal and with a PYTHONPATH set for a test with an absolute import) and confirm that pytest still imports the correct local astropy.

    So if nobody can actually demonstrate this failure mode AND if there is an actual practical benefit, then I would be 👍 with going to absolute imports.

    I'm not sure I have seen a clear statement of what we can do with astropy (testing-wise as developers) with absolute imports that is not possible with relative imports. What's the benefit?

  9. astrofrog commented on Aug 23, 2017

    @astrofrog
    Member

    If we go with absolute imports, it might be cleaner to do it everywhere rather than just with tests?

    Note that PEP8 actually recommends absolute imports unless there is a good reason not to

  10. astrofrog commented on Aug 23, 2017

    @astrofrog
    Member

    Also I have a script somewhere that can convert all imports from relative to absolute, so if we do want to do this, that might be easier than doing it by hand!

  11. drdavella commented on Aug 23, 2017

    @drdavella
    ContributorAuthor

    I'm not sure I have seen a clear statement of what we can do with astropy (testing-wise as developers) with absolute imports that is not possible with relative imports. What's the benefit?

    This is one reason that I actually hadn't thought of:

    Note that PEP8 actually recommends absolute imports unless there is a good reason not to

    The other main motivation for using absolute imports in your tests is that it means that the tests have to import your package in the same way that any user code does. The tests can no longer get away with having a privileged position just because they live within the same code base. This gives you some additional confidence that what you are actually testing is the same as what your users are using, and it can help identify packaging issues earlier.

  12. mhvk commented on Aug 23, 2017

    @mhvk
    Contributor

    On absolute imports elsewhere: I'd definitely prefer to have from astropy.extern... over from ..extern ..., and generally feel that between submodules an absolute path is clearer. I'm less sure that I would really like to replace from .utils ... with from astropy.time.utils ..., i.e., imports within a submodule perhaps are clearer using relative imports.

    Maybe one could have a more general rule to avoid relative imports from a module above oneself? That would cover all tests, and, to me at least, make the rest of the code clearer (and would be easy to search & replace using a script).

    That said, I don't want to let this distract from the question for this PR, which was explicitly about tests. I think that is a good idea independently.

  13. drdavella commented on Aug 23, 2017

    @drdavella
    ContributorAuthor

    @mhvk it looks like PEP328 recognizes that it's difficult for a module within a package to import itself without relative imports, so your example of from .utils ... should continue to be acceptable.

  14. taldcroft commented on Aug 23, 2017

    @taldcroft
    Member

    E.g. in astropy/io/ascii/ecsv.py, the current is:

    from ...extern import six
    
    from . import core, basic
    from ...table import meta, serialize
    from ...utils.data_info import serialize_context_as
    

    The suggestion of @mhvk would be the following (after re-arranging lines a bit), which I like. Within the context of a sub-package it makes a clear distinction between external dependences and sub-package modules.

    from astropy.extern import six
    from astropy.table import meta, serialize
    from astropy.utils.data_info import serialize_context_as
    
    from . import core, basic
    
  15. drdavella commented on Aug 23, 2017

    @drdavella
    ContributorAuthor

    This is a question that probably deserves an entirely separate thread but @taldcroft's example reminded me to ask it: is there a good reason that we continue to package six as an extern? Will the need for this go away with Py2?

  16. pllim commented on Aug 23, 2017

    @pllim
    Member

    is there a good reason that we continue to package six as an extern?

    This is a moot discussion once we remove Python 2 support. So, I'd say, just leave it as-is until we remove it completely for Python 3.

  17. mhvk commented on Aug 23, 2017

    @mhvk
    Contributor

    @taldcroft - yes, that is what I had in mind, and I think your example indeed shows it is an improvement.

  18. bsipocz commented on Aug 28, 2017

    @bsipocz
    Member

    is there a good reason that we continue to package six as an extern?

    and I can't wait the big cleanup session at the meeting when we finally can get rid all of it :)

  19. astrofrog commented on Nov 30, 2018

    @astrofrog
    Member

    See #8200 for an example implementation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions