खोज…


टिप्पणियों

पायथन के लिए कई यूनिट परीक्षण उपकरण हैं। यह दस्तावेज़ीकरण विषय बेसिक unittest मॉड्यूल का वर्णन करता है। अन्य परीक्षण उपकरण शामिल हैं py.test और nosetestsपरीक्षण के बारे में यह अजगर प्रलेखन गहराई में जाने के बिना इनमें से कई उपकरणों की तुलना करता है।

परीक्षण के अपवाद

उदाहरण के लिए जब गलत इनपुट दिया जाता है तो प्रोग्राम त्रुटियां फेंक देते हैं। इस वजह से, किसी को यह सुनिश्चित करने की आवश्यकता होती है कि वास्तविक गलत इनपुट दिए जाने पर कोई त्रुटि होती है। उसके कारण हमें एक सटीक अपवाद की जांच करने की आवश्यकता है, इस उदाहरण के लिए हम निम्नलिखित अपवाद का उपयोग करेंगे:

class WrongInputException(Exception):
    pass

यह अपवाद तब उठाया जाता है जब गलत इनपुट दिया जाता है, निम्नलिखित संदर्भ में जहां हम हमेशा टेक्स्ट इनपुट के रूप में एक संख्या की उम्मीद करते हैं।

def convert2number(random_input):
    try:
        my_input = int(random_input)
    except ValueError:
        raise WrongInputException("Expected an integer!")
    return my_input

यह जाँचने के लिए कि क्या कोई अपवाद उठाया गया है, हम उस अपवाद की जाँच करने के लिए assertRaises का उपयोग करते हैं। assertRaises उपयोग दो तरीकों से किया जा सकता है:

  1. नियमित फ़ंक्शन कॉल का उपयोग करना। पहला तर्क अपवाद प्रकार लेता है, दूसरा एक कॉल करने योग्य (आमतौर पर एक फ़ंक्शन) और बाकी तर्क इस कॉल करने योग्य के लिए पारित किए जाते हैं।
  2. क्लॉज के with उपयोग करना, फ़ंक्शन को केवल अपवाद प्रकार देता है। इसका यह फायदा है कि अधिक कोड को निष्पादित किया जा सकता है, लेकिन देखभाल के साथ इसका उपयोग किया जाना चाहिए क्योंकि कई फ़ंक्शन समान अपवाद का उपयोग कर सकते हैं जो समस्याग्रस्त हो सकते हैं। एक उदाहरण: self.assertRaises (WrongInputException) के साथ: Convert2number ("संख्या नहीं")

यह पहली बार निम्नलिखित परीक्षण मामले में लागू किया गया है:

import unittest

class ExceptionTestCase(unittest.TestCase):

    def test_wrong_input_string(self):
        self.assertRaises(WrongInputException, convert2number, "not a number")

    def test_correct_input(self):
        try:
            result = convert2number("56")
            self.assertIsInstance(result, int)
        except WrongInputException:
            self.fail()

एक अपवाद की जांच करने की भी आवश्यकता हो सकती है जिसे फेंका नहीं जाना चाहिए था। हालांकि, एक परीक्षण स्वचालित रूप से विफल हो जाएगा जब एक अपवाद फेंक दिया जाता है और इस तरह बिल्कुल भी आवश्यक नहीं हो सकता है। बस विकल्पों को दिखाने के लिए, दूसरी परीक्षण विधि एक मामले को दिखाती है कि कोई अपवाद कैसे नहीं फेंक सकता है। मूल रूप से, यह अपवाद को पकड़ रहा है और फिर असफल विधि का उपयोग करके परीक्षण को fail है।

मॉकिंग कार्य unittest.mock.create_autospec के साथ

फ़ंक्शन को मॉक करने का एक तरीका create_autospec फ़ंक्शन का उपयोग करना है, जो किसी ऑब्जेक्ट को उसके चश्मे के अनुसार मॉक आउट करेगा। फ़ंक्शन के साथ, हम यह सुनिश्चित करने के लिए उपयोग कर सकते हैं कि उन्हें उचित रूप से कहा जाता है।

एक फ़ंक्शन के साथ custom_math.py में multiply :

def multiply(a, b):
    return a * b

और एक समारोह multiples_of में process_math.py :

from custom_math import multiply


def multiples_of(integer, *args, num_multiples=0, **kwargs):
    """
    :rtype: list
    """
    multiples = []
    
    for x in range(1, num_multiples + 1):
        """
        Passing in args and kwargs here will only raise TypeError if values were 
        passed to multiples_of function, otherwise they are ignored. This way we can 
        test that multiples_of is used correctly. This is here for an illustration
        of how create_autospec works. Not recommended for production code.
        """
        multiple = multiply(integer,x, *args, **kwargs)
        multiples.append(multiple)
    
    return multiples

