2008-12-07 12 views
14

मैं उन कार्यों के परिवार के लिए यूनिट परीक्षण लागू कर रहा हूं जो सभी कई आविष्कार साझा करते हैं। उदाहरण के लिए, दो मैट्रिस के साथ फ़ंक्शन को कॉल करना ज्ञात आकार का मैट्रिक्स उत्पन्न करता है।पायथन एकजुट ढांचे में मैं कई समान यूनिट परीक्षणों को संक्षेप में कैसे कार्यान्वित कर सकता हूं?

मैं प्रत्येक संपत्ति के लिए एक व्यक्तिगत परीक्षण केस लिखने के बिना, इस संपत्ति के कार्यों के पूरे परिवार का परीक्षण करने के लिए इकाई परीक्षण लिखना चाहता हूं (विशेष रूप से बाद में अधिक कार्यों को बाद में जोड़ा जा सकता है)।

एक तरह से यह करने के लिए इन कार्यों की एक सूची से अधिक पुनरावृति करने के लिए होगा:

import unittest 
import numpy 

from somewhere import the_functions 
from somewhere.else import TheClass 

class Test_the_functions(unittest.TestCase): 
    def setUp(self): 
    self.matrix1 = numpy.ones((5,10)) 
    self.matrix2 = numpy.identity(5) 

    def testOutputShape(unittest.TestCase): 
    """Output of functions be of a certain shape""" 
    for function in all_functions: 
     output = function(self.matrix1, self.matrix2) 
     fail_message = "%s produces output of the wrong shape" % str(function) 
     self.assertEqual(self.matrix1.shape, output.shape, fail_message) 

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

मैं Dive Into Python से इस के लिए विचार आया। वहां, यह परीक्षण किए जा रहे कार्यों की एक सूची नहीं है बल्कि ज्ञात इनपुट-आउटपुट जोड़े की एक सूची है। इस दृष्टिकोण के साथ समस्या यह है कि यदि सूची का कोई भी तत्व परीक्षण में विफल रहता है, तो बाद के तत्वों का परीक्षण नहीं होता है।

मैंने unittest subclassing को देखा। टेस्टकेस और किसी भी तरह से एक तर्क के रूप में परीक्षण करने के लिए विशिष्ट कार्य प्रदान करते हैं, लेकिन जहां तक ​​मैं कह सकता हूं कि हमें unittest.main() का उपयोग करने से रोकता है क्योंकि तर्क पारित करने का कोई तरीका नहीं होगा टेस्टकेस के लिए।

मैंने लैमडेबा के साथ सेटैटर का उपयोग करके टेस्टकेस में "टेस्टसोमिंग" फ़ंक्शन को गतिशील रूप से संलग्न करने पर भी देखा, लेकिन टेस्टकेस उन्हें पहचान नहीं पाया।

मैं इसे कैसे लिख सकता हूं ताकि परीक्षणों की सूची का विस्तार करने के लिए यह छोटा हो, जबकि अभी भी प्रत्येक परीक्षण चल रहा है?

+0

संबंधित सवाल: http://stackoverflow.com/प्रश्न/328 99/कैसे-से-जेनरेट-डायनामिक-यूनिट-टेस्ट-इन-पायथन – jfs

उत्तर

4

आप परीक्षणों को गतिशील रूप से सम्मिलित करने के लिए मेटाक्लास का उपयोग कर सकते हैं। यह मेरे लिए ठीक काम करता है:

import unittest 

class UnderTest(object): 

    def f1(self, i): 
     return i + 1 

    def f2(self, i): 
     return i + 2 

class TestMeta(type): 

    def __new__(cls, name, bases, attrs): 
     funcs = [t for t in dir(UnderTest) if t[0] == 'f'] 

     def doTest(t): 
      def f(slf): 
       ut=UnderTest() 
       getattr(ut, t)(3) 
      return f 

     for f in funcs: 
      attrs['test_gen_' + f] = doTest(f) 
     return type.__new__(cls, name, bases, attrs) 

class T(unittest.TestCase): 

    __metaclass__ = TestMeta 

    def testOne(self): 
     self.assertTrue(True) 

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

धन्यवाद, यह काम करता है। केवल एक मामूली क्विर्क, नाक मेटाक्लास द्वारा जोड़े गए टेस्टकेस को देखने में असमर्थ है। कोई सुझाव? – saffsd

+0

