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.
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
- Write tests
- Get the tests to pass
- Optimize the design (Refactor)
- Repeat the process
Now let's look at how do TDD in python
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
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.
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
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
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
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.
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.
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
Now you can see all the tests have passed after running
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
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
Now you can repeat the process by applying it to another function.
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.