हम परीक्षण कर सकते हैं multiples_of बाहर मजाक से अकेले multiply । नीचे दिए गए उदाहरण में पायथन मानक पुस्तकालय का उपयोग एकतरफा किया गया है, लेकिन इसका उपयोग अन्य परीक्षण ढाँचों के साथ भी किया जा सकता है, जैसे कि पाइस्टेस्ट या नाक:

from unittest.mock import create_autospec
import unittest

# we import the entire module so we can mock out multiply
import custom_math 
custom_math.multiply = create_autospec(custom_math.multiply)
from process_math import multiples_of


class TestCustomMath(unittest.TestCase):
    def test_multiples_of(self):
        multiples = multiples_of(3, num_multiples=1)
        custom_math.multiply.assert_called_with(3, 1)
    
    def test_multiples_of_with_bad_inputs(self):
        with self.assertRaises(TypeError) as e:
            multiples_of(1, "extra arg",  num_multiples=1) # this should raise a TypeError

एक unittest.TestCase के भीतर टेस्ट सेटअप और टियरडाउन

कभी-कभी हम प्रत्येक परीक्षण के तहत चलाने के लिए एक संदर्भ तैयार करना चाहते हैं। setUp विधि को कक्षा में प्रत्येक परीक्षण से पहले चलाया जाता है। हर टेस्ट के अंत में tearDown चलाया जाता है। ये तरीके वैकल्पिक हैं। याद रखें कि सहकारी समितियों में अक्सर TestCases का उपयोग किया जाता है, इसलिए आपको इन तरीकों में हमेशा super कॉल करने के लिए सावधान रहना चाहिए ताकि बेस क्लास के setUp और tearDown तरीकों को भी कहा जाए। के आधार कार्यान्वयन TestCase खाली प्रदान करता है setUp और tearDown तरीकों इतना है कि वे अपवाद को ऊपर उठाने के बिना कहा जा सकता है:

import unittest


class SomeTest(unittest.TestCase):
    def setUp(self):
        super(SomeTest, self).setUp()
        self.mock_data = [1,2,3,4,5]

    def test(self):
        self.assertEqual(len(self.mock_data), 5)

    def tearDown(self):
        super(SomeTest, self).tearDown()
        self.mock_data = []


if __name__ == '__main__':
    unittest.main()

ध्यान दें कि python2.7 + में, addCleanup पद्धति भी है जो परीक्षण चलाने के बाद कॉल करने के लिए रजिस्टर करने का कार्य करती है। के विपरीत tearDown जो केवल तभी कहा जाता हो जाता है setUp सफल होता है, के माध्यम से पंजीकृत कार्यों addCleanup भी में एक बिना क्रिया का अपवाद की स्थिति में बुलाया जाएगा setUp । एक ठोस उदाहरण के रूप में, इस पद्धति को अक्सर विभिन्न मॉक को हटाते हुए देखा जा सकता है जो परीक्षण के दौरान पंजीकृत थे:

import unittest
import some_module


class SomeOtherTest(unittest.TestCase):
    def setUp(self):
        super(SomeOtherTest, self).setUp()
        
        # Replace `some_module.method` with a `mock.Mock`
        my_patch = mock.patch.object(some_module, 'method')
        my_patch.start()

        # When the test finishes running, put the original method back.
        self.addCleanup(my_patch.stop)

इस तरह से क्लीनअप को पंजीकृत करने का एक और लाभ यह है कि यह प्रोग्रामर को सेटअप कोड के बगल में सफाई कोड डालने की अनुमति देता है और यह आपको इस घटना में बचाता है कि एक उपवर्ग tearDown में super कॉल करना भूल जाता है।

अपवाद पर जोर देना

आप परीक्षण कर सकते हैं कि एक फ़ंक्शन दो अलग-अलग तरीकों के माध्यम से बिल्ट-इन यूनिस्ट के साथ एक अपवाद फेंकता है।

एक संदर्भ प्रबंधक का उपयोग करना

def division_function(dividend, divisor):
    return dividend / divisor


class MyTestCase(unittest.TestCase):
    def test_using_context_manager(self):
        with self.assertRaises(ZeroDivisionError):
            x = division_function(1, 0)

