আমি আপনার এক্সপার্ট সফটওয়্যার ইঞ্জিনিয়ারিং ট্রেইনার। কোর্স আউটলাইন অনুযায়ী আমরা এখন Section 15: xUnit testing-এর ভেতরে আছি। আগের লেকচারগুলোতে আমরা AddPerson এবং GetPersonByPersonId এর টেস্ট ও ইমপ্লিমেন্টেশন শেষ করেছি।

এই লেকচারে আমরা TDD (Test Driven Development) অ্যাপ্রোচ ফলো করে GetAllPersons মেথডের জন্য Unit Test লেখা শিখব, যার ইমপ্লিমেন্টেশন আমরা আগামী লেকচারে করব।

চলুন, আজকের লেকচারটি গুছিয়ে ও বিস্তারিতভাবে বুঝে নেওয়া যাক।

📝 Lecture Summary (Quick Revision)

ভবিষ্যতে দ্রুত রিভিশন দেওয়ার জন্য সম্পূর্ণ লেকচারের মূল কাজগুলো নিচে লিস্ট আকারে দেওয়া হলো:

  • Region Creation: GetAllPersons এর টেস্টগুলো অর্গানাইজ করার জন্য C# এ নতুন একটি #region তৈরি করা।

  • Test Case 1 (Empty List): সিস্টেমে কোনো Person অ্যাড করার আগে GetAllPersons কল করলে যেন একটি ফাঁকা (Empty) লিস্ট রিটার্ন করে, তা Assert.Empty() দিয়ে চেক করা।

  • Test Case 2 (Add Few Persons): * প্রথমে কয়েকটি Country তৈরি করা এবং তাদের ID সংগ্রহ করা।

  • এরপর ৩টি PersonAddRequest ডামি ডেটা তৈরি করা।

  • foreach লুপ চালিয়ে ডেটাগুলো AddPerson দিয়ে ডাটাবেসে (লিস্টে) ইনসার্ট করা।

  • এরপর GetAllPersons কল করে চেক করা যে, ইনসার্ট করা প্রতিটি Person নতুন ফিরে আসা লিস্টে আছে কিনা (Assert.Contains ব্যবহার করে)।

  • TDD Verification: টেস্টগুলো রান করে দেখা যে সবগুলো ফেইল করছে, কারণ GetAllPersons মেথডে এখনো NotImplementedException থ্রো করা আছে।


🧠 Comprehensive Breakdown

এই লেকচারে আমরা GetAllPersons মেথডের জন্য দুটি ভিন্ন ভিন্ন সিনারিও টেস্ট করেছি। চলুন প্রতিটি ধাপ বিস্তারিতভাবে বুঝে নিই।

১. Unit Test 1: Empty List Check (Priority: 9/10)

Concept & Why: xUnit-এ প্রতিটি টেস্ট সম্পূর্ণ স্বাধীনভাবে রান করে (Test Isolation)। তাই নতুন টেস্ট শুরু করার সময় সিস্টেমের লিস্ট সম্পূর্ণ ফাঁকা থাকার কথা। যদি আমরা কোনো ডেটা ইনসার্ট না করেই ডেটা দেখতে চাই, তবে আমাদের একটি এম্পটি (Empty) বা ফাঁকা লিস্ট পাওয়ার কথা, null নয়।

#region GetAllPersons
 
[Fact]
public void GetAllPersons_EmptyList()
{
    // Act: সরাসরি মেথড কল করা হচ্ছে (Arrange এর প্রয়োজন নেই)
    List<PersonResponse> persons_from_get = _personsService.GetAllPersons();
 
    // Assert: চেক করা হচ্ছে লিস্টটি ফাঁকা কিনা
    Assert.Empty(persons_from_get);
}
 

২. Unit Test 2: Add Few Persons and Retrieve (Priority: 10/10)

Concept & Why: এটি একটি লার্জার ইন্টিগ্রেশন স্টাইলের টেস্ট। এর মূল উদ্দেশ্য হলো— যদি আমরা সিস্টেমে ৩ জন Person অ্যাড করি, তবে GetAllPersons কল করলে ঠিক সেই ৩ জনকেই আমাদের ফেরত পেতে হবে।

যেহেতু Person অ্যাড করার জন্য Country ID ম্যান্ডেটরি (Foreign Key), তাই আমাদের প্রথমে কিছু Country অ্যাড করে নিতে হবে।

Step 2.1: Arrange - Create Countries

