Test-Driven Development with Python
Tags: python, testing, tutorial, devops
Published: 2022-07-30 06:15:00

Test-driven development (TDD) is an established technique for delivering better software more rapidly and sustainably over time. It is a standard practice in software engineering.

In this blog, I'll be explaining about test-driven development (TDD) and a how-to guide to TDD with python. In case you didn't even hear about it, also tag along.

## What is Test-Driven Development & Why?

Test-driven development (TDD) is a software development process relying on software requirements being converted to test cases before software is fully developed, and tracking all software development by repeatedly testing the software against all test cases. In short, It is when you write tests before you write the code that's being tested.

It has number of benefits

- Improved design of the system
- Better code quality and flexibility
- Good documentation as a byproduct
- Lower development costs and increased developer productivity
- Enhanced developer satisfaction
- Easier to maintain

## The Process

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/639uc4e81wyp4ob8c50e.png)

- Write tests
- Get the tests to pass 
- Optimize the design (Refactor)
- Repeat the process

Now let's look at how do TDD in python

## Prerequisites

There are several widely used libraries in python to write tests. In this guide, I am using a library called `pytest`. So make sure to install it first.

```
pip install -U pytest
```


Let's consider a small function to check whether a password is valid or invalid

## Writing Tests

Let's say a valid password meets the following requirements.

- length must be greater than 8
- should contain a uppercase letter [A-Z]
- should contain a lowercase letter [a-z]
- should contain a digit [0-9]

So the first step is to write the tests for the function. I'll start by declaring an empty function.

`validator.py`

```python
def pw_validator(password):
    pass
```
Now you can start writing tests in the same file or a separate file. The second option improves the code readability. If you choose to store the tests in a separate file, it should start with `test_` to make it recognizable by `pytest`

`test_validator.py`

```python
from validator import pw_validator
```

A test is basically a function that uses `assert()` method to check our validator returns the correct output. This test function also should start with `test_`.

Below you can see how I implemented some test functions with several valid & invalid passwords with the expected output.

```python
def test_case_1():
    assert pw_validator("8c4XTH&Z4a5z1Cxo") == True

def test_case_2():
    assert pw_validator("m6oj4l*6r#s$") == False #doesn't contain any upper case letter

def test_case_3():
    assert pw_validator("DU$8$256Q*W@V6!KSED@H") == False #doesn't contain any lower case letter

def test_case_4():
    assert pw_validator("DO!OPhXnqCjBR&J") == False #doesn't contain any digits

def test_case_5():
    assert pw_validator("9s@X85") == False #length is lower than 8

```
Now you can simply type `pytest` in the console to run the tests.
Of course, the tests will fail first as we didn't implement the validator function yet.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zhumupn93eqlzwnz4jdz.png)
Here each failed test is represented by a `"F"`. Passes tests will be represented by a `"."`

When writing test functions you can include several `assert` methods in a single function. But the `pytest` understands each test by functions. So the best practice is to include `assert` methods in separate functions. 

## Implement the function to pass tests

After writing tests, we can start working on the validator function. You can check each required condition with an `if else` statement as below.

```python
def pw_validator(password):

    if len(password) >= 8:

        if any(char.isdigit() for char in password):

            if any(char.isupper() for char in password):

                if any(char.islower() for char in password):
                    return True
                else:
                    return False
                    
            else:
                return False
                    
        else:
            return False
                
    else:
        return False
```
Now you can see all the tests have passed after running `pytest`.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hjgwu7ldf2u2uivmujc3.png)
You might definitely not come up with the correct implementation at first. For example, say you forgot to add functionality to check whether the password length is greater than 8 characters. Then one of the tests will return as failed.

```python
def pw_validator(password):

        if any(char.isdigit() for char in password):

            if any(char.isupper() for char in password):

                if any(char.islower() for char in password):
                    return True
                else:
                    return False

            else:
                return False
                    
        else:
            return False

```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nfi4mxe7xd9upalbgsjq.png)
So your job is to improve the function until it passes all the tests.

## Refactor
In this step, you'll make your code more readable, concise, and clean. In our case, you might notice the validator function seems a bit untidy with the `if else` tree. We can refactor it as below to improve quality.

```python
def pw_validator(password):
    return True if len(password) >= 8 and any(char.isdigit() for char in password) and any(char.isupper() for char in password) and any(char.islower() for char in password) else False
```
Now you can repeat the process by applying it to another function.

## Conclusion
At this time, you might have a clear understanding of what TDD is and how to use it in your project. TDD is widely used in data science & machine learning as it involves lots of testing.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/52l15xsrrolrziiz6q7j.png)