यह संदर्भ प्रबंधक के अंदर कोड चलाएगा और, यदि यह सफल होता है, तो यह परीक्षण में विफल हो जाएगा क्योंकि अपवाद नहीं उठाया गया था। यदि कोड सही प्रकार का अपवाद उठाता है, तो परीक्षण जारी रहेगा।

यदि आप अतिरिक्त अपवाद की सामग्री प्राप्त करना चाहते हैं, तो आप इसके विरुद्ध अतिरिक्त दावे भी कर सकते हैं।

class MyTestCase(unittest.TestCase):
    def test_using_context_manager(self):
        with self.assertRaises(ZeroDivisionError) as ex:
            x = division_function(1, 0)

        self.assertEqual(ex.message, 'integer division or modulo by zero')

कॉल करने योग्य फ़ंक्शन प्रदान करके

def division_function(dividend, divisor):
    """
    Dividing two numbers.

    :type dividend: int
    :type divisor: int

    :raises: ZeroDivisionError if divisor is zero (0).
    :rtype: int
    """
    return dividend / divisor


class MyTestCase(unittest.TestCase):
    def test_passing_function(self):
        self.assertRaises(ZeroDivisionError, division_function, 1, 0)

पहले पैरामीटर के लिए जाँच करने के लिए अपवाद होना चाहिए, और कॉल करने योग्य फ़ंक्शन को दूसरे पैरामीटर के रूप में पारित किया जाना चाहिए। निर्दिष्ट किए गए किसी भी अन्य पैरामीटर को सीधे उस फ़ंक्शन को पास किया जाएगा, जिसे आप अपवाद को ट्रिगर करने वाले मापदंडों को निर्दिष्ट करने की अनुमति देते हैं।

Unittests के भीतर जोर चुनना

जबकि पायथन का एक assert कथन है , पायथन इकाई परीक्षण ढांचे में परीक्षणों के लिए बेहतर दावे हैं: वे विफलताओं पर अधिक जानकारीपूर्ण हैं, और निष्पादन के डिबग मोड पर निर्भर नहीं हैं।

शायद सबसे सरल दावा है assertTrue , जो इस तरह इस्तेमाल किया जा सकता:

import unittest

class SimplisticTest(unittest.TestCase):
    def test_basic(self):
        self.assertTrue(1 + 1 == 2)

यह ठीक चलेगा, लेकिन रेखा को ऊपर से बदल देगा

        self.assertTrue(1 + 1 == 3)

असफल हो जायेगी।

assertTrue अभिकथन की संभावना सबसे सामान्य अभिकथन है, क्योंकि कुछ भी परीक्षण किया जा सकता है कुछ बूलियन स्थिति के रूप में, लेकिन अक्सर बेहतर विकल्प होते हैं। जब समानता के लिए परीक्षण, ऊपर के रूप में, यह लिखना बेहतर है

        self.assertEqual(1 + 1, 3)

जब पूर्व विफल रहता है, तो संदेश है

======================================================================

FAIL: test (__main__.TruthTest)

----------------------------------------------------------------------

Traceback (most recent call last):

  File "stuff.py", line 6, in test

    self.assertTrue(1 + 1 == 3)

AssertionError: False is not true

लेकिन जब उत्तरार्द्ध विफल हो जाता है, तो संदेश है

======================================================================

FAIL: test (__main__.TruthTest)

----------------------------------------------------------------------

Traceback (most recent call last):

  File "stuff.py", line 6, in test

    self.assertEqual(1 + 1, 3)
AssertionError: 2 != 3

जो अधिक जानकारीपूर्ण है (यह वास्तव में बाएं हाथ के परिणाम का मूल्यांकन करता है)।

आप मानक प्रलेखन में मुखरता की सूची पा सकते हैं। सामान्य तौर पर, यह एक अच्छा विचार है कि यह विशेष रूप से स्थिति को फिट करने वाले दावे को चुनना है। इस प्रकार, जैसा कि ऊपर दिखाया गया है, कि 1 + 1 == 2 को assertEqual करने के लिए assertEqual तुलना में assertTrue का उपयोग करना बेहतर है। इसी तरह, उसने दृढ़तापूर्वक कहा कि के लिए a is None , यह बेहतर उपयोग करने के लिए है assertIsNone से assertEqual

यह भी ध्यान दें कि अभिकथन के नकारात्मक रूप हैं। इस प्रकार assertEqual पास अपने नकारात्मक समकक्ष हैं assertNotEqual , और assertIsNone के अपने नकारात्मक समकक्ष हैं assertIsNotNone । एक बार फिर, जब उचित हो तो नकारात्मक समकक्षों का उपयोग करके, त्रुटि संदेशों को स्पष्ट किया जाएगा।

