হ্যালো! আমি তোমার Simple Coding Tutor। আজ আমরা Section 15-এর অত্যন্ত গুরুত্বপূর্ণ লেকচার “Add Country - xUnit Test - Part 3” নিয়ে আলোচনা করব। এই লেকচারে আমরা TDD (Test-Driven Development) পদ্ধতি অনুসরণ করে AddCountry মেথডের জন্য ৪টি ভিন্ন টেস্ট কেস (Test Cases) কোড করব এবং টেস্ট রানার দিয়ে সেগুলো রান করে দেখব।

চলো, প্রথমে পুরো লেকচারের একটি সংক্ষিপ্ত সামারি দেখে নিই।


📝 লেকচার সামারি (Summary for Quick Revision)

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

  • Project Reference Setup: Test প্রজেক্টে Services প্রজেক্টের রেফারেন্স যুক্ত করা হয়েছে, যার ফলে টেস্ট প্রজেক্ট এখন তিনটি প্রজেক্টকে (Entities, ServiceContracts, Services) চেনে।
  • Four Core Test Cases: AddCountry মেথডের জন্য ৪টি টেস্ট রিকোয়ারমেন্ট ডিফাইন করা হয়েছে:
  1. CountryAddRequest অবজেক্টটি null হলে ArgumentNullException থ্রো করবে।
  2. CountryName প্রপার্টিটি null হলে ArgumentException থ্রো করবে।
  3. CountryName ডুপ্লিকেট (আগে থেকেই লিস্টে থাকলে) হলে ArgumentException থ্রো করবে।
  4. সঠিক CountryName দিলে ডেটা সফলভাবে সেভ হবে এবং একটি ভ্যালিড, অ-শূন্য (Non-empty) GUID জেনারেট হয়ে CountryResponse রিটার্ন করবে।
  • xUnit Features Used: টেস্ট মেথডগুলোকে চিহ্নিত করতে [Fact] attribute, এক্সেপশন টেস্ট করার জন্য Assert.Throws, এবং কন্ডিশন চেক করার জন্য Assert.True ব্যবহার করা হয়েছে।
  • The “Red” Phase: সব টেস্ট রান করার পর সবগুলোই ফেইল (Fail) করেছে এবং NotImplementedException দেখিয়েছে, যা TDD সাইকেলের প্রথম এবং স্বাভাবিক ধাপ।

🧠 Comprehensive Breakdown (বিস্তারিত আলোচনা)

১. Test Project-এ Services-এর Reference যুক্ত করা [Priority: 8/10]

টেস্ট প্রজেক্ট থেকে আসল সার্ভিস ক্লাসকে টেস্ট করার জন্য Services প্রজেক্টের রেফারেন্স প্রয়োজন।

  • কেন প্রয়োজন? আগের লেকচারে আমরা টেস্ট ক্লাসে ICountryService ইন্টারফেস ডিক্লেয়ার করেছিলাম। কিন্তু ইন্টারফেসের তো নিজস্ব কোনো কার্যকারিতা নেই। তার জন্য আসল ক্লাস CountriesService-এর অবজেক্ট তৈরি করতে হবে।
  • কিভাবে করা হয়েছে: Test প্রজেক্টের Dependencies-এ Right-click -> Add Project Reference -> Services প্রজেক্ট সিলেক্ট করে OK করা হয়েছে। এখন টেস্ট প্রজেক্টের কনস্ট্রাক্টরে আমরা নিচের কোডটি লিখতে পারি:
private readonly ICountryService _countryService;
 
public CountriesServiceTest()
{
    // আসল সার্ভিস ক্লাসের অবজেক্ট তৈরি করা হলো
    _countryService = new CountriesService(); 
}
 

২. টেস্ট কেস ১: Null Request হ্যান্ডলিং [Priority: 9/10]

