乐趣区

关于人工智能:数据科学家的Pytest

作者 |Khuyen Tran
编译 |VK
起源 |Towards Datas Science

动机

利用不同的 python 代码来解决 notebook 中的数据是很乏味的,然而为了使代码具备可复制性,你须要将它们放入函数和类中。将代码放入脚本时,代码可能会因某些函数而中断。那么,如何查看你的性能是否如你所冀望的那样工作呢?

例如,咱们应用 TextBlob 创立一个函数来提取文本的情感,TextBlob 是一个用于解决文本数据的 Python 库。咱们心愿确保它像咱们预期的那样工作:如果测试为踊跃,函数返回一个大于 0 的值;如果文本为消极,则返回一个小于 0 的值。

from textblob import TextBlob

def extract_sentiment(text: str):
        ''' 应用 textblob 提取情绪。在范畴 [- 1,1] 内 '''

        text = TextBlob(text)

        return text.sentiment.polarity

要晓得函数是否每次都会返回正确的值,最好的办法是将这些函数利用于不同的示例,看看它是否会产生咱们想要的后果。这就是测试的重要性。

一般来说,你应该在数据迷信我的项目中应用测试,因为它容许你:

  • 确保代码按预期工作
  • 检测边缘状况
  • 有信念用改良的代码替换现有代码,而不用放心毁坏整个管道

有许多 Python 工具可用于测试,但最简略的工具是 Pytest。

Pytest 入门

Pytest 是一个框架,它使得用 Python 编写小测试变得容易。我喜爱 pytest,因为它能够帮忙我用起码的代码编写测试。如果你不相熟测试,那么 pytest 是一个很好的入门工具。

要装置 pytest,请运行

pip install -U pytest

要测试下面所示的函数,咱们能够简略地创立一个函数,该函数以 test_结尾,前面跟着咱们要测试的函数的名称,即 extract_sentiment

#sentiment.py
def extract_sentiment(text: str):
        ''' 应用 textblob 提取情绪。在范畴 [- 1,1] 内 '''

        text = TextBlob(text)

        return text.sentiment.polarity

def test_extract_sentiment():

    text = "I think today will be a great day"

    sentiment = extract_sentiment(text)

    assert sentiment > 0

在测试函数中,咱们将函数 extract_sentiment 利用于示例文本:“I think today will be a great day”。咱们应用 assert sentiment > 0 来确保情绪是踊跃的。

就这样!当初咱们筹备好运行测试了。

如果咱们的脚本名是 sentiment.py,咱们能够运行

pytest sentiment.py

Pytest 将遍历咱们的脚本并运行以 test 结尾的函数。下面的测试输入如下所示

========================================= test session starts ==========================================
platform linux -- Python 3.8.3, pytest-5.4.2, py-1.8.1, pluggy-0.13.1

collected 1 item
process.py .                                                                                     [100%]

========================================== 1 passed in 0.68s ===========================================

很酷!咱们不须要指定要测试哪个函数。只有函数名以 test 结尾,pytest 就会检测并执行该函数!咱们甚至不须要导入 pytest 就能够运行 pytest

如果测试失败,pytest 会产生什么输入?

#sentiment.py

def test_extract_sentiment():

    text = "I think today will be a great day"

    sentiment = extract_sentiment(text)

    assert sentiment < 0
>>> pytest sentiment.py

========================================= test session starts ==========================================
platform linux -- Python 3.8.3, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
collected 1 item

process.py F                                                                                     [100%]
=============================================== FAILURES ===============================================
________________________________________ test_extract_sentiment ________________________________________

def test_extract_sentiment():
    
        text = "I think today will be a great day"
    
        sentiment = extract_sentiment(text)
    
>       assert sentiment < 0
E       assert 0.8 < 0

process.py:17: AssertionError
======================================= short test summary info ========================================
FAILED process.py::test_extract_sentiment - assert 0.8 < 0
========================================== 1 failed in 0.84s ===========================================

从输入能够看出,测试失败是因为函数的情感值为 0.8,并且不小于 0!咱们不仅能够晓得咱们的函数是否如预期的那样工作,而且还能够晓得为什么它不起作用。从这个角度来看,咱们晓得在哪里修复咱们的函数,以实现咱们想要的性能。

同一函数的屡次测试

咱们能够用其余例子来测试咱们的函数。新测试函数的名称是什么?

第二个函数的名称能够是 test_extract_sentiment_2,如果咱们想在带有负面情绪的文本上测试函数,那么它的名称能够是 test_extract_sentiment_negative。任何函数名只有以 test 结尾就能够工作

