আমি আপনার এক্সপার্ট সফটওয়্যার ইঞ্জিনিয়ারিং ট্রেইনার। কোর্স আউটলাইন অনুযায়ী, আমরা এখন Section 15: xUnit testing-এর একদম শেষ পর্যায়ে আছি। এই লেকচারে আমরা TDD (Test Driven Development) অ্যাপ্রোচ ফলো করে DeletePerson মেথডের জন্য Unit Test লেখা শিখব। এর ইমপ্লিমেন্টেশন আমরা আগামী লেকচারে করব।
চলুন, আজকের লেকচারটি গুছিয়ে ও বিস্তারিতভাবে বুঝে নেওয়া যাক।
📝 Lecture Summary (Quick Revision)
ভবিষ্যতে দ্রুত রিভিশন দেওয়ার জন্য সম্পূর্ণ লেকচারের মূল কাজগুলো নিচে লিস্ট আকারে দেওয়া হলো:
- Interface Setup:
IPersonsServiceইন্টারফেসেDeletePersonনামের একটি নতুন মেথড ডিক্লেয়ার করা, যা ইনপুট হিসেবেGuid?(personId) রিসিভ করবে এবং ডিলিট সফল হলেtrue, না হলেfalse(Boolean) রিটার্ন করবে। - Dummy Implementation:
PersonsServiceক্লাসে মেথডটির ইনিশিয়াল বা ডামি ইমপ্লিমেন্টেশন করা (যেখানেNotImplementedExceptionথ্রো করা হয়)। - Test Case 1 (Valid Person ID): একটি নতুন Country ও Person তৈরি করে তার ভ্যালিড
PersonIdদিয়েDeletePersonকল করা এবং রিটার্ন ভ্যালুtrueআসে কিনা তাAssert.Trueদিয়ে চেক করা। - Test Case 2 (Invalid Person ID): একটি সম্পূর্ণ নতুন এবং ফেক ID (
Guid.NewGuid()) দিয়েDeletePersonকল করা এবং রিটার্ন ভ্যালুfalseআসে কিনা তাAssert.Falseদিয়ে চেক করা। - TDD Verification: টেস্টগুলো রান করে দেখা যে সবগুলো ফেইল করছে, কারণ মেথডটিতে এখনো আসল লজিক লেখা হয়নি।
🧠 Comprehensive Breakdown
এই লেকচারে আমরা ডিলিট অপারেশনের দুটি ভিন্ন সিনারিও টেস্ট করেছি। চলুন প্রতিটি ধাপ বিস্তারিতভাবে বুঝে নিই।
১. Interface-এ Delete Method ডিক্লেয়ারেশন (Priority: 7/10)
Concept & Why: যখন Controller থেকে কোনো নির্দিষ্ট ইউজারকে ডিলিট করার রিকোয়েস্ট আসবে, তখন এই মেথডটি কল করা হবে। ডিলিট করার পর সাধারণত কোনো ডেটা (DTO) রিটার্ন করার প্রয়োজন হয় না, শুধু অপারেশনটি সফল হয়েছে কি না, তা জানানোর জন্য bool (true/false) রিটার্ন করা হয়। ইনপুট প্যারামিটারকে Nullable (?) করা হয়েছে, যাতে ভুল করে null আসলেও আমরা তা হ্যান্ডেল করতে পারি।
// ServiceContracts/IPersonsService.cs
/// <summary>
/// Deletes a person based on the given person id
/// </summary>
/// <param name="personId">PersonID to delete</param>
/// <returns>Returns true, if the deletion is successful; otherwise false</returns>
bool DeletePerson(Guid? personId);
২. Unit Test 1: Valid Person ID (Successful Deletion) (Priority: 10/10)
Concept & Why: এই টেস্টে আমরা চেক করব, সিস্টেমে আসলেই আছে এমন কাউকে ডিলিট করতে চাইলে মেথডটি true রিটার্ন করে কিনা।
Test Isolation-এর কারণে আমাদের আগে একটি Country এবং একটি Person তৈরি করে নিতে হবে। তারপর সেই Person-এর জেনারেট হওয়া ID দিয়ে ডিলিট কল করতে হবে।
#region DeletePerson
[Fact]
public void DeletePerson_ValidPersonID()
{
// Arrange: Country এবং Person তৈরি করা (কোড সংক্ষেপিত)
/* ... add country and add person logic ... */
// person_response_from_add হলো নতুন তৈরি হওয়া Person
// Act: সঠিক ID দিয়ে DeletePerson কল করা
bool isDeleted = _personsService.DeletePerson(person_response_from_add.PersonID);
// Assert: চেক করা যে ডিলিট অপারেশন সফল (true) হয়েছে কিনা
Assert.True(isDeleted);
}
৩. Unit Test 2: Invalid Person ID (Failed Deletion) (Priority: 10/10)
Concept & Why: আমরা যদি এমন কোনো ID দিয়ে ডিলিট করতে চাই যা সিস্টেমে নেই (যেমন একটি সদ্য তৈরি করা Guid.NewGuid()), তবে সিস্টেমের ক্র্যাশ করা উচিত নয়; বরং মেথডটির উচিত সুন্দরভাবে false রিটার্ন করা। যেহেতু এখানে সিস্টেমে থাকা কাউকে ডিলিট করার দরকার নেই, তাই Arrange ব্লকে নতুন কোনো Person তৈরি করারও প্রয়োজন নেই।
[Fact]
public void DeletePerson_InvalidPersonID()
{
// Arrange: কোনো ডামি Person তৈরি করার দরকার নেই
// Act: সিস্টেমে নেই এমন একটি নতুন ফেক ID দিয়ে DeletePerson কল করা
bool isDeleted = _personsService.DeletePerson(Guid.NewGuid());
// Assert: চেক করা যে ডিলিট অপারেশন ব্যর্থ (false) হয়েছে কিনা
Assert.False(isDeleted);
}
#endregion
৪. TDD Verification (Priority: 5/10)
টেস্টগুলো লেখার পর রান করলে যথারীতি এগুলো ফেইল করে, কারণ PersonsService ক্লাসে DeletePerson মেথডের ভেতর এখনো throw new NotImplementedException(); লেখা আছে।
🚀 Modern C# (.NET 10 Update) & Smarter Approach
লেকচারের পদ্ধতিটি লজিক্যালি শতভাগ সঠিক। তবে ডিলিট অপারেশনের টেস্ট করার সময় আরও একটি গুরুত্বপূর্ণ ভেরিফিকেশন (Verification) করা বেস্ট প্র্যাকটিস, যা লেকচারার এখানে দেখাননি।
Verify Data is Actually Removed:
শুধু মেথডটি true রিটার্ন করলেই যে ডাটাবেস থেকে ডেটা রিমুভ হয়েছে, তা নিশ্চিত হওয়া যায় না। তাই DeletePerson কল করার পর GetPersonByPersonId কল করে চেক করা উচিত যে ডেটাটি আসলেই আর পাওয়া যাচ্ছে না (অর্থাৎ null রিটার্ন করছে)।
Smarter Deletion Test:
[Fact]
public void DeletePerson_ValidPersonID_ShouldRemoveFromDatabase()
{
// Arrange: Person তৈরি করা
var person = await CreateAndAddDummyPersonAsync();
// Act 1: ഡিলিট করা
bool isDeleted = _personsService.DeletePerson(person.PersonID);
// Act 2: ডাটাবেস থেকে আবার খোঁজার চেষ্টা করা
var deletedPerson = _personsService.GetPersonByPersonId(person.PersonID);
// Assert
Assert.True(isDeleted); // মেথড যেন true রিটার্ন করে
Assert.Null(deletedPerson); // ডাটাবেসে যেন ডেটা আর না থাকে
}
🏆 Best Practices (For Delete Operations)
- Soft Delete vs Hard Delete: লেকচারে আমরা যে পদ্ধতিটি ইমপ্লিমেন্ট করতে যাচ্ছি, তা হলো Hard Delete (লিস্ট বা ডাটাবেস থেকে একেবারে মুছে ফেলা)। রিয়েল-ওয়ার্ল্ড এন্টারপ্রাইজ প্রজেক্টে সাধারণত ডেটা একেবারে মুছে ফেলা হয় না, বরং একটি ফ্লাগ (Flag) যেমন
IsDeleted = trueসেট করা হয়। একে Soft Delete বলে। - Idempotent Deletion:
DeletePersonমেথডটি Idempotent হওয়া উচিত। এর মানে হলো, একই ইনভ্যালিড ID দিয়ে বারবার ডিলিট কল করলেও যেন সিস্টেম ক্র্যাশ না করে, বরং ফলস (false) রিটার্ন করে। আমাদের লেখা টেস্টটি (Test Case 2) সেই বিষয়টিই নিশ্চিত করছে। - Avoid Exception for Not Found: ডিলিট করার সময় যদি ডেটা না পাওয়া যায়, তখন Exception (যেমন
ArgumentException) থ্রো করার চেয়েfalseরিটার্ন করাটা API ডিজাইনের বেস্ট প্র্যাকটিস। লেকচারার ঠিক এই পদ্ধতিটিই ফলো করেছেন।