अनिश्चित उत्पादन के साथ इकाई परीक्षण के तरीके


37

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

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

लेकिन उन मामलों के बारे में क्या है जहां SUT अनिश्चित उत्पादन उत्पन्न करने वाला है?

यदि मैं उसी मान पर न्यूनतम और अधिकतम लंबाई तय करता हूं, तो मैं आसानी से जांच सकता हूं कि उत्पन्न पासवर्ड अपेक्षित लंबाई का है। लेकिन अगर मैं स्वीकार्य लंबाई की सीमा निर्दिष्ट करता हूं (15 - 20 वर्ण कहते हैं), तो अब आपको समस्या है कि आप परीक्षण को सौ बार चला सकते हैं और 100 पास प्राप्त कर सकते हैं, लेकिन 101 वें रन पर आपको 9 वर्ण स्ट्रिंग वापस मिल सकती है।

पासवर्ड क्लास के मामले में, जो इसके मूल में काफी सरल है, यह एक बड़ी समस्या साबित नहीं होनी चाहिए। लेकिन यह मुझे सामान्य मामले के बारे में सोच रहा था। वह कौन सी रणनीति है जो आमतौर पर SUT से निपटने के लिए सबसे अच्छी एक के रूप में स्वीकार की जाती है जो डिजाइन द्वारा अनिश्चित उत्पादन उत्पन्न कर रहे हैं?


9
करीबी वोट क्यों? मुझे लगता है कि यह पूरी तरह से वैध सवाल है।
मार्क बेकर

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

1
@MarkBaker क्योंकि अधिकांश unittesting सवाल programmers.se पर हैं। यह प्रवास के लिए वोट है, प्रश्न को बंद करने के लिए नहीं।
इक्के

जवाबों:


20

"गैर-निर्धारक" आउटपुट में इकाई परीक्षण के उद्देश्यों के लिए निर्धारक बनने का एक तरीका होना चाहिए। यादृच्छिकता को संभालने का एक तरीका यादृच्छिक इंजन के प्रतिस्थापन की अनुमति देना है। यहाँ एक उदाहरण है (PHP 5.3+):

function DoSomethingRandom($getRandomIntLessThan)
{
    if ($getRandomIntLessThan(2) == 0)
    {
        // Do action 1
    }
    else
    {
        // Do action 2
    }
}

// For testing purposes, always return 1
$alwaysReturnsOne = function($n) { return 1; };
DoSomethingRandom($alwaysReturnsOne);

आप फ़ंक्शन का एक विशेष परीक्षण संस्करण बना सकते हैं, जो संख्याओं के किसी भी क्रम को लौटाता है जिसे आप सुनिश्चित करना चाहते हैं कि परीक्षण पूरी तरह से दोहराए जाने योग्य है। वास्तविक कार्यक्रम में, आपके पास एक डिफ़ॉल्ट कार्यान्वयन हो सकता है जो ओवरराइड होने पर कमबैक हो सकता है।


1
दिए गए सभी उत्तरों में अच्छे सुझाव थे जो मैंने उपयोग किए थे, लेकिन यह एक ऐसा है जो मुझे लगता है कि मुख्य मुद्दा नाखून है इसलिए इसे स्वीकार किया जाता है।
गॉर्डन १०

1
सिर पर बहुत सुंदर नाखून। गैर-नियतात्मक होने के बावजूद, अभी भी सीमाएं हैं।
सर्फेस

21

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

आप यह भी परीक्षण कर सकते हैं कि रूटीन हर बार आपके पासवर्ड जनरेटर को एक ही मूल्य के साथ निर्धारित करके हर बार एक निर्धारित परिणाम देता है।


पीडब्लू वर्ग एक निरंतरता रखता है जो अनिवार्य रूप से वर्णों का पूल है जिससे पासवर्ड उत्पन्न किया जाना चाहिए। इसे उपवर्गित करके और एक ही चरित्र के साथ निरंतर को ओवरराइड करके मैं परीक्षण के प्रयोजनों के लिए गैर-निर्धारकता के एक क्षेत्र को समाप्त करने में कामयाब रहा। तो धन्यवाद।
गॉर्डन

14

"अनुबंध" के खिलाफ परीक्षण करें। जब तरीकों को "15 से 20 वर्ण लंबाई के az से पासवर्ड उत्पन्न करता है" के रूप में परिभाषित किया जाता है, तो इसे इस तरह से परखें

$this->assertTrue ((bool) preg_match('^[a-z]{15,20}$', $password));

अतिरिक्त आप पीढ़ी को निकाल सकते हैं, इसलिए सब कुछ, जो उस पर निर्भर करता है, एक और "स्थिर" जनरेटर वर्ग का उपयोग करके परीक्षण किया जा सकता है