मैं नाक से परिचित नहीं हूँ। यह कक्षा में विधियों को जोड़ता है, इसलिए मुझे यकीन नहीं है कि उन्हें याद करने के लिए नाक क्या कर सकता है। मैं यह जानना दिलचस्प होगा कि इसका जादू क्या है। – Dustin

+0

__new__ में 'f' के उपयोग के साथ इसे थोड़ा ठीक कर रहा है, यह थोड़ा अस्पष्ट है –

1

मेटाक्लास एक विकल्प है। एक और विकल्प TestSuite का उपयोग करना है:

import unittest 
import numpy 
import funcs 

# get references to functions 
# only the functions and if their names start with "matrixOp" 
functions_to_test = [v for k,v in funcs.__dict__ if v.func_name.startswith('matrixOp')] 

# suplly an optional setup function 
def setUp(self): 
    self.matrix1 = numpy.ones((5,10)) 
    self.matrix2 = numpy.identity(5) 

# create tests from functions directly and store those TestCases in a TestSuite 
test_suite = unittest.TestSuite([unittest.FunctionTestCase(f, setUp=setUp) for f in functions_to_test]) 


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

परीक्षण नहीं किया गया है। लेकिन यह ठीक काम करना चाहिए।

+1

unittest.main() स्वचालित रूप से इसे नहीं उठाता है, और न ही नाक करता है। साथ ही, FunctionTestCase बिना तर्क के सेटअप को कॉल करता है, और functions_to_test को परीक्षण में दावा करने वाले किसी चीज़ में लपेटने की आवश्यकता होती है। – saffsd

11

यहां "संबंधित परीक्षणों के परिवार" के लिए मेरा पसंदीदा दृष्टिकोण है। मुझे टेस्टकेस के स्पष्ट उप-वर्ग पसंद हैं जो सामान्य विशेषताओं को व्यक्त करते हैं।

class MyTestF1(unittest.TestCase): 
    theFunction= staticmethod(f1) 
    def setUp(self): 
     self.matrix1 = numpy.ones((5,10)) 
     self.matrix2 = numpy.identity(5) 
    def testOutputShape(self): 
     """Output of functions be of a certain shape""" 
     output = self.theFunction(self.matrix1, self.matrix2) 
     fail_message = "%s produces output of the wrong shape" % (self.theFunction.__name__,) 
     self.assertEqual(self.matrix1.shape, output.shape, fail_message) 

class TestF2(MyTestF1): 
    """Includes ALL of TestF1 tests, plus a new test.""" 
    theFunction= staticmethod(f2) 
    def testUniqueFeature(self): 
     # blah blah blah 
     pass 

class TestF3(MyTestF1): 
    """Includes ALL of TestF1 tests with no additional code.""" 
    theFunction= staticmethod(f3) 

, एक समारोह में जोड़ें MyTestF1 का एक उपवर्ग जोड़ें। MyTestF1 के प्रत्येक उप-वर्ग में MyTestF1 के सभी परीक्षण शामिल हैं जिनमें किसी भी प्रकार का कोई डुप्लिकेट कोड नहीं है।

अद्वितीय सुविधाओं को एक स्पष्ट तरीके से संभाला जाता है। सबक्लास में नए तरीके जोड़े गए हैं।

यह unittest.main()

+0

मुझे यह ऑब्जेक्ट उन्मुख समाधान पसंद है। "स्पष्ट से स्पष्ट स्पष्ट है" – muhuk

+0

मुझे यह पसंद नहीं है क्योंकि यह डुप्लिकेट कोड का एक संपूर्ण ढेर पेश करता है। चूंकि प्रत्येक कार्य का परीक्षण उसी आविष्कार का परीक्षण करने के लिए किया जाता है, इसलिए मुझे इसे एक टेस्टकेस में घुमाने के बिना बिल्कुल व्यक्त करने का तरीका चाहिए। शायद मुझे चाहिए? फिर भी सुझाव के लिए धन्यवाद। – saffsd

+0

एक सुपरक्लास में सामान्य कोड को रिफैक्टर करें। यही सुपरक्लास के लिए है। आपका "सामान्य परीक्षण" ठीक है क्यों हमारे पास सुपरक्लास और उप-वर्ग हैं। –

-1

इस दृष्टिकोण के साथ समस्या यह है कि यदि सूची के किसी भी तत्व परीक्षण विफल रहता है, बाद में तत्वों परीक्षण किया नहीं मिलता है के साथ पूरी तरह से संगत है।