ইউজার বা কন্ট্রোলার যদি ভুল করে কোনো ডেটা না পাঠিয়ে সম্পূর্ণ null অবজেক্ট সার্ভিসে পাঠায়, তবে সিস্টেমকে ক্র্যাশ না করে একটি নির্দিষ্ট এক্সেপশন থ্রো করতে হবে।

  • কেন? C#-এ এটি একটি স্ট্যান্ডার্ড প্র্যাকটিস যে মেথডের রিকোয়ার্ড আর্গুমেন্ট null হলে ArgumentNullException থ্রো করা হয়।
  • কোড ইমপ্লিমেন্টেশন:
[Fact]
public void AddCountry_NullCountry()
{
    // Arrange
    CountryAddRequest? request = null;
 
    // Act & Assert (একত্রে ল্যাম্বডা এক্সপ্রেশনের মাধ্যমে)
    Assert.Throws<ArgumentNullException>(() => 
    {
        _countryService.AddCountry(request);
    });
}
 

৩. টেস্ট কেস ২: Null Country Name হ্যান্ডলিং [Priority: 9/10]

হতে পারে আর্গুমেন্ট অবজেক্টটি null নয়, কিন্তু তার ভেতরের CountryName প্রপার্টিটি null বা খালি।

  • কেন? দেশের নাম ছাড়া কোনো দেশের এন্ট্রি ডেটাবেসে হওয়া সম্ভব নয়। তাই এক্ষেত্রে ArgumentException থ্রো করা উচিত।
  • কোড ইমপ্লিমেন্টেশন:
[Fact]
public void AddCountry_CountryNameIsNull()
{
    // Arrange
    CountryAddRequest request = new CountryAddRequest() { CountryName = null };
 
    // Act & Assert
    Assert.Throws<ArgumentException>(() =>
    {
        _countryService.AddCountry(request);
    });
}
 

৪. টেস্ট কেস ৩: ডুপ্লিকেট দেশের নাম প্রতিরোধ করা [Priority: 9/10]

একই নামের দেশ ডেটাবেসে একাধিকবার যুক্ত হতে দেওয়া যাবে না। যেমন- লিস্টে একবার “USA” থাকলে দ্বিতীয়বার “USA” অ্যাড করতে গেলে সিস্টেম এরর দেবে।

  • কেন? ডেটার ইউনিকনেস (Uniqueness) বজায় রাখার জন্য এটি অত্যন্ত জরুরি।
  • কোড ইমপ্লিমেন্টেশন:
[Fact]
public void AddCountry_DuplicateCountryName()
{
    // Arrange
    CountryAddRequest request1 = new CountryAddRequest() { CountryName = "USA" };
    CountryAddRequest request2 = new CountryAddRequest() { CountryName = "USA" };
 
    // Act
    _countryService.AddCountry(request1); // প্রথমবার সফলভাবে অ্যাড হওয়া উচিত (ধরে নেওয়া হচ্ছে)
 
    // Assert (দ্বিতীয়বার একই নাম দিলে এক্সেপশন আশা করছি)
    Assert.Throws<ArgumentException>(() =>
    {
        _countryService.AddCountry(request2);
    });
}
 

৫. টেস্ট কেস ৪: সঠিক ডেটা ইনপুট (Success Path) [Priority: 10/10]

সব ভ্যালিডেশন পার হয়ে যখন সঠিক দেশের নাম (যেমন- “Japan”) দেওয়া হবে, তখন সেটি সফলভাবে সিস্টেমে যুক্ত হতে হবে।

  • কেন? এটি হলো আমাদের মেথডের মূল বা হ্যাপি পাথ (Happy Path)। টেস্ট করতে হবে যে মেথডটি যুক্ত করার পর একটি নতুন Guid আইডি তৈরি করছে কি না এবং তা CountryResponse আকারে ফেরত দিচ্ছে কি না।
  • কোড ইমপ্লিমেন্টেশন:
[Fact]
public void AddCountry_ProperCountryDetails()
{
    // Arrange
    CountryAddRequest request = new CountryAddRequest() { CountryName = "Japan" };
 
    // Act
    CountryResponse response = _countryService.AddCountry(request);
 
    // Assert (চেক করছি আইডিটি জেনারেট হয়েছে কি না এবং তা খালি কি না)
    Assert.True(response.CountryId != Guid.Empty);
}
 