यूनिट टेस्ट पाइस्टेस्ट के साथ

pytest स्थापित करना:

pip install pytest

परीक्षण तैयार हो रहे हैं:

mkdir tests
touch tests/test_docker.py

docker_something/helpers.py में परीक्षण करने के लिए कार्य:

from subprocess import Popen, PIPE 
# this Popen is monkeypatched with the fixture `all_popens`

def copy_file_to_docker(src, dest):
    try:
        result = Popen(['docker','cp', src, 'something_cont:{}'.format(dest)], stdout=PIPE, stderr=PIPE)
        err = result.stderr.read()
        if err:
            raise Exception(err)
    except Exception as e:
        print(e)
    return result

def docker_exec_something(something_file_string):
    fl = Popen(["docker", "exec", "-i", "something_cont", "something"], stdin=PIPE, stdout=PIPE, stderr=PIPE)
    fl.stdin.write(something_file_string)
    fl.stdin.close()
    err = fl.stderr.read()
    fl.stderr.close()
    if err:
        print(err)
        exit()
    result = fl.stdout.read()
    print(result)

परीक्षण का आयात test_docker.py :

import os
from tempfile import NamedTemporaryFile
import pytest
from subprocess import Popen, PIPE

from docker_something import helpers
copy_file_to_docker = helpers.copy_file_to_docker
docker_exec_something = helpers.docker_exec_something

test_docker.py में ऑब्जेक्ट जैसी फ़ाइल को test_docker.py :

class MockBytes():
    '''Used to collect bytes
    '''
    all_read = []
    all_write = []
    all_close = []

    def read(self, *args, **kwargs):
        # print('read', args, kwargs, dir(self))
        self.all_read.append((self, args, kwargs))

    def write(self, *args, **kwargs):
        # print('wrote', args, kwargs)
        self.all_write.append((self, args, kwargs))

    def close(self, *args, **kwargs):
        # print('closed', self, args, kwargs)
        self.all_close.append((self, args, kwargs))

    def get_all_mock_bytes(self):
        return self.all_read, self.all_write, self.all_close

Test_docker.py में test_docker.py साथ बंदर पैचिंग:

@pytest.fixture
def all_popens(monkeypatch):
    '''This fixture overrides / mocks the builtin Popen
        and replaces stdin, stdout, stderr with a MockBytes object

        note: monkeypatch is magically imported
    '''
    all_popens = []
    
    class MockPopen(object):
        def __init__(self, args, stdout=None, stdin=None, stderr=None):
            all_popens.append(self)
            self.args = args
            self.byte_collection = MockBytes()
            self.stdin = self.byte_collection
            self.stdout = self.byte_collection
            self.stderr = self.byte_collection
            pass
    monkeypatch.setattr(helpers, 'Popen', MockPopen)

    return all_popens

उदाहरण परीक्षण, पहले से शुरू होना चाहिए test_docker.py फ़ाइल में उपसर्ग test_

def test_docker_install():
    p = Popen(['which', 'docker'], stdout=PIPE, stderr=PIPE)
    result = p.stdout.read()
    assert 'bin/docker' in result

def test_copy_file_to_docker(all_popens):    
    result = copy_file_to_docker('asdf', 'asdf')
    collected_popen = all_popens.pop()
    mock_read, mock_write, mock_close = collected_popen.byte_collection.get_all_mock_bytes()
    assert mock_read
    assert result.args == ['docker', 'cp', 'asdf', 'something_cont:asdf']


def test_docker_exec_something(all_popens):
    
    docker_exec_something(something_file_string)

    collected_popen = all_popens.pop()
    mock_read, mock_write, mock_close = collected_popen.byte_collection.get_all_mock_bytes()
    assert len(mock_read) == 3
    something_template_stdin = mock_write[0][1][0]
    these = [os.environ['USER'], os.environ['password_prod'], 'table_name_here', 'test_vdm', 'col_a', 'col_b', '/tmp/test.tsv']
    assert all([x in something_template_stdin for x in these])

एक समय में एक परीक्षण चल रहा है:

py.test -k test_docker_install tests
py.test -k test_copy_file_to_docker tests
py.test -k test_docker_exec_something tests

tests फ़ोल्डर में सभी परीक्षण चल रहे हैं:

py.test -k test_ tests


Modified text is an extract of the original Stack Overflow Documentation
के तहत लाइसेंस प्राप्त है CC BY-SA 3.0
से संबद्ध नहीं है Stack Overflow