आप का मानना ​​है कि, अगर एक परीक्षण में विफल रहता है के बिंदु से यह को देखें, तो यह है कि महत्वपूर्ण है और अपने पूरे पैकेज, अवैध है, क्योंकि तब यह कोई बात नहीं है कि अन्य तत्वों का परीक्षण नहीं किया जाएगा ' अरे, आपको ठीक करने में त्रुटि है।

एक बार यह परीक्षा उत्तीर्ण हो जाने के बाद, अन्य परीक्षण तब चलाए जाएंगे।

माना जाता है कि अन्य परीक्षणों में असफल होने के ज्ञान से जानकारी प्राप्त की जा सकती है, और इससे डिबगिंग में मदद मिल सकती है, लेकिन इसके अलावा, मान लें कि कोई परीक्षण विफलता पूरी एप्लिकेशन विफलता है।

+4

मैं इसे पहचानता हूं, लेकिन एक अन्य काउंटर-तर्क है कि यदि आप बैच में परीक्षण चला रहे हैं, तो रातोंरात कहें, आप जानना चाहते हैं कि सभी असफलताएं केवल पहले नहीं हैं। – saffsd

5

आप पहले से ही नाक उपयोग कर रहे हैं (और अपनी टिप्पणी के कुछ सुझाव है कि आप कर रहे हैं), क्यों तुम सिर्फ Test Generators, जो पैरामीट्रिक परीक्षण लागू करने के लिए सबसे सरल रास्ता नहीं मैं का सामना करना पड़ा हैं का उपयोग नहीं करते:

उदाहरण के लिए:

from binary_search import search1 as search 

def test_binary_search(): 
    data = (
     (-1, 3, []), 
     (-1, 3, [1]), 
     (0, 1, [1]), 
     (0, 1, [1, 3, 5]), 
     (1, 3, [1, 3, 5]), 
     (2, 5, [1, 3, 5]), 
     (-1, 0, [1, 3, 5]), 
     (-1, 2, [1, 3, 5]), 
     (-1, 4, [1, 3, 5]), 
     (-1, 6, [1, 3, 5]), 
     (0, 1, [1, 3, 5, 7]), 
     (1, 3, [1, 3, 5, 7]), 
     (2, 5, [1, 3, 5, 7]), 
     (3, 7, [1, 3, 5, 7]), 
     (-1, 0, [1, 3, 5, 7]), 
     (-1, 2, [1, 3, 5, 7]), 
     (-1, 4, [1, 3, 5, 7]), 
     (-1, 6, [1, 3, 5, 7]), 
     (-1, 8, [1, 3, 5, 7]), 
    ) 

    for result, n, ns in data: 
     yield check_binary_search, result, n, ns 

def check_binary_search(expected, n, ns): 
    actual = search(n, ns) 
    assert expected == actual 

का उत्पादन:

$ nosetests -d 
................... 
---------------------------------------------------------------------- 
Ran 19 tests in 0.009s 

OK 
1

ऊपर metaclass कोड नाक के साथ परेशानी होती है क्योंकि इसके selector.py में नाक के wantMethod लो है किसी दिए गए परीक्षण विधि के __name__ पर ठीक है, विशेषता dict कुंजी नहीं है।

नाक के साथ मेटाक्लास परिभाषित परीक्षण विधि का उपयोग करने के लिए, विधि का नाम और शब्दकोश कुंजी समान होना चाहिए, और नाक (यानी 'test_' के साथ) का पता लगाने के लिए उपसर्ग किया जाना चाहिए।

# test class that uses a metaclass 
class TCType(type): 
    def __new__(cls, name, bases, dct): 
     def generate_test_method(): 
      def test_method(self): 
       pass 
      return test_method 

     dct['test_method'] = generate_test_method() 
     return type.__new__(cls, name, bases, dct) 

class TestMetaclassed(object): 
    __metaclass__ = TCType 

    def test_one(self): 
     pass 
    def test_two(self): 
     pass 
5

आपको यहां मेटा क्लासेस का उपयोग करने की आवश्यकता नहीं है। एक साधारण पाश बस ठीक फिट बैठता है। नीचे दिए गए उदाहरण पर एक नज़र डालें: TestCase1

import unittest 
class TestCase1(unittest.TestCase): 
    def check_something(self, param1): 
     self.assertTrue(param1) 