class RandomGenerator implements PasswordGenerator {
  public function create() {
    // Create $rndPwd
    return $rndPwd;
  }
}

class StaticGenerator implements PasswordGenerator {
  private $pwd;
  public function __construct ($pwd) { $this->pwd = $pwd; }
  public function create      ()     { return $this->pwd; }
}

आपके द्वारा दिया गया regex उपयोगी साबित हुआ इसलिए मैंने अपने परीक्षण में एक ट्वीक किए गए संस्करण को शामिल किया। धन्यवाद।
गॉर्डन

6

आपके पास एक है Password generatorऔर आपको एक यादृच्छिक स्रोत की आवश्यकता है।

जैसा कि आपने प्रश्न में कहा है कि randomयह गैर-नियतात्मक उत्पादन है क्योंकि यह वैश्विक स्थिति है । मतलब यह मान उत्पन्न करने के लिए सिस्टम के बाहर कुछ एक्सेस करता है।

आप अपने सभी वर्गों के लिए कभी भी ऐसा कुछ नहीं निकाल सकते, लेकिन आप यादृच्छिक मूल्यों के निर्माण के लिए पासवर्ड पीढ़ी को अलग कर सकते हैं।

<?php
class PasswordGenerator {

    public function __construct(RandomSource $randomSource) {
        $this->randomSource = $randomSource
    }

    public function generatePassword() {
        $password = '';
        for($length = rand(10, 16); $length; $length--) {
            $password .= $this-toChar($this->randomSource->rand(1,26));
        }
    }

}

यदि आप इस तरह की संरचना बनाते हैं तो आप RandomSourceअपने परीक्षणों के लिए नकली हो सकते हैं ।

आप 100% परीक्षण नहीं कर पाएंगे, RandomSourceलेकिन इस प्रश्न में मूल्यों के परीक्षण के लिए आपको जो सुझाव मिले हैं, उन पर इसे लागू किया जा सकता है (जैसे परीक्षण जो rand->(1,26);हमेशा 1 से 26 की संख्या देता है।


यह बहुत अच्छा जवाब है।
निक हॉजेस

3

एक कण भौतिकी मोंटे कार्लो के मामले में, मैंने "यूनिट परीक्षण" {*} लिखा है जो एक पूर्व निर्धारित यादृच्छिक बीज के साथ गैर-नियतात्मक दिनचर्या का आह्वान करता है , और फिर कई बार सांख्यिकीय संख्या में दौड़ता है और बाधाओं (ऊर्जा स्तरों) के उल्लंघन की जांच करता है इनपुट ऊर्जा के ऊपर दुर्गम होना चाहिए, सभी पास कुछ स्तर का चयन करना चाहिए, आदि) और पहले दर्ज किए गए परिणामों के खिलाफ प्रतिगमन।


* {} इस तरह के परीक्षण इकाई परीक्षण के लिए "परीक्षण तेज बनाएं" सिद्धांत का उल्लंघन करते हैं, इसलिए आप उन्हें किसी अन्य तरीके से बेहतर रूप से चिह्नित कर सकते हैं: उदाहरण के लिए स्वीकृति परीक्षण या प्रतिगमन परीक्षण। फिर भी, मैंने अपनी इकाई परीक्षण रूपरेखा का उपयोग किया।


3

मुझे दो कारणों से स्वीकृत जवाब से असहमत होना होगा :

  1. Overfitting
  2. अव्यवहारिकता

(ध्यान दें कि यह हो सकता है कई परिस्थितियों में एक अच्छा उत्तर सकता है, लेकिन सभी में नहीं, और शायद अधिकांश में नहीं।)

तो मुझे इससे क्या मतलब है? खैर, ओवरफिटिंग से मेरा मतलब है कि सांख्यिकीय परीक्षण की एक विशिष्ट समस्या: ओवरफिटिंग तब होती है जब आप डेटा के अत्यधिक विवश सेट के खिलाफ स्टोचस्टिक एल्गोरिथ्म का परीक्षण करते हैं। यदि आप फिर वापस जाते हैं और अपने एल्गोरिथ्म को परिष्कृत करते हैं, तो आप इसे स्पष्ट रूप से प्रशिक्षण डेटा को बहुत अच्छी तरह से फिट कर देंगे (आप गलती से फिट होंगे अपने एल्गोरिथ्म को परीक्षण डेटा में ), लेकिन अन्य सभी डेटा शायद बिल्कुल नहीं (क्योंकि आप इसके खिलाफ कभी भी परीक्षण नहीं करते हैं) ।

(संयोग से, यह हमेशा एक समस्या है जो इकाई परीक्षण के साथ पीछे हटती है। यही कारण है कि अच्छे परीक्षण पूर्ण होते हैं , या कम से कम किसी दिए गए इकाई के लिए प्रतिनिधि हैं , और यह सामान्य रूप से कठिन है।)

