DEV Community

Namah Shrestha
Namah Shrestha

Posted on


Chapter 2: The Need Of A Project Structure In Python Testing

2.1 Understanding the problem with import statements.

  • Our test file looks something like this:

    import pytest
    from app import simple_calculator_function
    def test_simple_calculator_function() -> None:
        assert simple_calculator_function("5*(4+5)") == 45 # test addition and multiplication
        assert simple_calculator_function("10 - (100/2)") == -40 # test subtraction and division. 
        assert simple_calculator_function("'a' + 'b'") == 'ab' # test string concatenation
  • The import statement from app import simple_calculator_function assumes that is on the same folder as the and therefore this kind of import works.

  • We do not want to depend on this feature for tests. We want our tests to run no matter where they are.

  • Here, to make sure that the import works, we would need to make sure that and are in the same directory.

  • In case they are in different directories, we need to make sure we run both tests and the application from a common working directory. This working directory is outside both tests and src directories
    In this case, the import statement in the file would be:

        from <common_working_directory>.<app_directory>.app import simple_calculator_function.

    In this case, the tests also need to be run from the common_working_directory. The test directory would be: <common_working_directory>/tests/test_*.py.

  • This stops us from running the tests from anywhere and now we have to depend on having the same working directory as well.

  • We need to make sure that import works from anywhere and doesn't depend on the directory structure.

  • The way to make that happen is to make your project an installable project.

2.2 Solution to the problem with import statements.

  • The solution to the import problem is to turn your project into an installable package.
  • We need to setup before applying the solution. This is what we will do in this section.
  • So lets move on with a new folder structure. We create src/simple_calculator/ and tests/ directories.

    - src/
        - simple_calculator/
    - tests/
    - .gitignore

    The is placed inside src/simple_calculator/ along with an file and is placed inside tests/ along with an file.

  • As soon as we create the new directory we get an import error on the test file when we run it.

    • Unresolved Reference 'app'
    • This is because the test file can no longer find app. They are in different directories.
  • To make it find the app, we can import from the project directory's root and run tests also from the same directory like we mentioned eariler. This would mean the import statement inside would look like:

    from import simple_calculator_function
  • Like we mentioned earlier, the problem with this is that we need to run the test also from the project directory.

  • Again the idea is that would like to run our tests from anywhere without worrying about directory structures for imports.

  • To enable that we can expect our code to function as a library so that imports can happen from anywhere.

  • This means making our application installable. So that the following import works after installing our package in the virtual environment with pip.

    from import simple_calculator_function
  • Now no matter where the test is it can use the same import statement everywhere. This solves the problem of having to depend on directory structure for imports.

2.3 Making our application installable.

  • To make our application installable, we need to add a bunch of configuration files.
    • This is an open issue in the Python community.
    • The idea is, we shouldn’t need these many files to make our application installable.
    • Things could have been easier maybe, but, that is how it is at the moment.
  • NOTE: The structure that we will follow using, setup.cfg and pyproject.toml are not required to be the same always. perfectly supports entire functionalities. We can write the entire configurations on any one of these files if we have to. We are just organising our code better.
  • If you look at the documentation of setuptools: Then you’ll see that each and every configuration field in the example files has a counter part in each of the file types.
  • The community is pushing the configuration more towards the toml file and just leaving the metadata in the cfg file.

2.3.1 Understanding the pyproject.toml file.

  • The first file we will look at is pyproject.toml.
  • In the early days of Python, there was only one way to install packages. We needed a file.
  • But nowadays, there are several options such as Poetry and other such examples.
  • We can still stick to using the way and that is what we will be doing in this article.
  • We can do this by inserting a build-backend to our [build-system] in our pyproject.toml file.

    requires = ["setuptools>=42.8", "wheel"]
    build-backend = "setuptools.build_meta"
  • We have setup build-backed to use setuptools.build_meta, this will make our project run code in The build-backend requires setuptools and we mention that in the requires section.

  • So the next file to look at is, because our build-backend is setuptools.

2.3.2 Understanding the file.

  • Next is the file.
  • In the early days, used to be the place containing the installation script.
  • It would do everything required to do in order to install a python package.
  • We can run arbitrary code inside Since it is a python script.
  • This is seen as a security risk and therefore, more and more code is being stripped out of the file and put into one of these other configuration file.
  • Let’s create a basic file.

    import setuptools
    if __name__ == "__main__":
  • This basic file is going to allow us to install our package in editable mode. Since, we have mentioned setuptools.build_meta in build-system in pyproject.toml, when we run the install script, will be executed.

2.3.3 Understanding the setup.cfg file.

  • To store the metadata of the project such as Title and Description, we create a setup.cfg file.
  • The file could look as follows:

    name = something
    description = just some dummy codebase
    author = Coding with Zim
    license = MIT
    license_file = LICENSE
    platforms = unix, linux, osx, cygwin, win32
    classifiers = 
        Programming Language :: Python :: 3
        Programming Language :: Python :: 3 :: Only
        Programming Language :: Python :: 3.6
        Programming Language :: Python :: 3.7
        Programming Language :: Python :: 3.8
        Programming Language :: Python :: 3.9
    packages =
    install_requires =
    python_requires = >=3.6
    package_dir =
    zip_safe = no
  • NOTE: In [options] we have new line after = sign. This signifies that there can be more than one of these values. So, packages, install_requires, package_dir can have multiple values.

  • packages are the names of packages that we are creating.

  • install_requires are the names of requirements. Need to look at how to do this with requirements.txt.

  • python_requires denotes the versions of python supported.

  • package_dir is the directory where our application module lives. Inside the src directory. There might be other names. We are just going with the naming convention.

  • zip_safe is no. No idea what it is for now.

  • Why use this file instead of putting everything in
    Since this is just a configuration file and not a python script, we don’t have to worry about it executing arbitrary code which was the case with This is what the community wants. They want to push everything into different configuration files.

  • Then we add a requirements.txt file which contains all our dependencies.


    In our case, it only contains requests for demo purposes.
    NOTE: In we gave a version >=2. In requirements.txt , we specify the actual version. That is best practice.

  • With this our project structure looks something like this:

    - src/
        - something/
    - tests/
    - .gitignore
    - pyproject.toml
    - requirements.txt
    - setup.cfg
  • Now we should be able to install it with pip by the following command:

    your_project_folder$ pip install -e . # e means editable i guess.

Top comments (4)

thomasbnt profile image
Thomas Bnt

Please avoid full capital letters in title, it gives the impression of aggressiveness.

zim95 profile image
Namah Shrestha

Thanks will do

anikethsdeshpande profile image
Aniketh Deshpande

"-e ." means install the current directory in editable mode.

zim95 profile image
Namah Shrestha

Thank you. I shall update the same.

Timeless DEV post...

Git Concepts I Wish I Knew Years Ago

The most used technology by developers is not Javascript.

It's not Python or HTML.

It hardly even gets mentioned in interviews or listed as a pre-requisite for jobs.

I'm talking about Git and version control of course.

One does not simply learn git