def _add_test(name, param1): 
    def test_method(self): 
     self.check_something(param1) 
    setattr(TestCase1, 'test_'+name, test_method) 
    test_method.__name__ = 'test_'+name 

for i in range(0, 3): 
    _add_test(str(i), False) 

एक बार के लिए मार डाला है कि दोनों नाक और unittest द्वारा समर्थित हैं 3 परीक्षण तरीकों है।

+0

हाँ मुझे "उपकरण" के प्रयोजनों के लिए मेटाक्लास मिलते हैं एकल उपयोग कक्षाएं कभी भी अच्छी तरह से उड़ती नहीं हैं, यह एक बेहतर तरीका है। –

0

मैं ऊपर metaclass उदाहरण पढ़ा है, और मुझे यह पसंद आया, लेकिन यह दो बातें याद आ रही थी:

  1. कैसे एक डेटा संरचना के साथ यह ड्राइव करने के लिए?
  2. यह सुनिश्चित करने के लिए कि परीक्षण फ़ंक्शन सही तरीके से लिखा गया है?

मैंने यह और अधिक संपूर्ण उदाहरण लिखा है, जो डेटा संचालित है, और जिसमें परीक्षण कार्य स्वयं इकाई परीक्षण है।

import unittest 

TEST_DATA = (
    (0, 1), 
    (1, 2), 
    (2, 3), 
    (3, 5), # This intentionally written to fail 
) 


class Foo(object): 

    def f(self, n): 
    return n + 1 


class FooTestBase(object): 
    """Base class, defines a function which performs assertions. 

    It defines a value-driven check, which is written as a typical function, and 
    can be tested. 
    """ 

    def setUp(self): 
    self.obj = Foo() 

    def value_driven_test(self, number, expected): 
    self.assertEquals(expected, self.obj.f(number)) 


class FooTestBaseTest(unittest.TestCase): 
    """FooTestBase has a potentially complicated, data-driven function. 

    It needs to be tested. 
    """ 
    class FooTestExample(FooTestBase, unittest.TestCase): 
    def runTest(self): 
     return self.value_driven_test 

    def test_value_driven_test_pass(self): 
    test_base = self.FooTestExample() 
    test_base.setUp() 
    test_base.value_driven_test(1, 2) 

    def test_value_driven_test_fail(self): 
    test_base = self.FooTestExample() 
    test_base.setUp() 
    self.assertRaises(
     AssertionError, 
     test_base.value_driven_test, 1, 3) 


class DynamicTestMethodGenerator(type): 
    """Class responsible for generating dynamic test functions. 

    It only wraps parameters for specific calls of value_driven_test. It could 
    be called a form of currying. 
    """ 

    def __new__(cls, name, bases, dct): 
    def generate_test_method(number, expected): 
     def test_method(self): 
     self.value_driven_test(number, expected) 
     return test_method 
    for number, expected in TEST_DATA: 
     method_name = "testNumbers_%s_and_%s" % (number, expected) 
     dct[method_name] = generate_test_method(number, expected) 
    return type.__new__(cls, name, bases, dct) 


class FooUnitTest(FooTestBase, unittest.TestCase): 
    """Combines generated and hand-written functions.""" 

    __metaclass__ = DynamicTestMethodGenerator 


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

जब ऊपर के उदाहरण चल रहा है, अगर वहाँ कोड (या गलत परीक्षण डाटा) में एक बग है, त्रुटि संदेश समारोह नाम शामिल होंगे, जो डिबगिंग में मदद करनी चाहिए।

.....F 
====================================================================== 
FAIL: testNumbers_3_and_5 (__main__.FooUnitTest) 
---------------------------------------------------------------------- 
Traceback (most recent call last): 
    File "dyn_unittest.py", line 65, in test_method 
    self.value_driven_test(number, expected) 
    File "dyn_unittest.py", line 30, in value_driven_test 
    self.assertEquals(expected, self.obj.f(number)) 
AssertionError: 5 != 4 

---------------------------------------------------------------------- 
Ran 6 tests in 0.002s 

FAILED (failures=1) 
3

मुझे लगता है कि यह प्रश्न पुराना है। मैं फिर वापस बारे में निश्चित नहीं हूँ, लेकिन आज हो सकता है आप कुछ "डेटा के आधार पर परीक्षण" संकुल इस्तेमाल कर सकते हैं:

संबंधित मुद्दे

 संबंधित मुद्दे