৬. টেস্ট রান করা এবং TDD-এর “Red” ফেজ [Priority: 8/10]

সবগুলো টেস্ট মেথডের উপরে [Fact] অ্যাট্রিবিউট দেওয়ার পর সেগুলো Visual Studio-র Test Explorer-এ দৃশ্যমান হয়।

  • ফলাফল: সবগুলো টেস্ট রান করার পর প্রতিটি টেস্ট ফেইল (Fail) করেছে।
  • কারণ: আমাদের CountriesService ক্লাসের মেথডে এখনো কোনো আসল কোড লেখা নেই, সেখানে শুধু throw new NotImplementedException() লেখা আছে।
  • TDD ফিলোসফি: এটিই হলো TDD-এর সৌন্দর্য (Red-Green-Refactor সাইকেল)। প্রথমে আমরা টেস্ট লিখে নিশ্চিত হলাম যে আমাদের রিকোয়ারমেন্টগুলো কী কী। পরের লেকচারে আমরা সার্ভিস ক্লাসে এমনভাবে কোড লিখব যেন এই লাল (Failed) টেস্টগুলো সব সবুজ (Passed) হয়ে যায়।

⌨️ IDE Shortcut Tricks:

  • Visual Studio: Test Explorer উইন্ডোটি ওপেন করার শর্টকাট হলো Ctrl + E, T। সবগুলো টেস্ট একসাথে রান করার শর্টকাট হলো Ctrl + R, A
  • Visual Studio Code (VS Code): VS Code-এ টেস্ট রান করার জন্য প্রথমে Testing সাইডবার আইকনে ক্লিক করতে পারো অথবা Ctrl + Shift + P চেপে Test: Run All Tests কমান্ডটি এক্সিকিউট করতে পারো। কুইক অ্যাকশন বা মিসিং নেমস্পেস ইমপোর্ট (using xunit;) করার শর্টকাট হলো Ctrl + . (Windows) বা Cmd + . (Mac)।

💡 Best Practices & .NET 10 Updates

Best Practices (উদাহরণসহ):

  1. Unit Test Naming Convention: টেস্ট মেথডের নাম সবসময় স্পষ্ট হতে হবে। AddCountry_NullCountry বা AddCountry_ProperCountryDetails দেখেই বোঝা যাচ্ছে কোন মেথডের কোন স্টেট টেস্ট করা হচ্ছে। কখনো TestMethod1 বা Test1 নাম দেবে না।
  2. Isolate Assertions: টেস্ট কেস ৪-এ আমরা চেক করেছি response.CountryId != Guid.Empty। সাকসেস পাথে অবজেক্টটি নিজেই null কি না তাও চেক করা ভালো প্র্যাকটিস।

.NET 10 Context (Modern Testing using Fluent Assertions):

বর্তমানে .NET 10 প্রজেক্টগুলোতে xUnit-এর ডিফল্ট Assert.True বা Assert.Throws এর চেয়ে FluentAssertions নামক একটি নুগেট প্যাকেজ ব্যাপক জনপ্রিয়। এটি কোডকে মানুষের পড়ার ভাষার মতো (Human-readable) সহজ করে তোলে।

যেমন, .NET 10-এ Fluent Assertions ব্যবহার করে সাকসেস পাথের টেস্টটি আরও মডার্নভাবে এভাবে লেখা যায়:

// .NET 10 Modern Unit Testing Style with FluentAssertions
[Fact]
public void AddCountry_ProperCountryDetails_Modern()
{
    // Arrange
    CountryAddRequest request = new() { CountryName = "Japan" };
 
    // Act
    CountryResponse response = _countryService.AddCountry(request);
 
    // Fluent Assertions style (Much more readable!)
    response.Should().NotBeNull();
    response.CountryId.Should().NotBeEmpty();
    response.CountryName.Should().Be("Japan");
}