जावा, रूबी, और पायथन जैसी भाषाओं में एप्लिकेशन लिखने वाले सॉफ़्टवेयर डेवलपर्स के पास समय के साथ अपने सॉफ़्टवेयर की अखंडता को बनाए रखने में मदद करने के लिए परिष्कृत लाइब्रेरी हैं। वे टेस्ट बनाते हैं जो एप्लिकेशन को संरचित वातावरण में निष्पादन की एक श्रृंखला के माध्यम से चलाते हैं ताकि यह सुनिश्चित हो सके कि उनके सॉफ़्टवेयर के सभी पहलू अपेक्षा के अनुरूप काम करते हैं।
ये टेस्ट तब और भी शक्तिशाली होते हैं जब उन्हें निरंतर एकीकरण (CI) सिस्टम में स्वचालित किया जाता है, जहां स्रोत रिपॉजिटरी में हर पुश टेस्ट को चलाने का कारण बनता है, और डेवलपर्स को तुरंत सूचित किया जाता है जब टेस्ट विफल होते हैं। यह तेज़ फीडबैक डेवलपर्स के उनके एप्लिकेशन की कार्यात्मक अखंडता में आत्मविश्वास को बढ़ाता है।
बैश ऑटोमेटेड टेस्टिंग सिस्टम (BATS) बैश स्क्रिप्ट और लाइब्रेरी लिखने वाले डेवलपर्स को जावा, रूबी, पायथन, और अन्य डेवलपर्स द्वारा उपयोग की जाने वाली प्रथाओं को अपने बैश कोड पर लागू करने में सक्षम बनाता है।
BATS इंस्टॉल करना
BATS GitHub पेज में इंस्टॉलेशन निर्देश शामिल हैं। दो BATS हेल्पर लाइब्रेरी हैं जो अधिक शक्तिशाली दावे प्रदान करती हैं या BATS द्वारा उपयोग किए जाने वाले टेस्ट एनीथिंग प्रोटोकॉल (TAP) आउटपुट फॉर्मेट को ओवरराइड करने की अनुमति देती हैं। इन्हें एक मानक स्थान में इंस्टॉल किया जा सकता है और सभी स्क्रिप्ट द्वारा सोर्स किया जा सकता है। प्रत्येक स्क्रिप्ट या लाइब्रेरी के सेट के लिए Git रिपॉजिटरी में BATS और इसकी हेल्पर लाइब्रेरी का एक पूर्ण संस्करण शामिल करना अधिक सुविधाजनक हो सकता है। इसे git submodule सिस्टम का उपयोग करके पूरा किया जा सकता है।
निम्नलिखित कमांड BATS और इसकी हेल्पर लाइब्रेरी को Git रिपॉजिटरी में test डायरेक्टरी में इंस्टॉल करेंगे।
git submodule init
git submodule add https://github.com/sstephenson/bats test/libs/bats
git submodule add https://github.com/ztombol/bats-assert test/libs/bats-assert
git submodule add https://github.com/ztombol/bats-support test/libs/bats-support
git add .
git commit -m 'installed bats'
Git रिपॉजिटरी को क्लोन करने और उसके सबमॉड्यूल को एक ही समय में इंस्टॉल करने के लिए, git clone में --recurse-submodules फ्लैग का उपयोग करें।
प्रत्येक BATS टेस्ट स्क्रिप्ट को bats एक्जीक्यूटेबल द्वारा निष्पादित किया जाना चाहिए। यदि आपने BATS को अपने सोर्स कोड रेपो के test/libs डायरेक्टरी में इंस्टॉल किया है, तो आप टेस्ट को इस तरह चला सकते हैं:
./test/libs/bats/bin/bats <path to test script>
वैकल्पिक रूप से, अपने प्रत्येक BATS टेस्ट स्क्रिप्ट की शुरुआत में निम्नलिखित जोड़ें:
#!/usr/bin/env ./test/libs/bats/bin/bats
load 'libs/bats-support/load'
load 'libs/bats-assert/load'
और chmod +x <path to test script>। यह a) उन्हें ./test/libs/bats में इंस्टॉल्ड BATS के साथ निष्पादन योग्य बना देगा और b) इन हेल्पर लाइब्रेरी को शामिल करेगा। BATS टेस्ट स्क्रिप्ट आमतौर पर test डायरेक्टरी में स्टोर की जाती हैं और टेस्ट किए जा रहे स्क्रिप्ट के नाम पर नामित होती हैं, लेकिन .bats एक्सटेंशन के साथ। उदाहरण के लिए, bin/build का टेस्ट करने वाला BATS स्क्रिप्ट test/build.bats कहलाना चाहिए।
आप BATS को एक रेगुलर एक्सप्रेशन पास करके BATS टेस्ट फाइलों के पूरे सेट को भी चला सकते हैं, उदाहरण के लिए, ./test/lib/bats/bin/bats test/*.bats।
BATS कवरेज के लिए लाइब्रेरी और स्क्रिप्ट का आयोजन
बैश स्क्रिप्ट और लाइब्रेरी को इस तरह से व्यवस्थित किया जाना चाहिए कि उनके आंतरिक कामकाज को BATS के लिए कुशलतापूर्वक उजागर किया जा सके। सामान्य तौर पर, लाइब्रेरी फंक्शन और शेल स्क्रिप्ट जो कॉल या निष्पादित होने पर कई कमांड चलाते हैं, कुशल BATS टेस्टिंग के लिए उत्तरदायी नहीं होते हैं।
उदाहरण के लिए, build.sh एक विशिष्ट स्क्रिप्ट है जो कई लोग लिखते हैं। यह अनिवार्य रूप से कोड का एक बड़ा ढेर है। कुछ लोग इस कोड के ढेर को लाइब्रेरी में एक फंक्शन में भी डाल सकते हैं। लेकिन कोड के एक बड़े ढेर को BATS टेस्ट में चलाना और उन सभी संभावित प्रकार की विफलताओं को अलग-अलग टेस्ट केस में कवर करना असंभव है जिनका यह सामना कर सकता है। इस कोड के ढेर को पर्याप्त कवरेज के साथ टेस्ट करने का एकमात्र तरीका इसे कई छोटे, पुन: प्रयोज्य, और सबसे महत्वपूर्ण, स्वतंत्र रूप से टेस्ट करने योग्य फंक्शन में तोड़ना है।
लाइब्रेरी में अधिक फंक्शन जोड़ना सीधा है। एक अतिरिक्त लाभ यह है कि इनमें से कुछ फंक्शन अपने आप में आश्चर्यजनक रूप से उपयोगी बन सकते हैं। एक बार जब आप अपने लाइब्रेरी फंक्शन को कई छोटे फंक्शन में तोड़ देते हैं, तो आप BATS टेस्ट में लाइब्रेरी को source कर सकते हैं और फंक्शन को किसी अन्य कमांड की तरह चला सकते हैं ताकि उन्हें टेस्ट किया जा सके।
बैश स्क्रिप्ट को भी कई फंक्शन में तोड़ा जाना चाहिए, जिन्हें स्क्रिप्ट के मुख्य भाग द्वारा कॉल किया जाना चाहिए जब स्क्रिप्ट निष्पादित होती है। इसके अलावा, BATS के साथ बैश स्क्रिप्ट का टेस्ट करना बहुत आसान बनाने के लिए एक बहुत उपयोगी ट्रिक है: स्क्रिप्ट के मुख्य भाग में निष्पादित होने वाले सभी कोड को लें और इसे run_main जैसी किसी चीज़ नामक फंक्शन में ले जाएं। फिर, स्क्रिप्ट के अंत में निम्नलिखित जोड़ें:
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]
then
run_main
fi
यह अतिरिक्त कोड कुछ खास करता है। यह स्क्रिप्ट को स्क्रिप्ट के रूप में निष्पादित होने पर अलग व्यवहार करने की तुलना में अलग व्यवहार करता है जब इसे source के साथ एनवायरनमेंट में लाया जाता है। यह ट्रिक स्क्रिप्ट को लाइब्रेरी के समान तरीके से टेस्ट करने में सक्षम बनाती है, इसे सोर्स करके और व्यक्तिगत फंक्शन का टेस्ट करके। उदाहरण के लिए, यहां बेहतर BATS टेस्टेबिलिटी के लिए रीफैक्टर किया गया build.sh है।
टेस्ट लिखना और चलाना
जैसा कि ऊपर उल्लेख किया गया है, BATS एक TAP-अनुपालन टेस्टिंग फ्रेमवर्क है जिसका सिंटैक्स और आउटपुट उन लोगों से परिचित होगा जिन्होंने JUnit, RSpec, या Jest जैसे अन्य TAP-अनुपालन टेस्टिंग सूट का उपयोग किया है। इसके टेस्ट व्यक्तिगत टेस्ट स्क्रिप्ट में व्यवस्थित हैं। टेस्ट स्क्रिप्ट को एक या अधिक वर्णनात्मक @test ब्लॉक में व्यवस्थित किया जाता है जो टेस्ट किए जा रहे एप्लिकेशन की इकाई का वर्णन करते हैं। प्रत्येक @test ब्लॉक कमांड की एक श्रृंखला चलाएगा जो टेस्ट एनवायरनमेंट तैयार करता है, टेस्ट की जाने वाली कमांड चलाता है, और टेस्ट की गई कमांड के एग्जिट और आउटपुट के बारे में दावे करता है। कई दावा फंक्शन bats, bats-assert, और bats-support लाइब्रेरी के साथ इम्पोर्ट किए जाते हैं, जिन्हें BATS टेस्ट स्क्रिप्ट की शुरुआत में एनवायरनमेंट में लोड किया जाता है। यहां एक विशिष्ट BATS टेस्ट ब्लॉक है:
@test "requires CI_COMMIT_REF_SLUG environment variable" {
unset CI_COMMIT_REF_SLUG
assert_empty "${CI_COMMIT_REF_SLUG}"
run some_command
assert_failure
assert_output --partial "CI_COMMIT_REF_SLUG"
}
यदि एक BATS स्क्रिप्ट में setup और/या teardown फंक्शन शामिल हैं, तो वे BATS द्वारा प्रत्येक टेस्ट ब्लॉक के चलने से पहले और बाद में स्वचालित रूप से निष्पादित होते हैं। इससे एनवायरनमेंट वेरिएबल, टेस्ट फाइलें बनाने, और एक या सभी टेस्ट के लिए आवश्यक अन्य चीजें करना संभव हो जाता है, फिर प्रत्येक टेस्ट चलने के बाद उन्हें तोड़ दिया जाता है। Build.bats हमारे नए स्वरूपित build.sh स्क्रिप्ट का एक पूर्ण BATS टेस्ट है। (इस टेस्ट में mock_docker कमांड को नीचे मॉकिंग/स्टबिंग अनुभाग में समझाया जाएगा।)
जब टेस्ट स्क्रिप्ट चलती है, तो BATS प्रत्येक @test ब्लॉक को एक अलग सबप्रोसेस के रूप में चलाने के लिए exec का उपयोग करता है। इससे एक @test में एनवायरनमेंट वेरिएबल और यहां तक कि फंक्शन को एक्सपोर्ट करना संभव हो जाता है बिना अन्य @tests को प्रभावित किए या अपने वर्तमान शेल सत्र को प्रदूषित किए। टेस्ट रन का आउटपुट एक मानक फॉर्मेट है जिसे मनुष्यों द्वारा समझा जा सकता है और प्रोग्रामेटिक रूप से TAP उपभोक्ताओं द्वारा पार्स या हेरफेर किया जा सकता है। यहां CI_COMMIT_REF_SLUG टेस्ट ब्लॉक के आउटपुट का एक उदाहरण है जब यह विफल होता है:
✗ requires CI_COMMIT_REF_SLUG environment variable
(from function `assert_output' in file test/libs/bats-assert/src/assert.bash, line 231,
in test file test/ci_deploy.bats, line 26)
`assert_output --partial "CI_COMMIT_REF_SLUG"' failed
-- output does not contain substring --
substring (1 lines):
CI_COMMIT_REF_SLUG
output (3 lines):
./bin/deploy.sh: join_string_by: command not found
oc error
Could not login
--
** Did not delete , as test failed **
1 test, 1 failure
यहां एक सफल टेस्ट का आउटपुट है:
✓ requires CI_COMMIT_REF_SLUG environment variable
हेल्पर्स
किसी भी शेल स्क्रिप्ट या लाइब्रेरी की तरह, BATS टेस्ट स्क्रिप्ट में टेस्ट में सामान्य कोड साझा करने या उनकी क्षमताओं को बढ़ाने के लिए हेल्पर लाइब्रेरी शामिल हो सकती हैं। ये हेल्पर लाइब्रेरी, जैसे bats-assert और bats-support, को BATS के साथ भी टेस्ट किया जा सकता है।
लाइब्रेरी को BATS स्क्रिप्ट के समान टेस्ट डायरेक्टरी में रखा जा सकता है या test/libs डायरेक्टरी में यदि टेस्ट डायरेक्टरी में फाइलों की संख्या अनियंत्रित हो जाती है। BATS load फंक्शन प्रदान करता है जो टेस्ट किए जा रहे स्क्रिप्ट के सापेक्ष एक बैश फाइल का पाथ लेता है (उदाहरण के लिए, test, हमारे मामले में) और उस फाइल को सोर्स करता है। फाइलों को .bash प्रीफिक्स के साथ समाप्त होना चाहिए, लेकिन load फंक्शन को पास किए गए फाइल के पाथ में प्रीफिक्स शामिल नहीं हो सकता। build.bats bats-assert और bats-support लाइब्रेरी, एक छोटी helpers.bash लाइब्रेरी, और एक docker_mock.bash लाइब्रेरी (नीचे वर्णित) को इंटरप्रेटर मैजिक लाइन के नीचे टेस्ट स्क्रिप्ट की शुरुआत में रखे गए निम्नलिखित कोड के साथ लोड करता है:
load 'libs/bats-support/load'
load 'libs/bats-assert/load'
load 'helpers'
load 'docker_mock'
टेस्ट इनपुट को स्टब करना और बाहरी कॉल को मॉक करना
अधिकांश बैश स्क्रिप्ट और लाइब्रेरी जब चलती हैं तो फंक्शन और/या एक्जीक्यूटेबल निष्पादित करती हैं। अक्सर उन्हें इन फंक्शन या एक्जीक्यूटेबल के एग्जिट स्टेटस या आउटपुट (stdout, stderr) के आधार पर विशिष्ट तरीकों से व्यवहार करने के लिए प्रोग्राम किया जाता है। इन स्क्रिप्ट को ठीक से टेस्ट करने के लिए, इन कमांड के नकली संस्करण बनाना अक्सर आवश्यक होता है जो किसी विशिष्ट टेस्ट के दौरान विशिष्ट तरीके से व्यवहार करने के लिए डिज़ाइन किए जाते हैं, एक प्रक्रिया जिसे "स्टबिंग" कहा जाता है। प्रोग्राम की जासूसी करना भी आवश्यक हो सकता है ताकि यह सुनिश्चित हो सके कि यह किसी विशिष्ट कमांड को कॉल करता है, या यह किसी विशिष्ट कमांड को विशिष्ट आर्गुमेंट के साथ कॉल करता है, एक प्रक्रिया जिसे "मॉकिंग" कहा जाता है। इस पर अधिक जानकारी के लिए, रूबी RSpec में मॉकिंग और स्टबिंग की इस शानदार चर्चा को देखें, जो किसी भी टेस्टिंग सिस्टम पर लागू होती है।
बैश शेल ट्रिक्स प्रदान करता है जिनका उपयोग आपके BATS टेस्ट स्क्रिप्ट में मॉकिंग और स्टबिंग करने के लिए किया जा सकता है। सभी को मूल फंक्शन या एक्जीक्यूटेबल को ओवरराइड करने वाले फंक्शन को एक्सपोर्ट करने के लिए -f फ्लैग के साथ बैश export कमांड के उपयोग की आवश्यकता होती है। यह टेस्ट किए गए प्रोग्राम के निष्पादित होने से पहले किया जाना चाहिए। यहां cat एक्जीक्यूटेबल को ओवरराइड करने का एक सरल उदाहरण है:
function cat() { echo "THIS WOULD CAT ${*}" }
export -f cat
यह विधि उसी तरीके से लाइब्रेरी में एक फंक्शन को ओवरराइड करती है। यदि किसी टेस्ट को टेस्ट किए जा रहे स्क्रिप्ट या लाइब्रेरी के भीतर एक फंक्शन को ओवरराइड करने की आवश्यकता है, तो फंक्शन को स्टब या मॉक करने से पहले टेस्ट किए गए स्क्रिप्ट या लाइब्रेरी को सोर्स करना महत्वपूर्ण है। अन्यथा, स्टब/मॉक को वास्तविक फंक्शन से बदल दिया जाएगा जब स्क्रिप्ट सोर्स की जाती है। साथ ही, जिस कमांड का आप टेस्ट कर रहे हैं उसे चलाने से पहले स्टब/मॉक करना सुनिश्चित करें। यहां build.bats का एक उदाहरण है जो build.sh में वर्णित raise फंक्शन को मॉक करता है ताकि यह सुनिश्चित हो सके कि लॉगिन फंक्शन द्वारा एक विशिष्ट त्रुटि संदेश उठाया गया है:
@test ".login raises on oc error" {
source ${profile_script}
function raise() { echo "${1} raised"; }
export -f raise
run login
assert_failure
assert_output -p "Could not login raised"
}
आम तौर पर, टेस्ट के बाद स्टब/मॉक फंक्शन को अनसेट करना आवश्यक नहीं है, क्योंकि export केवल वर्तमान सबप्रोसेस को वर्तमान @test ब्लॉक के exec के दौरान प्रभावित करता है। हालांकि, उन कमांड (जैसे cat, sed, आदि) को मॉक/स्टब करना संभव है जिनका उपयोग BATS assert* फंक्शन आंतरिक रूप से करते हैं। इन मॉक/स्टब फंक्शन को इन असर्ट कमांड के चलने से पहले unset किया जाना चाहिए, अन्यथा वे ठीक से काम नहीं करेंगे। यहां build.bats का एक उदाहरण है जो sed को मॉक करता है, build_deployable फंक्शन चलाता है, और कोई भी दावा चलाने से पहले sed को अनसेट करता है:
@test ".build_deployable prints information, runs docker build on a modified Dockerfile.production and publish_image when its not a dry_run" {
local expected_dockerfile='Dockerfile.production'
local application='application'
local environment='environment'
local expected_original_base_image="${application}"
local expected_candidate_image="${application}-candidate:${environment}"
local expected_deployable_image="${application}:${environment}"
source ${profile_script}
mock_docker build --build-arg OAUTH_CLIENT_ID --build-arg OAUTH_REDIRECT --build-arg DDS_API_BASE_URL -t "${expected_deployable_image}" -
function publish_image() { echo "publish_image ${*}"; }
export -f publish_image
function sed() {
echo "sed ${*}" >&2;
echo "FROM application-candidate:environment";
}
export -f sed
run build_deployable "${application}" "${environment}"
assert_success
unset sed
assert_output --regexp "sed.*${expected_dockerfile}"
assert_output -p "Building ${expected_original_base_image} deployable ${expected_deployable_image} FROM ${expected_candidate_image}"
assert_output -p "FROM ${expected_candidate_image} piped"
assert_output -p "build --build-arg OAUTH_CLIENT_ID --build-arg OAUTH_REDIRECT --build-arg DDS_API_BASE_URL -t ${expected_deployable_image} -"
assert_output -p "publish_image ${expected_deployable_image}"
}
कभी-कभी वही कमांड, जैसे foo, एक ही फंक्शन में टेस्ट किए जा रहे कई बार, अलग-अलग आर्गुमेंट के साथ, लागू किया जाएगा। इन स्थितियों में फंक्शन के एक सेट के निर्माण की आवश्यकता होती है:
- mock_foo: अपेक्षित आर्गुमेंट को इनपुट के रूप में लेता है, और इन्हें एक TMP फाइल में स्थायी करता है
- foo: कमांड का मॉक्ड संस्करण, जो स्थायी अपेक्षित आर्गुमेंट की सूची के साथ प्रत्येक कॉल को प्रोसेस करता है। इसे export -f के साथ एक्सपोर्ट किया जाना चाहिए।
- cleanup_foo: TMP फाइल को हटाता है, teardown फंक्शन में उपयोग के लिए। यह टेस्ट कर सकता है कि अनसेट करने से पहले @test ब्लॉक सफल रहा था।
चूंकि यह कार्यक्षमता अक्सर विभिन्न टेस्ट में पुन: उपयोग की जाती है, इसलिए एक हेल्पर लाइब्रेरी बनाना समझ में आता है जिसे अन्य लाइब्रेरी की तरह लोड किया जा सकता है।
एक अच्छा उदाहरण docker_mock.bash है। इसे build.bats में लोड किया जाता है और किसी भी टेस्ट ब्लॉक में उपयोग किया जाता है जो डॉकर एक्जीक्यूटेबल को कॉल करने वाले फंक्शन का टेस्ट करता है। docker_mock का उपयोग करने वाला एक विशिष्ट टेस्ट ब्लॉक इस तरह दिखता है:
@test ".publish_image fails if docker push fails" {
setup_publish
local expected_image="image"
local expected_publishable_image="${CI_REGISTRY_IMAGE}/${expected_image}"
source ${profile_script}
mock_docker tag "${expected_image}" "${expected_publishable_image}"
mock_docker push "${expected_publishable_image}" and_fail
run publish_image "${expected_image}"
assert_failure
assert_output -p "tagging ${expected_image} as ${expected_publishable_image}"
assert_output -p "tag ${expected_image} ${expected_publishable_image}"
assert_output -p "pushing image to gitlab registry"
assert_output -p "push ${expected_publishable_image}"
}
यह टेस्ट एक अपेक्षा सेट करता है कि डॉकर को अलग-अलग आर्गुमेंट के साथ दो बार कॉल किया जाएगा। दूसरे डॉकर कॉल के विफल होने के साथ, यह टेस्ट की गई कमांड चलाता है, फिर एग्जिट स्टेटस और डॉकर के अपेक्षित कॉल का टेस्ट करता है।
BATS का एक पहलू जिसे mock_docker.bash द्वारा पेश किया गया है, वह है ${BATS_TMPDIR} एनवायरनमेंट वेरिएबल, जिसे BATS शुरुआत में सेट करता है ताकि टेस्ट और हेल्पर्स एक मानक स्थान में TMP फाइलें बना और नष्ट कर सकें। यदि कोई टेस्ट विफल होता है तो mock_docker.bash लाइब्रेरी अपनी स्थायी मॉक फाइल को नहीं हटाएगी, लेकिन यह प्रिंट करेगी कि यह कहां स्थित है ताकि इसे देखा और हटाया जा सके। आपको समय-समय पर इस डायरेक्टरी से पुरानी मॉक फाइलों को साफ करने की आवश्यकता हो सकती है।
मॉकिंग/स्टबिंग के बारे में एक सावधानी नोट: build.bats टेस्ट टेस्टिंग के एक सिद्धांत का जानबूझकर उल्लंघन करता है जिसमें कहा गया है: जो आपका नहीं है उसे मॉक न करें! यह सिद्धांत मांग करता है कि उन कमांड के कॉल जिन्हें टेस्ट के डेवलपर ने नहीं लिखा, जैसे docker, cat, sed, आदि, को उनकी अपनी लाइब्रेरी में लपेटा जाना चाहिए, जिन्हें उन स्क्रिप्ट के टेस्ट में मॉक किया जाना चाहिए जो उनका उपयोग करती हैं। रैपर लाइब्रेरी को तब बाहरी कमांड को मॉक किए बिना टेस्ट किया जाना चाहिए।
यह अच्छी सलाह है और इसे नज़रअंदाज करने की कीमत चुकानी पड़ती है। यदि डॉकर CLI API बदलता है, तो टेस्ट स्क्रिप्ट इस बदलाव का पता नहीं लगाएंगी, जिसके परिणामस्वरूप एक फाल्स पॉजिटिव होगा जो तब तक प्रकट नहीं होगा जब तक टेस्ट किया गया build.sh स्क्रिप्ट नए संस्करण के डॉकर के साथ प्रोडक्शन सेटिंग में नहीं चलता। टेस्ट डेवलपर्स को यह तय करना होगा कि वे इस मानक का कितनी सख्ती से पालन करना चाहते हैं, लेकिन उन्हें अपने निर्णय के साथ शामिल ट्रेडऑफ को समझना चाहिए।