I want to write in Python! (2) Let's write a test

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.

The story to introduce this time

It's about writing a test. Use pytest.

I want to write a test in Python!

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.

Test get_fibonacci_by_index ()

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.

Various asserts

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

Testing with unintended arguments

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 asInt```, 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.

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

Python3.5.2

$ 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

I want to write in Python! (2) Let's write a test
I want to do Dunnett's test in Python
I want to create a window in Python
I want to write to a file with Python
I want to write in Python! (1) Code format check
I want to embed a variable in a Python string
I want to easily implement a timeout in python
I want to randomly sample a file in Python
I want to work with a robot in python.
I want to write in Python! (3) Utilize the mock
I want to make input () a nice complement in python
I want to print in a comprehension
I want to build a Python environment
I want to write a triple loop and conditional branch in one line in python
I want to make a game with Python
I don't want to take a coding test
I want to merge nested dicts in Python
I want to display the progress in Python!
I want to convert a table converted to PDF in Python back to CSV
I want to color a part of an Excel string in Python
I want to do a monkey patch only partially safely in Python
I want to generate a UUID quickly (memorandum) ~ Python ~
I want to transition with a button in flask
Even in JavaScript, I want to see Python `range ()`!
I tried to implement a pseudo pachislot in Python
[Python] I want to make a nested list a tuple
Write code to Unit Test a Python web app
I want to use the R dataset in python
I want to run a quantum computer with Python
I want to do something in Python when I finish
I want to manipulate strings in Kotlin like Python!
Python program is slow! I want to speed up! In such a case ...
Write a binary search in Python
How to write a Python class
Write a table-driven test in C
Write A * (A-star) algorithm in Python
Write selenium test code in python
Write a pie chart in Python
Write a vim plugin in Python
Write a depth-first search in Python
I want to debug with Python
I tried to implement a one-dimensional cellular automaton in Python
I want to do something like sort uniq in Python
[Python] I want to get a common set between numpy
I want to start a lot of processes from python
I tried "How to get a method decorated in Python"
To write a test in Go, first design the interface
I want to send a message from Python to LINE Bot
I tried to make a stopwatch using tkinter in python
I want to be able to run Python in VS Code
I didn't want to write the AWS key in the program
I want to use a python data source in Re: Dash to get query results
I tried to implement PLSA in Python
I want to use a wildcard that I want to shell with Python remove
I tried to implement permutation in Python
I made a payroll program in Python!
I want to solve APG4b with Python (only 4.01 and 4.04 in Chapter 4)
I tried to implement PLSA in Python 2
I want to use jar from python
Write a short property definition in Python
I want to do a full text search with elasticsearch + python