#sentiment.py

def test_extract_sentiment_positive():

    text = "I think today will be a great day"

    sentiment = extract_sentiment(text)

    assert sentiment > 0

def test_extract_sentiment_negative():

    text = "I do not think this will turn out well"

    sentiment = extract_sentiment(text)

    assert sentiment < 0
>>> pytest sentiment.py

========================================= test session starts ==========================================
platform linux -- Python 3.8.3, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
collected 2 items

process.py .F                                                                                    [100%]
=============================================== FAILURES ===============================================
___________________________________ test_extract_sentiment_negative ____________________________________

def test_extract_sentiment_negative():
    
        text = "I do not think this will turn out well"
    
        sentiment = extract_sentiment(text)
    
>       assert sentiment < 0
E       assert 0.0 < 0

process.py:25: AssertionError
======================================= short test summary info ========================================
FAILED process.py::test_extract_sentiment_negative - assert 0.0 < 0
===================================== 1 failed, 1 passed in 0.80s ======================================

从输入中,咱们晓得一个测试通过,一个测试失败,以及测试失败的起因。咱们心愿“I do not think this will turn out well”这句话是消极的,但后果却是 0。

这有助于咱们了解,函数可能不会 100% 精确;因而,在应用此函数提取文本情感时,咱们应该审慎。

参数化:组合测试

以上 2 个测试性能用于测试同一性能。有没有方法把两个例子合并成一个测试函数?这时参数化就派上用场了

用样本列表参数化

应用 pytest.mark.parametrize(),通过在参数中提供示例列表,咱们能够应用不同的示例执行测试。

# sentiment.py

from textblob import TextBlob
import pytest

def extract_sentiment(text: str):
        ''' 应用 textblob 提取情绪。在范畴 [- 1,1] 内 '''

        text = TextBlob(text)

        return text.sentiment.polarity

testdata = ["I think today will be a great day","I do not think this will turn out well"]

@pytest.mark.parametrize('sample', testdata)
def test_extract_sentiment(sample):

    sentiment = extract_sentiment(sample)

    assert sentiment > 0

在下面的代码中,咱们将变量 sample 调配给一个示例列表,而后将该变量增加到测试函数的参数中。当初每个例子将一次测试一次。

========================== test session starts ===========================
platform linux -- Python 3.8.3, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
collected 2 items

sentiment.py .F                                                    [100%]

================================ FAILURES ================================
_____ test_extract_sentiment[I do not think this will turn out well] _____

sample = 'I do not think this will turn out well'

@pytest.mark.parametrize('sample', testdata)
    def test_extract_sentiment(sample):
    
        sentiment = extract_sentiment(sample)
    
>       assert sentiment > 0
E       assert 0.0 > 0

sentiment.py:19: AssertionError
======================== short test summary info =========================
FAILED sentiment.py::test_extract_sentiment[I do not think this will turn out well]
====================== 1 failed, 1 passed in 0.80s ===================

应用 parametrize(),咱们能够在 once 函数中测试两个不同的示例!

应用示例列表和预期输入进行参数化

如果咱们冀望不同的例子有不同的输入呢?Pytest 还容许咱们向测试函数的参数增加示例和预期输入!

例如,上面的函数查看文本是否蕴含特定的单词。

def text_contain_word(word: str, text: str):
    '''查看文本是否蕴含特定的单词'''
    
    return word in text

如果文本蕴含单词,则返回 True。

如果单词是“duck”,而文本是“There is a duck in this text”,咱们冀望返回 True。

如果单词是‘duck’,而文本是‘There is nothing here’,咱们冀望返回 False。

咱们将应用 parametrize()而不应用元组列表。

# process.py
import pytest
def text_contain_word(word: str, text: str):
    '''查找文本是否蕴含特定的单词'''
    
    return word in text

testdata = [('There is a duck in this text',True),
    ('There is nothing here', False)
    ]

@pytest.mark.parametrize('sample, expected_output', testdata)
def test_text_contain_word(sample, expected_output):

    word = 'duck'

    assert text_contain_word(word, sample) == expected_output

