यह हम सभी के साथ होता है। जैसे-जैसे सॉफ़्टवेयर प्रोजेक्ट बढ़ते हैं, कोडबेस के कुछ हिस्से एक व्यापक टेस्ट सूट के बिना प्रोडक्शन में चले जाते हैं। जब आप कुछ महीनों बाद कोड के उसी क्षेत्र को फिर से देखते हैं, तो इसे समझना मुश्किल हो सकता है; इससे भी बदतर, इसमें एक बग हो सकता है, और हमें नहीं पता कि इसे ठीक करना कहां से शुरू करें।
टेस्ट के बिना कोड को संशोधित करना एक बड़ी चुनौती है। हम निश्चित नहीं हो सकते कि हम इस प्रक्रिया में कुछ तोड़ देंगे, और सब कुछ मैन्युअल रूप से जांचना, सबसे अच्छा, गलतियों की संभावना है; आमतौर पर, यह असंभव है।
इस तरह के कोड से निपटना डेवलपर्स के रूप में हमारे द्वारा किए जाने वाले सबसे आम कार्यों में से एक है, और कई तकनीकों ने वर्षों से इस मुद्दे पर ध्यान केंद्रित किया है, जैसे कि कैरेक्टराइज़ेशन टेस्ट, जिन पर हमने पिछले लेख में चर्चा की थी।
आज, हम कैरेक्टराइज़ेशन टेस्ट पर आधारित एक और तकनीक को कवर करेंगे और जिसे केंट बेक द्वारा पेश किया गया था, जिन्होंने कई साल पहले आधुनिक प्रोग्रामिंग दुनिया में TDD को भी पेश किया था।
TCR क्या है?
TCR का अर्थ है "टेस्ट, कमिट, रिवर्ट", लेकिन इसे "टेस्ट && कमिट || रिवर्ट" कहना अधिक सटीक है। आइए देखें क्यों।
यह तकनीक लिगेसी कोड को टेस्ट करने के लिए एक वर्कफ़्लो का वर्णन करती है। हम एक स्क्रिप्ट का उपयोग करेंगे जो हर बार जब हम अपनी प्रोजेक्ट फाइलों को सेव करते हैं तो टेस्ट चलाती है। प्रक्रिया इस प्रकार है:
- सबसे पहले, हम लिगेसी कोड के उस हिस्से के लिए एक खाली यूनिट टेस्ट बनाते हैं जिसे हम टेस्ट करना चाहते हैं।
- फिर हम एक सिंगल एसर्टेशन जोड़ते हैं और टेस्ट को सेव करते हैं।
- चूंकि हमने अपनी स्क्रिप्ट सेट कर ली है, टेस्ट स्वचालित रूप से चलता है। यदि यह सफल होता है, तो परिवर्तन कमिट हो जाता है। यदि यह विफल होता है, तो परिवर्तन हटा दिया जाता है (रिवर्ट), और हमें फिर से प्रयास करने की आवश्यकता होती है।
एक बार टेस्ट पास हो जाने पर, हम एक नया टेस्ट केस जोड़ सकते हैं।
अनिवार्य रूप से, TCR आपके कोड को "ग्रीन" स्थिति में रखने के बारे में है, बजाय पहले एक फेलिंग टेस्ट लिखने (रेड) और फिर इसे पास कराने (ग्रीन) के, जैसा कि हम टेस्ट-ड्रिवन डेवलपमेंट के साथ करते हैं। यदि हम एक फेलिंग टेस्ट लिखते हैं, तो यह बस गायब हो जाएगा, और हमें फिर से "ग्रीन" स्थिति में वापस लाया जाएगा।
उद्देश्य
इस तकनीक का मुख्य लक्ष्य हर बार टेस्ट केस जोड़ने पर कोड को थोड़ा बेहतर समझना है। यह स्वाभाविक रूप से टेस्ट कवरेज बढ़ाएगा और कई रीफैक्टरिंग को अनब्लॉक करेगा जो अन्यथा संभव नहीं होते।
TCR के फायदों में से एक यह है कि यह कई परिदृश्यों में उपयोगी है। हम इसे बिना किसी टेस्ट वाले कोड के साथ या आंशिक रूप से टेस्ट किए गए कोड के साथ उपयोग कर सकते हैं। यदि टेस्ट पास नहीं होता है, तो हम बस परिवर्तन को रिवर्ट करते हैं और फिर से प्रयास करते हैं।
हम इसका उपयोग कैसे कर सकते हैं?
केंट बेक विभिन्न लेखों और वीडियो में (अंत में लिंक किए गए) दिखाते हैं कि एक अच्छा दृष्टिकोण एक स्क्रिप्ट का उपयोग करना है जो प्रोजेक्ट में कुछ फाइलों के सेव होने के बाद चलती है।
यह आपके द्वारा टेस्ट करने का प्रयास कर रहे प्रोजेक्ट पर बहुत अधिक निर्भर करेगा। निम्नलिखित जैसी स्क्रिप्ट, जो एडिटर में एक प्लगइन का उपयोग करके हर बार फाइलों को सेव करने पर निष्पादित होती है, एक अच्छी शुरुआत है:
(rspec && git commit -am "WIP") || git reset --hard
यदि आप विजुअल स्टूडियो कोड का उपयोग कर रहे हैं, तो हर सेव पर निष्पादित करने के लिए एक अच्छा प्लगइन "runonsave" है। आप उपरोक्त कमांड या अपने प्रोजेक्ट के लिए एक समान कमांड शामिल कर सकते हैं। इस मामले में, संपूर्ण कॉन्फ़िग फाइल होगी:
{
"folders": [{ "path": "." }],
"settings": {
"emeraldwalk.runonsave": {
"commands": [
{
"match": "*.rb",
"cmd": "cd ${workspaceRoot} && rspec && git commit -am WIP || git reset --hard"
}
]
}
}
}
याद रखें कि बाद में, आप कमिट को गिट के साथ सीधे कमांड लाइन में या PR मर्ज करते समय स्क्वैश कर सकते हैं यदि आप गिटहब का उपयोग कर रहे हैं:

