আমি আপনার এক্সপার্ট সফটওয়্যার ইঞ্জিনিয়ারিং ট্রেইনার। কোর্স আউটলাইন অনুযায়ী, আমরা এখন Section 15: xUnit testing-এর ভেতরে আছি। এই লেকচারে আমরা TDD (Test Driven Development) অ্যাপ্রোচ ফলো করে GetSortedPersons মেথডটির জন্য Unit Test লেখা শিখব। এর ইমপ্লিমেন্টেশন আমরা আগামী লেকচারে করব।
চলুন, আজকের লেকচারটি বিস্তারিতভাবে বুঝে নেওয়া যাক।
📝 Lecture Summary (Quick Revision)
ভবিষ্যতে দ্রুত রিভিশন দেওয়ার জন্য সম্পূর্ণ লেকচারের মূল কাজগুলো নিচে লিস্ট আকারে দেওয়া হলো:
- Enumeration (
SortOrderOptions):ServiceContracts/Enumsফোল্ডারেAscendingএবংDescendingঅপশন দিয়ে একটিenumতৈরি করা। - Interface Setup:
IPersonsServiceইন্টারফেসেGetSortedPersonsমেথড ডিক্লেয়ার করা, যা ৩টি প্যারামিটার নিবে:allPersons(অসর্টেড লিস্ট),sortBy(প্রপার্টির নাম) এবংsortOrder(enum)। - Dummy Implementation:
PersonsServiceক্লাসে মেথডটির ইনিশিয়াল বা ডামি ইমপ্লিমেন্টেশন করা (যেখানেNotImplementedExceptionথ্রো করা হয়)। - Test Case Setup: আগের লেকচারগুলোর মতো Country এবং Person তৈরি করে
AddPersonএর মাধ্যমে ইনসার্ট করা। - Expected Result Preparation: ইনসার্ট করা ডেটাগুলোর লিস্টকে টেস্টের ভেতরেই LINQ-এর
OrderByDescending()দিয়ে সর্ট করে Expected লিস্ট তৈরি করা। - Act & Assert:
GetSortedPersonsমেথড কল করে Actual লিস্ট পাওয়া এবংforলুপ চালিয়ে Expected লিস্টের প্রতিটি ইনডেক্সের সাথে Actual লিস্টের একই ইনডেক্সের অবজেক্ট হুবহু মিলে কিনা তা চেক করা (Assert.Equalব্যবহার করে)। - TDD Verification: টেস্ট রান করে দেখা যে সেটি ফেইল করছে, কারণ আসল লজিক এখনো লেখা হয়নি।
🧠 Comprehensive Breakdown
এই লেকচারে আমরা সর্টিং বা ক্রমানুসারে সাজানোর ফাংশনালিটির জন্য টেস্ট লিখেছি। চলুন প্রতিটি ধাপ বিস্তারিতভাবে বুঝে নিই।
১. SortOrderOptions Enum তৈরি করা (Priority: 9/10)
Concept & Why: ডাটাবেস বা লিস্ট থেকে ডেটা সর্ট করার সময় সাধারণত দুটি অপশন থাকে: Ascending (A-Z বা ছোট থেকে বড়) এবং Descending (Z-A বা বড় থেকে ছোট)। হার্ডকোডেড স্ট্রিং ব্যবহারের চেয়ে enum ব্যবহার করা অনেক বেশি টাইপ-সেফ (Type-safe)।
// ServiceContracts/Enums/SortOrderOptions.cs
public enum SortOrderOptions
{
Ascending,
Descending
}
২. Interface-এ Method ডিক্লেয়ারেশন (Priority: 8/10)
Concept & Why: এই মেথডটি আগের মেথডগুলোর চেয়ে একটু আলাদা। এটি সরাসরি ডাটাবেস থেকে ডেটা না নিয়ে, ইনপুট হিসেবে একটি লিস্ট (allPersons) রিসিভ করে এবং সেটিকে সর্ট করে রিটার্ন করে। এটি করার কারণ হলো, আমরা সাধারণত প্রথমে ডেটা ফিল্টার (Search) করি, তারপর সেই ফিল্টার হওয়া ডেটার ওপর সর্টিং অ্যাপ্লাই করি।
// ServiceContracts/IPersonsService.cs
/// <summary>
/// Returns sorted list of persons
/// </summary>
/// <param name="allPersons">Represents list of persons to sort</param>
/// <param name="sortBy">Name of the property (key) based on which the persons should be sorted</param>
/// <param name="sortOrder">ASC or DESC</param>
/// <returns>Returns sorted persons as PersonResponse list</returns>
List<PersonResponse> GetSortedPersons(List<PersonResponse> allPersons, string sortBy, SortOrderOptions sortOrder);
৩. Unit Test: Sorting By PersonName Descending (Priority: 10/10)
Concept & Why: এটি হলো আসল টেস্ট। এখানে আমরা চেক করব যে, যদি আমরা PersonName ধরে Descending অর্ডারে সর্ট করতে বলি, তবে সিস্টেম আসলেই সর্ট করতে পারছে কিনা এবং প্রতিটি ইলিমেন্ট ঠিক ইনডেক্সে (Position) বসছে কিনা।
[Fact]
public void GetSortedPersons_ToBeSuccessful()
{
// Arrange 1: Data Setup (আগের মতই Country এবং 3 Person অ্যাড করা হলো)
// person_response_list_from_add নামের লিস্টে ইনসার্ট করা ডেটাগুলো আছে।
// Arrange 2: Get All Persons
List<PersonResponse> allPersons = _personsService.GetAllPersons();
// Act: মেথডটি কল করে Actual সর্টেড ডেটা নিয়ে আসা
List<PersonResponse> persons_list_from_sort = _personsService.GetSortedPersons(
allPersons,
nameof(Person.PersonName),
SortOrderOptions.Descending
);
// Arrange 3: Expected ডেটা তৈরি করা (LINQ ব্যবহার করে আমরা নিজেরা সর্ট করে নিচ্ছি)
person_response_list_from_add = person_response_list_from_add
.OrderByDescending(temp => temp.PersonName).ToList();
// Assert: লুপ চালিয়ে ইনডেক্স ধরে ধরে চেক করা
for (int i = 0; i < person_response_list_from_add.Count; i++)
{
// Expected[i] এর সাথে Actual[i] হুবহু মিলতে হবে
Assert.Equal(person_response_list_from_add[i], persons_list_from_sort[i]);
}
}
Why a for loop? আগের টেস্টগুলোতে আমরা foreach লুপ আর Assert.Contains ব্যবহার করেছিলাম, কারণ তখন শুধু ডেটা থাকলেই হতো, অর্ডার ম্যাটার করত না। কিন্তু সর্টিংয়ের ক্ষেত্রে Order (ক্রম) অত্যন্ত জরুরি। ১ নম্বর ইনডেক্সের ডেটা ১ নম্বরেই থাকতে হবে। তাই আমরা for লুপ দিয়ে [i] ইনডেক্স ধরে Assert.Equal করেছি।
৪. TDD Verification (Priority: 5/10)
টেস্টটি লেখার পর রান করলে যথারীতি এটি ফেইল করে, কারণ PersonsService ক্লাসে GetSortedPersons মেথডের ভেতর এখনো throw new NotImplementedException(); লেখা আছে।
🚀 Modern C# (.NET 10 Update) & Smarter Approach
লেকচারের পদ্ধতিটি লজিক্যালি শতভাগ সঠিক। তবে আধুনিক xUnit এবং C# ফিচার ব্যবহার করে আমরা for লুপ লেখা এড়িয়ে যেতে পারি।
1. Fluent Assertions for Collection Equality:
যখন আমরা দুটি লিস্টের ভেতরের ডেটা এবং তাদের ক্রমানুসারে (Order) মিল আছে কিনা তা চেক করতে চাই, তখন for লুপ লিখে Assert.Equal করার চেয়ে xUnit এর মডার্ন অ্যাপ্রোচ অনেক ক্লিন।
// Modern xUnit approach (No for loop needed!)
Assert.Equal(person_response_list_from_add, persons_list_from_sort);
xUnit এর Assert.Equal যখন দুটি IEnumerable বা List এর ওপর কল করা হয়, তখন এটি স্বয়ংক্রিয়ভাবে ইনডেক্স ধরে ধরে চেক করে (Order matters)। তাই আপনার আলাদা করে লুপ লেখার কোনো প্রয়োজন নেই!
2. Avoid Code Duplication (Helper Methods):
লেকচারার বারবার বলেছেন, “To avoid the repetition, you can keep the persons creation code in a common reusable method”। রিয়েল-ওয়ার্ল্ড প্রজেক্টে সব টেস্টের আগে এই ডামি ডেটা তৈরি করার জন্য আমরা Test Class-এর ভেতরে একটি প্রাইভেট মেথড বা xUnit-এর IClassFixture ব্যবহার করি।
🏆 Best Practices (For Sorting Tests)
- Test Multiple Scenarios: লেকচারে শুধু Descending অর্ডারের টেস্ট দেখানো হয়েছে। প্রফেশনাল প্রজেক্টে Ascending অর্ডারের জন্যও আলাদা একটি টেস্ট লিখতে হবে।
- Test Multiple Columns: শুধু
PersonNameনয়,DateOfBirthবাEmailদিয়ে সর্ট করলেও ঠিকমতো কাজ করে কিনা, তার জন্য[Theory]এবং[InlineData]ব্যবহার করে ডাটা-ড্রিভেন টেস্ট (Data-driven testing) করা বেস্ট প্র্যাকটিস (যা হয়তো কোর্সের পরের দিকে দেখানো হতে পারে)। - Use Immutable Variables: টেস্টের ভেতরে একই ভ্যারিয়েবলের ভ্যালু বারবার পরিবর্তন (Mutate) করা থেকে বিরত থাকা উচিত। যেমন
person_response_list_from_addকে আবার সর্ট করে একই ভ্যারিয়েবলে না রেখেexpected_sorted_listনামের নতুন একটি ভ্যারিয়েবলে রাখা উচিত, এতে কনফিউশন কম হয়।