Pytest practices
Published: 2023-11-11 07:33:20

[Pytest](https://docs.pytest.org/en/7.4.x/) is a popular testing framework for Python. It has a clean and easy-to-understand syntax, making it accessible for beginners while providing advanced features for more complex testing scenarios. I would like to share my testing process using Pytest.

## Prepare for testing
### Install Pytest from command
```bash
pip3 install pytest
```

### Project structure

Then we should make sure our testing environment is working. Let's look my sample test structure:

```bash
project/
├── sample.py
└── tests/
    └── sample_test.py
    └── __init__.py
```

Note that the `__init__.py` in `tests` folder is to let Pytest  find the module correctly, without that, you may encounter this error:
```bash
tests/sample_test.py:2: in <module>
    import sample
E   ModuleNotFoundError: No module named 'sample'
```

The example tests are some basic check, like:

```python
# sample.py
def square(x: float) -> float:
    return x * x
```

```python
# tests/sample_test.py
import sample


def test_square():
    assert sample.square(5) == 25

```

Then we can run the test command at project root folder: 
```bash
pytest # Or pytest <test file>.py for one specific file
```

The test result should be like
```bash
 % pytest
================================================================ test session starts ================================================================
platform darwin -- Python 3.11.5, pytest-7.4.3, pluggy-1.3.0
rootdir: /Users/name/path/to/project
plugins: anyio-3.7.1
collected 1 item                                                                                                                                    

tests/sample_test.py .                                                                                                                        [100%]

================================================================= 1 passed in 0.01s =================================================================
```

### Test my project functions

I tested my `process_line` function with the following test case:
```python
import pytest
from txt2html import process_line


@pytest.mark.parametrize(
    "input_line, expected_output",
    [
        ("This is a normal line.", "This is a normal line."),
        ("*This should be italic.*", "<i>This should be italic.</i>"),
        ("**This should not be bold.**", "**This should not be bold.**"),
        (
            "*Multiple* *italics* *in* *one* *line.*",
            "<i>Multiple</i> <i>italics</i> <i>in</i> <i>one</i> <i>line.</i>",
        ),
        ("No italics in this line.", "No italics in this line."),
    ],
)
def test_process_line(input_line, expected_output):
    result = process_line(input_line)
    assert result == expected_output

```

And it gave me results that my code has errors: (I found the summary info is good enough for me to know the issue)
```bash
============================================================== short test summary info ==============================================================
FAILED tests/test_process_line.py::test_process_line[This is a normal line.-This is a normal line.] - AssertionError: assert 'This is a normal line. ' == 'This is a normal line.'
FAILED tests/test_process_line.py::test_process_line[*This should be italic.*-<i>This should be italic.</i>] - AssertionError: assert '*This should be italic.* ' == '<i>This shou...e italic.</i>'
FAILED tests/test_process_line.py::test_process_line[**This should not be bold.**-**This should not be bold.**] - AssertionError: assert '**This shoul...t be bold.** ' == '**This should not be bold.**'
FAILED tests/test_process_line.py::test_process_line[*Multiple* *italics* *in* *one* *line.*-<i>Multiple</i> <i>italics</i> <i>in</i> <i>one</i> <i>line.</i>] - AssertionError: assert '<i>Multiple<...<i>line.</i> ' == '<i>Multiple<... <i>line.</i>'
FAILED tests/test_process_line.py::test_process_line[No italics in this line.-No italics in this line.] - AssertionError: assert 'No italics in this line. ' == 'No italics in this line.'
================================================================= 5 failed in 0.05s =================================================================
```
Here I realized my code always added a tailing space, which is not as expected. After adding `strip()` to remove space, I run again and this time it give me another error:

```bash
============================================================== short test summary info ==============================================================
FAILED tests/test_process_line.py::test_process_line[This should be *italic*.-This should be <i>italic</i>.] - AssertionError: assert 'This should ...i>italic*</i>' == 'This should ...i>italic</i>.'
============================================================ 1 failed, 4 passed in 0.04s ============================================================
```

This shows me my code has bugs cannot convert to italic at this situation: the `*` has connected with an symbol `.`
```text
*word*.
```

This is so great! The test cases seamlessly steering me through potential pitfalls and making debugging an enjoyable challenge. Now I'm going to find out how to deal with that bug.