इसका मतलब है कि हमें मुख्य ब्रांच में केवल एक कमिट मिलेगा, उस ब्रांच पर हमारे द्वारा किए गए सभी कमिट्स के लिए जिस पर हम काम कर रहे हैं। गिटहब का यह आरेख इसे अच्छी तरह से समझाता है:

TCR के साथ हमारा पहला टेस्ट लिखना
हम तकनीक को स्पष्ट करने के लिए एक सरल उदाहरण का उपयोग करेंगे। हमारे पास एक क्लास है जिसे हम जानते हैं कि काम कर रही है, लेकिन हमें इसे संशोधित करने की आवश्यकता है।
हम बस एक परिवर्तन कर सकते हैं और परिवर्तनों को डेप्लॉय कर सकते हैं। हालांकि, हम यह सुनिश्चित करना चाहते हैं कि हम इस प्रक्रिया में कुछ भी न तोड़ें, जो हमेशा एक अच्छा विचार है।
# worker.rb
class Worker
def initialize(age, active_years, veteran)
@age = age
@active_years = active_years
@veteran = veteran
end
def can_retire?
return true if @age >= 67
return true if @active_years >= 30
return true if @age >= 60 && @active_years >= 25
return true if @veteran && @active_years > 25
false
end
end
पहला कदम टेस्ट के लिए एक नई फाइल बनाना होगा, ताकि हम वहां उन्हें जोड़ना शुरू कर सकें। हमने can_retire? मेथड में पहली पंक्ति देखी है:
def can_retire?
return true if @age >= 67
...
...
end
इस प्रकार, हम पहले इस केस को टेस्ट कर सकते हैं:
# specs/worker_spec.rb
require_relative './../worker'
describe Worker do
describe 'can_retire?' do
it "should return true if age is higher than 67" do
end
end
end
यहां एक त्वरित सुझाव: जब आप TCR के साथ काम कर रहे हों, तो हर बार जब आप सेव करते हैं, तो नवीनतम परिवर्तन गायब हो जाएंगे यदि टेस्ट पास नहीं होते हैं। इसलिए, हम वास्तव में एसर्टेशन के साथ लाइन या लाइनें लिखने और सेव करने से पहले टेस्ट को "सेट अप" करने के लिए जितना संभव हो उतना कोड रखना चाहते हैं।
यदि हम उपरोक्त फाइल को ऐसे ही सेव करते हैं, तो हम फिर टेस्ट के लिए एक लाइन जोड़ सकते हैं।
require_relative './../worker'
describe Worker do
describe 'can_retire?' do
it "should return true if age is higher than 67" do
expect(Worker.new(70, 10, false).can_retire?).to be_true ## यह लाइन अब सेव करने पर गायब हो सकती है
end
end
end
जब हम सेव करते हैं, यदि नई लाइन गायब नहीं होती है, तो हमने अच्छा काम किया है; टेस्ट पास हो जाता है!
अधिक टेस्ट जोड़ना
एक बार हमारे पास पहला टेस्ट हो जाने पर, हम झूठे मामलों को ध्यान में रखते हुए अधिक केस जोड़ते रह सकते हैं। कुछ काम के बाद, हमारे पास ऐसा कुछ है:
# frozen_string_literal: true
require_relative './../worker'
describe Worker do
describe 'can_retire?' do
it 'should return true if age is higher than 67' do
expect(Worker.new(70, 10, false).can_retire?).to be true
end
it 'should return true if age is 67' do
expect(Worker.new(67, 10, false).can_retire?).to be true
end
it 'should return true if age is less than 67' do
expect(Worker.new(50, 10, false).can_retire?).to be false
end
it 'should return true if active years is higher than 30' do
expect(Worker.new(60, 31, false).can_retire?).to be true
end
it 'should return true if active years is 30' do
expect(Worker.new(60, 30, false).can_retire?).to be true
end
end
end
हर मामले में, हम पहले "इट" ब्लॉक लिखते हैं, सेव करते हैं, और फिर expect(...) के साथ एसर्टेशन जोड़ते हैं।
हमेशा की तरह, हम उतने टेस्ट जोड़ सकते हैं जितने संभव हों, लेकिन एक बार जब हम अपेक्षाकृत निश्चित हो जाते हैं कि सब कुछ कवर हो गया है, तो बहुत अधिक जोड़ने का कोई मतलब नहीं है।
अभी भी कुछ केस कवर करने बाकी हैं, इसलिए हमें पूर्णता के लिए उन्हें जोड़ना चाहिए।
अंतिम टेस्ट
यहां इसका अंतिम रूप में स्पेक फाइल है। जैसा कि आप देख सकते हैं, हम अभी भी अधिक केस जोड़ सकते हैं, लेकिन मुझे लगता है कि यह TCR की प्रक्रिया को स्पष्ट करने के लिए पर्याप्त है।
# frozen_string_literal: true
require_relative './../worker'
describe Worker do
describe 'can_retire?' do
it 'should return true if age is higher than 67' do
expect(Worker.new(70, 10, false).can_retire?).to be true
end
it 'should return true if age is 67' do
expect(Worker.new(67, 10, false).can_retire?).to be true
end
it 'should return true if age is less than 67' do
expect(Worker.new(50, 10, false).can_retire?).to be false
end
it 'should return true if active years is higher than 30' do
expect(Worker.new(60, 31, false).can_retire?).to be true
end
it 'should return true if active years is 30' do
expect(Worker.new(20, 30, false).can_retire?).to be true
end
it 'should return true if age is higher than 60 and active years is higher than 25' do
expect(Worker.new(60, 30, false).can_retire?).to be true
end
it 'should return true if age is higher than 60 and active years is higher than 25' do
expect(Worker.new(61, 30, false).can_retire?).to be true
end
it 'should return true if age is 60 and active years is higher than 25' do
expect(Worker.new(60, 30, false).can_retire?).to be true
end
it 'should return true if age is higher than 60 and active years is 25' do
expect(Worker.new(61, 25, false).can_retire?).to be true
end
it 'should return true if age is 60 and active years is 25' do
expect(Worker.new(60, 25, false).can_retire?).to be true
end
it 'should return true if is veteran and active years is higher than 25' do
expect(Worker.new(60, 25, false).can_retire?).to be true
end
end
end
रीफैक्टर करने के तरीके
अगर आप यहां तक पढ़े हैं, तो शायद कुछ ऐसा है जो कोड के साथ थोड़ा अजीब लगता है। हमारे पास कई "जादुई नंबर" हैं जिन्हें स्थिरांक में निकाला जाना चाहिए, टेस्ट और Worker क्लास दोनों में।
हम मुख्य can_retire? पब्लिक मेथड में प्रत्येक केस के लिए प्राइवेट मेथड भी बना सकते हैं।
मैं दोनों संभावित रीफैक्टरिंग को आपके लिए अभ्यास के रूप में छोड़ दूंगा। हालांकि, अब हमारे पास टेस्ट हैं, इसलिए अगर हम किसी भी चरण में गलती करते हैं, तो वे हमें बताएंगे।
निष्कर्ष
मैं आपको अपने प्रोजेक्ट्स के साथ TCR आज़माने के लिए प्रोत्साहित करता हूं। यह एक बहुत सस्ता प्रयोग है क्योंकि आपको किसी बाहरी सर्वर में फैंसी कंटीन्यूअस इंटीग्रेशन या नई लाइब्रेरी पर निर्भरता की आवश्यकता नहीं है। आपको बस अपने कंप्यूटर पर कुछ फाइलों को सेव करने पर कमांड निष्पादित करने का एक तरीका चाहिए।
यह टेस्ट जोड़ते समय एक "गेमिंग" अनुभव भी देगा, जो हमेशा मजेदार और दिलचस्प होता है। इसके अलावा, आपके एडिटर से फेलिंग टेस्ट को हटाने का अनुशासन आपको एक अतिरिक्त सुरक्षा जाल देगा, यह पुष्टि करके कि आप जो टेस्ट रिपॉजिटरी में पुश कर रहे हैं वे पास हो रहे हैं।
मुझे उम्मीद है कि आप लिगेसी कोड से निपटने में इस नई तकनीक को उपयोगी पाएंगे। मैंने पिछले कुछ महीनों में इसका कई बार उपयोग किया है, और यह हमेशा एक खुशी की बात रही है।
अतिरिक्त संसाधन
- परिचय के रूप में अच्छा वीडियो।
- VS कोड में TCR का उपयोग कैसे करें पर केंट बेक का दृष्टिकोण।
- सेव पर स्क्रिप्ट चलाने के लिए VS कोड का प्लगइन।