DEV Community

Cover image for Test-Driven Development with Python
chamodperera
chamodperera

Posted on

Test-Driven Development with Python

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

  • 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
Enter fullscreen mode Exit fullscreen mode

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

def pw_validator(password):
    pass
Enter fullscreen mode Exit fullscreen mode

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

from validator import pw_validator
Enter fullscreen mode Exit fullscreen mode

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.

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

Enter fullscreen mode Exit fullscreen mode

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
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.

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
Enter fullscreen mode Exit fullscreen mode

Now you can see all the tests have passed after running pytest.
Image description
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.

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

Enter fullscreen mode Exit fullscreen mode

Image description
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.

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
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)