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
- 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
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
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.
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.
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
Now you can see all the tests have passed after running pytest
.
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
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
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.
Top comments (0)