函数的参数构造为 parametrize(‘sample,expected_out’,’testdata),testdata=[(<sample1>,<output1>),(<sample2>,<output2>)

>>> pytest process.py

========================================= test session starts ==========================================
platform linux -- Python 3.8.3, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
plugins: hydra-core-1.0.0, Faker-4.1.1
collected 2 items

process.py ..                                                                                    [100%]

========================================== 2 passed in 0.04s ===========================================

咱们的两个测试都通过了!

一次测试一个函数

当脚本中测试函数的数量越来越大时,你可能心愿一次测试一个函数而不是多个函数。用 pytest 很容易,pytest file.py::function_name

testdata = ["I think today will be a great day","I do not think this will turn out well"]

@pytest.mark.parametrize('sample', testdata)
def test_extract_sentiment(sample):

    sentiment = extract_sentiment(sample)

    assert sentiment > 0


testdata = [('There is a duck in this text',True),
    ('There is nothing here', False)
    ]

@pytest.mark.parametrize('sample, expected_output', testdata)
def test_text_contain_word(sample, expected_output):

    word = 'duck'

    assert text_contain_word(word, sample) == expected_output

例如,如果你只想运行 test_text_contain_word,请运行

pytest process.py::test_text_contain_word

而 pytest 只执行咱们指定的一个测试!

fixture:应用雷同的数据来测试不同的函数

如果咱们想用雷同的数据来测试不同的函数呢?例如,咱们想测试“今 Today I found a duck and I am happy”这句话是否蕴含“duck”这个词,它的情绪是否是踊跃的。这是 fixture 派上用场的时候。

pytest fixture 是一种向不同的测试函数提供数据的办法

@pytest.fixture
def example_data():
    return 'Today I found a duck and I am happy'


def test_extract_sentiment(example_data):

    sentiment = extract_sentiment(example_data)

    assert sentiment > 0

def test_text_contain_word(example_data):

    word = 'duck'

    assert text_contain_word(word, example_data) == True

在下面的示例中,咱们应用 decorator 创立了一个示例数据 @pytest.fixture 在函数 example_data 的上方。这将把 example_data 转换成一个值为“Today I found a duck and I am happy”的变量

当初,咱们能够应用示例数据作为任何测试的参数!

组织你的我的项目

最初但并非最不重要的是,当代码变大时,咱们可能须要将数据迷信函数和测试函数放在两个不同的文件夹中。这将使咱们更容易找到每个函数的地位。

test_<name>.py<name>_test.py命名咱们的测试函数. Pytest 将搜寻名称以“test”结尾或以“test”结尾的文件,并在该文件中执行名称以“test”结尾的函数。这很不便!

有不同的办法来组织你的文件。你能够将咱们的数据迷信文件和测试文件组织在同一个目录中,也能够在两个不同的目录中组织,一个用于源代码,一个用于测试

办法 1:

test_structure_example/
├── process.py
└── test_process.py

办法 2:

test_structure_example/
├── src
│   └── process.py
└── tests
    └── test_process.py

因为数据迷信函数很可能有多个文件,测试函数有多个文件,所以你可能须要将它们放在不同的目录中,如办法 2。

这是 2 个文件的样子

from textblob import TextBlob

def extract_sentiment(text: str):
        ''' 应用 textblob 提取情绪。在范畴 [- 1,1] 内 '''

        text = TextBlob(text)

        return text.sentiment.polarity
import sys
import os.path
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)))
from src.process import extract_sentiment
import pytest


def test_extract_sentiment():

    text = 'Today I found a duck and I am happy'

    sentiment = extract_sentiment(text)

    assert sentiment > 0

简略地说增加 sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 能够从父目录导入函数。

在根目录 (test_structure_example/) 下,运行 pytest tests/test_process.py 或者运行在 test_structure_example/tests 目录下的pytest test_process.py

========================== test session starts ===========================
platform linux -- Python 3.8.3, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
collected 1 item

tests/test_process.py .                                            [100%]

=========================== 1 passed in 0.69s ============================

很酷!

论断

你刚刚理解了 pytest。我心愿本文能很好地概述为什么测试很重要,以及如何将测试与 pytest 联合到数据迷信我的项目中。通过测试,你不仅能够晓得你的函数是否按预期工作,而且还能够自信地应用不同的工具或不同的代码构造来切换现有代码。

本文的源代码能够在这里找到:

https://github.com/khuyentran…

我喜爱写一些根本的数据迷信概念,玩不同的算法和数据迷信工具。

原文链接:https://towardsdatascience.co…

欢送关注磐创 AI 博客站:
http://panchuang.net/

sklearn 机器学习中文官网文档:
http://sklearn123.com/

欢送关注磐创博客资源汇总站:
http://docs.panchuang.net/

退出移动版