यदि आप यादृच्छिक संख्या जनरेटर को प्लग-इन करके अपना परीक्षण निर्धारक बनाते हैं, तो आप हमेशा एक ही बहुत छोटे और (आमतौर पर गैर-प्रतिनिधि ) के खिलाफ परीक्षण करते हैं। डेटा सेट के । यह आपके डेटा को स्कैन करता है और आपके फ़ंक्शन में पूर्वाग्रह पैदा कर सकता है।

दूसरा बिंदु, अव्यवहारिकता, तब उत्पन्न होती है जब आपको स्टोकेस्टिक चर पर कोई नियंत्रण नहीं मिला है। यह आमतौर पर यादृच्छिक संख्या जनरेटर के साथ नहीं होता है (जब तक कि आपको "यादृच्छिक के वास्तविक" स्रोत की आवश्यकता नहीं होती है) लेकिन ऐसा तब हो सकता है जब स्टोचस्टिक आपकी समस्या में अन्य तरीकों से चुपके हो। उदाहरण के लिए, समवर्ती कोड का परीक्षण करते समय: दौड़ की स्थिति हमेशा स्टोचस्टिक होती है, आप नहीं कर सकते उन्हें आसानी से निर्धारित ।

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


2

आपके पास वास्तव में यहां कई जिम्मेदारियां हैं। यूनिट परीक्षण और विशेष रूप से टीडीडी इस प्रकार को उजागर करने के लिए बहुत अच्छा है।

जिम्मेदारियां हैं:

1) यादृच्छिक संख्या जनरेटर। 2) पासवर्ड फॉर्मेटर।

पासवर्ड फॉर्मेटर यादृच्छिक संख्या जनरेटर का उपयोग करता है। एक इंटरफेस के रूप में अपने कंस्ट्रक्टर के माध्यम से अपने फॉर्मेटर में जनरेटर को इंजेक्ट करें। अब आप अपने यादृच्छिक संख्या जनरेटर (सांख्यिकीय परीक्षण) का पूरी तरह से परीक्षण कर सकते हैं और एक नकली रैंडम संख्या जनरेटर को इंजेक्ट करके फ़ॉर्मेटर का परीक्षण कर सकते हैं।

न केवल यू बेहतर कोड प्राप्त करें आप बेहतर परीक्षण प्राप्त करें।


2

जैसा कि दूसरों ने पहले ही उल्लेख किया है, आप यादृच्छिकता को दूर करके इस कोड का परीक्षण करते हैं।

आप एक उच्च-स्तरीय परीक्षण भी कर सकते हैं, जो यादृच्छिक संख्या जनरेटर को जगह में छोड़ देता है, केवल अनुबंध (पासवर्ड की लंबाई, अनुमत वर्ण, ...) का परीक्षण करता है और, विफलता पर, आपको सिस्टम को पुन: पेश करने की अनुमति देने के लिए पर्याप्त जानकारी देता है। एक उदाहरण में राज्य जहां यादृच्छिक परीक्षण विफल रहा।

इससे कोई फर्क नहीं पड़ता है कि परीक्षण स्वयं दोहराए जाने योग्य नहीं है - जब तक आप कारण जान सकते हैं कि यह एक बार क्यों विफल रहा।


2

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

देखने का एक और तरीका यह है कि इकाई परीक्षणों को इस सवाल का जवाब देना चाहिए कि "क्या यह कोड वह है जो मैं इसे करने का इरादा रखता हूं?"। आपके मामले में, आपको नहीं पता कि आप कोड का इरादा क्या करना चाहते हैं क्योंकि यह गैर-नियतात्मक है।

इस दिमाग के साथ, अपने तर्क को छोटे, आसानी से समझे जाने वाले, आसानी से जांचे जाने वाले अलगाव भागों में अलग करें। विशेष रूप से, आप एक अलग विधि (या वर्ग!) बनाते हैं जो यादृच्छिकता के स्रोत को इसके इनपुट के रूप में लेता है, और आउटपुट के रूप में पासवर्ड का उत्पादन करता है। यह कोड स्पष्ट रूप से निर्धारक है।

अपने यूनिट टेस्ट में, आप इसे हर बार एक ही नहीं काफी-रैंडम इनपुट फीड करते हैं। बहुत छोटे यादृच्छिक धाराओं के लिए, बस अपने परीक्षण में मूल्यों को हार्ड-कोड करें। अन्यथा, अपने परीक्षण में आरएनजी को एक निरंतर बीज प्रदान करें।

परीक्षण के उच्च स्तर पर (इसे "स्वीकृति" या "एकीकरण" या जो भी कहें), आप कोड को एक सच्चे यादृच्छिक स्रोत के साथ चलने देंगे।


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

