Hello. This is leo1109. This time is a continuation of the article (code format check) of previous.
All the code used in the article has been uploaded on GitHub.
It's about writing a test. Use pytest.
The pytest documentation has the following examples:
inc (x) is a function that returns x plus one. (Increment) As a test, test_answer () is defined.
# content of test_sample.py
def inc(x):
return x + 1
def test_answer():
assert inc(3) == 5
Run the test. You need to install pytest, so either run it with pip or Please install using requirements.txt in the repository.
pip install pytest
pip install -r requrements.txt
Try running pytest.
$ pytest
======= test session starts ========
collected 1 item
test_sample.py F
======= FAILURES ========
_______ test_answer ________
def test_answer():
> assert inc(3) == 5
E assert 4 == 5
E + where 4 = inc(3)
test_sample.py:5: AssertionError
======= 1 failed in 0.12 seconds ========
Since inc (3) is supposed to return 4, it is not equal to 5, so it was judged as a failure.
Now let's write a test.
get_fibonacci_by_index(1)
Returns 1 so let's write a test to make sure it returns 1.
# python 3.5.2
import my_math
class TestGetFibonacciByIndex:
def test(self):
assert my_math.get_fibonacci_by_index(1) == 1
The following is the result of execution.
$ pytest my_math_test.py
================================================================================= test session starts =================================================================================
my_math_test.py .
============================================================================== 1 passed in 0.03 seconds ===============================================================================
Unlike before, it was displayed as 1 passed.
Let's test other patterns as well.
To add a test case, it is easy to add an assert as it is. Depending on the granularity of the test, you may need to make sure that the return value is an Int.
Since it is applied as Python code, not only various operators but also arrays and dictionaries can be used.
# python 3.5.2
import my_math
class TestGetFibonacciByIndex:
def test(self):
assert my_math.get_fibonacci_by_index(1) == 1
assert my_math.get_fibonacci_by_index(2) == 1
assert my_math.get_fibonacci_by_index(3) == 2
assert my_math.get_fibonacci_by_index(4) > 2
assert (my_math.get_fibonacci_by_index(5) == 5) is True
def test_is_instance(self):
assert isinstance(my_math.get_fibonacci_by_index(2), int)
def test_as_array(self):
expected = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
got = []
for i in range(1, 11):
got.append(my_math.get_fibonacci_by_index(i))
assert expected == got
Let's run it. When run with the v option, the results are displayed for each test method. The method name must start with test for the test to run.
$ pytest my_math_test.py -v
================================================================================= test session starts =================================================================================
my_math_test.py::TestGetFibonacciByIndex::test PASSED
my_math_test.py::TestGetFibonacciByIndex::test_is_instance PASSED
my_math_test.py::TestGetFibonacciByIndex::test_as_array PASSED
============================================================================== 3 passed in 0.09 seconds ===============================================================================
By the way, what happens if you give 0 or a negative number to ``` get_fibonacci_by_index (x)` ``? Until now, you have only given natural numbers.
When I gave 0 and -1, I got 1. Is this the intended behavior?
>>> import my_math; my_math.get_fibonacci_by_index(0)
1
>>> import my_math; my_math.get_fibonacci_by_index(-1)
1
Looking at the source code, it seems that it returns 1 unless it goes into ``` range (1, x)` ``.
It's important to write a test for different cases, but it's hard to reach out too much. For example, what if a string comes in as an argument? What if the bool value comes? Etc.
In this `get_fibonacci_by_index (x)`
, (x) should be for natural numbers.
Therefore, it should be possible to define that only natural numbers are assumed.
... but this time let's write a test! So let's take a detour a little. See the test case below.
def test_bool(self):
assert my_math.get_fibonacci_by_index(True) == 1
assert my_math.get_fibonacci_by_index(False) == 1
This test passes unexpectedly.
The reason is that `` `True, Falsecan be evaluated as
Int```, which is 1,0 respectively.
>>> int(True)
1
>>> int(False)
0
In the future, if you add an implementation that strictly checks the type, this test may not pass.
However, the case where True, False
is specified should not be the intended pattern.
Therefore, let's write a test that expects TypeError
this time.
pytest.You can use raises with the with clause to write tests that expect exceptions.
def test_bool(self):
with pytest.raises(TypeError):
my_math.get_fibonacci_by_index(True)
with pytest.raises(TypeError):
my_math.get_fibonacci_by_index(False)
But of course the test doesn't pass.
It doesn't look very good to leave it in this state ...
$ pytest -v my_math_test.py ================================================================================= test session starts =================================================================================
my_math_test.py::TestGetFibonacciByIndex::test PASSED my_math_test.py::TestGetFibonacciByIndex::test_is_instance PASSED my_math_test.py::TestGetFibonacciByIndex::test_as_array PASSED my_math_test.py::TestGetFibonacciByIndex::test_bool FAILED
====================================================================================== FAILURES ======================================================================================= __________________________________________________________________________ TestGetFibonacciByIndex.test_bool __________________________________________________________________________
self = <my_math_test.TestGetFibonacciByIndex object at 0x10566de10>
def test_bool(self):
with pytest.raises(TypeError):
my_math.get_fibonacci_by_index(True)
E Failed: DID NOT RAISE <class 'TypeError'>
my_math_test.py:30: Failed ========================================================================= 1 failed, 3 passed in 0.12 seconds ==========================================================================
So let's skip this test once.
## Skip the test
Let's use pytest as a decorator.
#### **`pytest.mark.You can skip the target test by using skip.`**
Add `` `pytest``` to import.
import pytest
..
@pytest.mark.skip
def test_bool(self):
with pytest.raises(TypeError):
my_math.get_fibonacci_by_index(True)
with pytest.raises(TypeError):
my_math.get_fibonacci_by_index(False)
You can also specify the conditions to skip.
sys.version_info is an idiom to get the Python version.
Of course, don't forget to import sys.
import sys .. @pytest.mark.skipif(sys.version_info < (3,5), reason="requires python 3.5") def test_requires_35(self): assert True
If you run it on Python 2.7.11 and Python 3.5.2 respectively, you can see that the target test is skipped in 2.7.11.
$ pytest -v my_math_test.py ================================================================================= test session starts ================================================================================= platform darwin -- Python 2.7.11, pytest-3.2.0, py-1.4.34, pluggy-0.4.0
my_math_test.py::TestGetFibonacciByIndex::test_requires_35 SKIPPED
$ pytest -v my_math_test.py ================================================================================= test session starts ================================================================================= platform darwin -- Python 3.5.2, pytest-3.2.0, py-1.4.34, pluggy-0.4.0
my_math_test.py::TestGetFibonacciByIndex::test_requires_35 PASSED
This is useful when you are using a function that is implemented only in a specific version, or when you implement a module that supports multiple versions.
Also, `` `pyenv``` is useful when switching between versions of Python to run.
(The explanation of pyenv is different from the contents of this chapter, so I will omit it.)
- https://github.com/pyenv/pyenv
## Adjust the order of imports
If you write the code as above, the following three imports should be written.
import sys
import pytest
import my_math
In fact, there are tools available to adjust this order as well.
You can use isort to sort imports.
(There are various other functions, but this is just a brief introduction.)
- https://github.com/timothycrosley/isort
Well, I was able to write the test safely. It's very easy!
When you add code, you can reduce implementation mistakes by getting into the habit of writing tests.
## next time
I want to write tests for complex methods in Python! is.
Recommended Posts