Making PyTest in repl.it work
Tags: replit, python, pytest, beginners
Published: 2022-03-30 07:44:03

This is a very short guide on how to run PyTest within a [repl.it](http://repl.it) environment. It covers some of the issues I dealt with in trying to get PyTest working on repl.it instances. For the purposes of this article, I will be using the below 4 line script, named `test_main.py`.

# Making a Simple Test

Pytest looks at your filenames and functions—anything that begins with “test” are eligible targets for pytest to run on. 

Make a file called `test_main.py`.

Write two assertions—if you can’t think of any, just copy the below in:

```python
def test_one_equals_one():
  assert 1 == 1
  
def test_one_does_not_equal_two():
  assert 1 != 2
```

# Using PyTest in repl.it

[True as of April 1, 2022]

You may have stumbled upon guides where testing was as simple as invoking `pytest` on the command line. 

However, doing so in [repl.it](http://repl.it) will give you the error below:

```python
$ pytest
/usr/bin/env: ‘./python3’: No such file or directory
```

This error is because [repl.it](http://repl.it) instantiates instances within a virtual environment, as shown below:

```python
~/ImperfectGaseousLocks$ which python
/home/runner/ImperfectGaseousLocks/venv/bin/python
```

Let’s see where pip installed pytest...

```python
~/ImperfectGaseousLocks$ which pytest
/home/runner/ImperfectGaseousLocks/venv/bin/pytest
```

Looks good to me! So why does pytest try to call python over in /usr/bin/env? How do we call pytest using the correct python? 

# PATH issues

As it turns out, pytest has a shebang: `#!/usr/bin/env`. The implication is that when you invoke `pytest`, the invocation order becomes:

1. /home/runner/ImperfectGaseousLocks/venv/bin/pytest invoked
2. pytest tells the system to use `#!/usr/bin/env`
3. `/usr/bin/env` does not have `./python3`, so it yells at us

# Solution 1

Use `python -m pytest` in the directory where the test file lies.

Heading over to [the Python documentation](https://docs.python.org/3/using/cmdline.html), you can see this under “interface options”

```bash
-m <module-name>
Search sys.path for the named module and execute its contents as the __main__ module.

Since the argument is a module name, you must not give a file extension (.py). The module name should be a valid absolute Python module name, but the implementation may not always enforce this (e.g. it may allow you to use a name that includes a hyphen).

Package names (including namespace packages) are also permitted. When a package name is supplied instead of a normal module, the interpreter will execute <pkg>.__main__ as the main module. This behaviour is deliberately similar to the handling of directories and zipfiles that are passed to the interpreter as the script argument.

Note This option cannot be used with built-in modules and extension modules written in C, since they do not have Python module files. However, it can still be used for precompiled modules, even if the original source file is not available.
If this option is given, the first element of sys.argv will be the full path to the module file (while the module file is being located, the first element will be set to "-m"). As with the -c option, the current directory will be added to the start of sys.path.

-I option can be used to run the script in isolated mode where sys.path contains neither the current directory nor the user’s site-packages directory. All PYTHON* environment variables are ignored, too.

Many standard library modules contain code that is invoked on their execution as a script. An example is the timeit module:

python -m timeit -s 'setup here' 'benchmarked code here'
python -m timeit -h # for details
```

In English, when you use `-m`, Python imports PyTest for you to run as a script. This makes sure that relative imports will work as expected, and thus PyTest should too.

With this, python sees pytest, and pytest ignores the shebang line and executes successfully.

```bash
~/ImperfectGaseousLocks$ python -m pytest
==================================== test session starts ====================================
platform linux -- Python 3.8.12, pytest-7.1.1, pluggy-0.13.1
rootdir: /home/runner/ImperfectGaseousLocks
collected 2 items                                                                           

test_main.py ..                                                                       [100%]

===================================== 2 passed in 0.14s =====================================
```

# Solution 2

Change the shebang part of the pytest script!

The error tells us exactly what’s wrong. A file called “./python3” does not exist. However, what does exist is a file called “python3,” so let’s fix that.

1. `~/ImperfectGaseousLocks$ vim venv/bin/pytest`
2. press `i` for insertion mode
3. Change `/usr/bin/env ./python3` to `/usr/bin/env python3`
4. press esc to exit insertion mode
5. `:x` to exit and save that script
6. Try running pytest alone in the directory where your tests are again

```bash
~/ImperfectGaseousLocks$ pytest -v
==================================== test session starts ====================================
platform linux -- Python 3.8.12, pytest-7.1.1, pluggy-0.13.1 -- /home/runner/ImperfectGaseousLocks/venv/bin/python3
cachedir: .pytest_cache
rootdir: /home/runner/ImperfectGaseousLocks
collected 2 items                                                                           

test_main.py::test_one_equals_one PASSED                                              [ 50%]
test_main.py::test_one_does_not_equal_two PASSED                                      [100%]

===================================== 2 passed in 0.12s =====================================
```

Success!

PATH issues can be a nightmare to navigate, and we all inevitably encounter silly issues like these once in a while.

Please comment if you have any questions.

This is my first ever technical blog post. I welcome any constructive criticism and I'm open to learning from anyone regardless of experience level. Hi!