1

उपर्युक्त अधिकांश उत्तर इंगित करते हैं कि यादृच्छिक संख्या जनरेटर का मज़ाक उड़ाने का तरीका है, हालांकि मैं केवल mt_rand फ़ंक्शन में निर्मित का उपयोग कर रहा था। अलोकेशन मॉकिंग का अर्थ होता है निर्माण समय पर एक यादृच्छिक संख्या जनरेटर को इंजेक्ट करने के लिए कक्षा को फिर से लिखना।

या तो मैंने सोचा!

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

यहाँ अंतिम परीक्षण सूट है:

namespace gordian\reefknot\util;

/**
 * The following function will take the place of mt_rand for the duration of 
 * the test.  It always returns the number exactly half way between the min 
 * and the max.
 */
function mt_rand ($min = 42, $max = NULL)
{
    $min    = intval ($min);
    $max    = intval ($max);

    $max    = $max < $min? $min: $max;
    $ret    = round (($max - $min) / 2) + $min;

    //fwrite (STDOUT, PHP_EOL . PHP_EOL . $ret . PHP_EOL . PHP_EOL);
    return ($ret);
}

/**
 * Override the password character pool for the test 
 */
class PasswordSubclass extends Password
{
    const CHARLIST  = 'AAAAAAAAAA';
}

/**
 * Test class for Password.
 * Generated by PHPUnit on 2011-12-17 at 18:10:33.
 */
class PasswordTest extends \PHPUnit_Framework_TestCase
{

    /**
     * @var gordian\reefknot\util\Password
     */
    protected $object;

    const PWMIN = 15;
    const PWMAX = 20;

    /**
     * Sets up the fixture, for example, opens a network connection.
     * This method is called before a test is executed.
     */
    protected function setUp ()
    {
    }

    /**
     * Tears down the fixture, for example, closes a network connection.
     * This method is called after a test is executed.
     */
    protected function tearDown ()
    {

    }

    public function testGetPassword ()
    {
        $this -> object = new PasswordSubclass (self::PWMIN, self::PWMAX);
        $pw = $this -> object -> getPassword ();
        $this -> assertTrue ((bool) preg_match ('/^A{' . self::PWMIN . ',' . self::PWMAX . '}$/', $pw));
        $this -> assertTrue (strlen ($pw) >= self::PWMIN);
        $this -> assertTrue (strlen ($pw) <= self::PWMAX);
        $this -> assertTrue ($pw === $this -> object -> getPassword ());
    }

    public function testGetPasswordFixedLen ()
    {
        $this -> object = new PasswordSubclass (self::PWMIN, self::PWMIN);
        $pw = $this -> object -> getPassword ();
        $this -> assertTrue ($pw === 'AAAAAAAAAAAAAAA');
        $this -> assertTrue ($pw === $this -> object -> getPassword ());
    }

    public function testGetPasswordFixedLen2 ()
    {
        $this -> object = new PasswordSubclass (self::PWMAX, self::PWMAX);
        $pw = $this -> object -> getPassword ();
        $this -> assertTrue ($pw === 'AAAAAAAAAAAAAAAAAAAA');
        $this -> assertTrue ($pw === $this -> object -> getPassword ());
    }

    public function testInvalidLenThrowsException ()
    {
        $exception  = NULL;
        try
        {
            $this -> object = new PasswordSubclass (self::PWMAX, self::PWMIN);
        }
        catch (\Exception $e)
        {
            $exception  = $e;
        }
        $this -> assertTrue ($exception instanceof \InvalidArgumentException);
    }
}

मैंने सोचा था कि मैं इसका उल्लेख करूंगा, क्योंकि PHP के आंतरिक कार्यों को ओवरराइड करना नामस्थानों के लिए एक और उपयोग है जो बस मेरे लिए नहीं हुआ था। इसके लिए मदद के लिए सभी को धन्यवाद।


0

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

यह मूल रूप से यह सुनिश्चित करता है कि आप अपने यादृच्छिक फ़ंक्शन का ठीक से उपयोग कर रहे हैं, न कि हर कॉल पर री-सीडिंग।


दरअसल, क्लास को इस तरह से डिज़ाइन किया जाता है कि पासवर्ड पहले कॉल पर उत्पन्न होता है getPassword () और फिर लैचेस, इसलिए यह हमेशा ऑब्जेक्ट के जीवनकाल के लिए एक ही पासवर्ड देता है। मेरा परीक्षण सूट पहले से ही जाँचता है कि एक ही पासवर्ड उदाहरण पर getPassword () के लिए कई कॉल हमेशा एक ही पासवर्ड स्ट्रिंग देता है। थ्रेड-सेफ्टी के लिए, यह वास्तव में PHP में चिंता का विषय नहीं है :)
गॉर्डन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.