[Fact]
public void GetAllPersons_AddFewPersons()
{
    // Arrange: ২ টি দেশ তৈরি করা
    CountryAddRequest country_request_1 = new CountryAddRequest() { CountryName = "USA" };
    CountryAddRequest country_request_2 = new CountryAddRequest() { CountryName = "India" };
 
    CountryResponse country_response_1 = _countriesService.AddCountry(country_request_1);
    CountryResponse country_response_2 = _countriesService.AddCountry(country_request_2);
 

Step 2.2: Arrange - Create Persons in a List এরপর আমরা ৩টি PersonAddRequest তৈরি করে একটি লিস্টে রাখব।

    // Arrange: ৩ জন Person এর রিকোয়েস্ট ডেটা তৈরি করা
    PersonAddRequest person_request_1 = new PersonAddRequest() { 
        PersonName = "Smith", Email = "smith@example.com", Gender = GenderOptions.Male, Address = "Address of Smith", CountryId = country_response_1.CountryId, DateOfBirth = DateTime.Parse("2002-05-06"), ReceiveNewsLetters = true 
    };
 
    PersonAddRequest person_request_2 = new PersonAddRequest() { 
        PersonName = "Mary", Email = "mary@example.com", Gender = GenderOptions.Female, Address = "Address of Mary", CountryId = country_response_2.CountryId, DateOfBirth = DateTime.Parse("2000-02-02"), ReceiveNewsLetters = false 
    };
 
    PersonAddRequest person_request_3 = new PersonAddRequest() { 
        PersonName = "Rahman", Email = "rahman@example.com", Gender = GenderOptions.Male, Address = "Address of Rahman", CountryId = country_response_2.CountryId, DateOfBirth = DateTime.Parse("1999-03-03"), ReceiveNewsLetters = true 
    };
 
    List<PersonAddRequest> person_requests = new List<PersonAddRequest>() { person_request_1, person_request_2, person_request_3 };
 

Step 2.3: Act - Insert Data and Retrieve All এখন আমরা foreach লুপ চালিয়ে ডেটাগুলো ডাটাবেসে ইনসার্ট করব এবং যা রেসপন্স আসবে তা person_response_list_from_add নামের একটি নতুন লিস্টে জমিয়ে রাখব। এরপর GetAllPersons কল করব।

    List<PersonResponse> person_response_list_from_add = new List<PersonResponse>();
 
    // লুপ চালিয়ে ৩ জনকেই সিস্টেমে অ্যাড করা হচ্ছে
    foreach (PersonAddRequest person_request in person_requests)
    {
        PersonResponse person_response = _personsService.AddPerson(person_request);
        person_response_list_from_add.Add(person_response);
    }
 
    // Act: সিস্টেমে থাকা সব Person কে কল করা হচ্ছে
    List<PersonResponse> persons_list_from_get = _personsService.GetAllPersons();
 

Step 2.4: Assert - Verification সবশেষে আমরা চেক করব যে, আমরা যে ৩ জনকে অ্যাড করেছিলাম (Expected), তারা GetAllPersons থেকে ফিরে আসা লিস্টে (Actual) উপস্থিত আছে কিনা।

    // Assert: যাচাই করা হচ্ছে
    foreach (PersonResponse person_response_from_add in person_response_list_from_add)
    {
        Assert.Contains(person_response_from_add, persons_list_from_get);
    }
}
#endregion
 

(নোট: Assert.Contains ইন্টারনালি Equals মেথড কল করে। যেহেতু আমরা আগের লেকচারগুলোতে PersonResponse ক্লাসে Equals মেথড ওভাররাইড করিনি (আমরা শুধু Country-তে করেছিলাম), তাই এটি কাজ করার জন্য আপনাকে PersonResponse ক্লাসে Equals মেথড ওভাররাইড করতে হবে, অথবা আধুনিক C#-এর Assert.Equivalent ব্যবহার করতে হবে।)

৩. TDD Verification (Priority: 5/10)

টেস্ট লেখা শেষে রান করলে দেখা যাবে যে এই দুটি টেস্টই ফেইল করেছে, কারণ আমাদের PersonsService ক্লাসে GetAllPersons মেথডের ভেতরে এখনো throw new NotImplementedException(); লেখা আছে।


🚀 Modern C# (.NET 10 Update) & Smarter Approach

লেকচারের পদ্ধতিটি লজিক্যালি শতভাগ সঠিক। তবে আধুনিক C# (C# 9 - 12+) ব্যবহার করে আমরা অনেক বড় বড় কোড ব্লক ছোট করে ফেলতে পারি।

1. Target-typed new and Collection Expressions (C# 12+): আপনি যখন List তৈরি করেন, তখন এত লম্বা লেখা না লিখে শুধু [] ব্যবহার করতে পারেন।

// Old
List<PersonAddRequest> person_requests = new List<PersonAddRequest>() { req1, req2, req3 };
 
// Modern (C# 12+)
List<PersonAddRequest> person_requests = [req1, req2, req3];
 

2. LINQ Select instead of foreach loop: ডেটা ইনসার্ট করার জন্য ম্যানুয়ালি লিস্ট ডিক্লেয়ার করে foreach লুপ চালানোর চেয়ে LINQ এর Select মেথড ব্যবহার করা অনেক বেশি স্মার্ট এবং পারফরম্যান্স-বান্ধব।

// Modern, One-liner Insertion (Replaces 7 lines of foreach loop code)
List<PersonResponse> person_response_list_from_add = person_requests
    .Select(req => _personsService.AddPerson(req))
    .ToList();
 

🏆 Best Practices (For xUnit & Bulk Data Setup)

  1. Avoid Hardcoding Test Data: যখন আপনি অনেকগুলো ডামি ডেটা তৈরি করেন, তখন বারবার new PersonAddRequest() লেখাটা কোডকে অনেক বড় করে ফেলে। রিয়েল-ওয়ার্ল্ড প্রজেক্টে এই কাজের জন্য AutoFixture বা Bogus এর মতো লাইব্রেরি ব্যবহার করা হয়, যা স্বয়ংক্রিয়ভাবে হাজার হাজার ফেইক (Fake) ডেটা জেনারেট করে দেয়। (কোর্সের পরবর্তী সেকশনে হয়তো AutoFixture দেখানো হবে)।
  2. Assert.Equivalent over Manual Loop: foreach চালিয়ে Assert.Contains কল না করে, আপনি যদি চান দুটি লিস্ট হুবহু একই রকম (ডেটার দিক থেকে), তাহলে আধুনিক xUnit-এ আপনি সরাসরি Assert.Equivalent(expectedList, actualList) ব্যবহার করতে পারেন।