What is the most critical thing that tests should do? It is an essential question. The answer will help you understand how to write tests. It is the question we will answer in this lesson.
Imagine you've written a function called capitalize(text)
that capitalizes the first letter of the string you pass:
capitalize('hello') # 'Hello'
Here's one way to implement it:
# main.py
def capitalize(text):
first_char = text[0].upper()
rest_substring = text[1:]
return f'{first_char}{rest_substring}'
We've created a function, so we need to check that it works.
One way to do this is to open a REPL and call a function with different arguments:
python -i main.py
capitalize('hello, hexlet!')
# => 'Hello, hexlet!'
We have ensured that the function works — at least for the arguments we passed. If the test finds any errors, we should fix them and repeat the process.
This whole process is manual testing. Its purpose is to make sure that the code works as it should. And it makes no difference to us how we implement that function. It is the answer to the question posed at the beginning of this lesson: Tests verify that the code or application works correctly, and it doesn't matter how we write the code.
How automatic tests work
All we need to do for automated testing is to repeat the checks we performed during manual testing. Good old if
and exceptions will do the trick. Even if you're not familiar with exceptions, that's okay. Two things are enough for this course: what exceptions are for and what their syntax is.
So far in Hexlet's courses, you've encountered bugs that occur accidentally, like calling a non-existent function and accessing a non-existent constant. But errors can also be created intentionally using exceptions, which we need. The exceptions mechanism generates errors:
# We throw a new exception
# The code after this expression will not run
# The script will stop with an error
raise Exception('Boom! An error occurred, stopping the execution')
print('nothing'); # It will never run
# After running, we will get the following output:
# The most recent calls are the last in the traceback:
# File "main.py", line 8, in <module>
# raise Exception('Boom! An error occurred, stopping the execution');
# Exception: Boom! An error occurred, stopping the execution
Now let's look at an example of the test:
# If the result of the function is not equal to the expected value
if capitalize('hello') != 'Hello':
# We throw an exception and terminate the test execution
raise Exception('The function is not working correctly!')
From the example above, we can see that tests have the same code as everything else. The code works in the same environment and is subject to the same coding rules and standards. It may also contain bugs, but that doesn't mean you have to write tests for tests. It's impossible to avoid all bugs, and you don't have to, or you won't get the resources you need for development.
We usually store tests in a directory at the root of the project. It's called tests, although there are other options: