আমি আপনার এক্সপার্ট সফটওয়্যার ইঞ্জিনিয়ারিং ট্রেইনার। কোর্স আউটলাইন অনুযায়ী, আমরা এখন Section 15: xUnit testing-এর ভেতরে আছি। এই লেকচারটির টপিক হলো ITestOutputHelper, যা xUnit-এর একটি অত্যন্ত দরকারী টুল।

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

📝 Lecture Summary (Quick Revision)

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

  • The Problem: xUnit-এ যখন কোনো টেস্ট পাস (Pass) করে, তখন Test Explorer-এ সাধারণত কোনো Expected বা Actual ভ্যালু দেখায় না। এর ফলে ভেতরে কী ডেটা তৈরি হচ্ছে তা বোঝা যায় না।
  • The Solution (ITestOutputHelper): টেস্ট রান করার সময় Console-এর মতো কোনো ডেটা প্রিন্ট করে দেখার জন্য ITestOutputHelper ইন্টারফেস ব্যবহার করা হয়।
  • Dependency Injection: PersonsServiceTest ক্লাসের কনস্ট্রাক্টরে ITestOutputHelper ইনজেক্ট করা এবং প্রাইভেট ফিল্ডে সেভ করে রাখা।
  • Writing Output: GetAllPersons_AddFewPersons টেস্টের ভেতরে _testOutputHelper.WriteLine() ব্যবহার করে Expected এবং Actual লিস্ট প্রিন্ট করা।
  • Overriding ToString(): ডিফল্টভাবে ক্লাসের নাম প্রিন্ট হওয়া এড়ানোর জন্য PersonResponse DTO ক্লাসে ToString() মেথড ওভাররাইড করে কাস্টম ডেটা ফরম্যাট সেট করা।
  • Testing Result: Test Explorer-এ টেস্টের Details সামারিতে গিয়ে Expected এবং Actual অবজেক্টের বিস্তারিত ডেটা লগ হিসেবে দেখতে পাওয়া।

🧠 Comprehensive Breakdown

এই লেকচারে আমরা টেস্টের লগ বা আউটপুট কনসোলে প্রিন্ট করা শিখেছি, যা ডিবাগিংয়ের জন্য অত্যন্ত গুরুত্বপূর্ণ। চলুন প্রতিটি ধাপ বিস্তারিতভাবে বুঝে নিই।

১. ITestOutputHelper ইনজেক্ট করা (Priority: 9/10)

Concept & Why: সাধারণ .NET অ্যাপ্লিকেশনে আমরা কিছু প্রিন্ট করার জন্য Console.WriteLine() ব্যবহার করি। কিন্তু xUnit টেস্টগুলো কোনো কন্সোল অ্যাপ নয়, তাই Console.WriteLine() এখানে কাজ করে না।

xUnit-এ আউটপুট প্রিন্ট করার জন্য Xunit.Abstractions নেমস্পেসের ITestOutputHelper ব্যবহার করতে হয়। আমরা এটি PersonsServiceTest ক্লাসের কনস্ট্রাক্টরে Dependency Injection এর মাধ্যমে রিসিভ করে একটি প্রাইভেট ফিল্ডে রেখেছি।

using Xunit.Abstractions;
 
public class PersonsServiceTest
{
    private readonly IPersonsService _personsService;
    private readonly ICountriesService _countriesService;
    
    // ITestOutputHelper এর জন্য প্রাইভেট ফিল্ড
    private readonly ITestOutputHelper _testOutputHelper;
 
    // Constructor Injection
    public PersonsServiceTest(ITestOutputHelper testOutputHelper)
    {
        _personsService = new PersonsService();
        _countriesService = new CountriesService();
        
        _testOutputHelper = testOutputHelper; // ইনজেক্ট করা হলো
    }
}
 

২. Test-এর ভেতরে Output প্রিন্ট করা (Priority: 10/10)

Concept & Why: আমরা GetAllPersons_AddFewPersons টেস্টটিতে দেখতে চাইছিলাম যে AddPerson করার পর যে ডেটা জেনারেট হচ্ছে (Expected) এবং GetAllPersons কল করার পর যে ডেটা আসছে (Actual), তাদের ভেতরের ভ্যালুগুলো কেমন।

এজন্য টেস্টের ভেতরে আমরা _testOutputHelper.WriteLine() ব্যবহার করেছি।

// Expected List প্রিন্ট করা
_testOutputHelper.WriteLine("Expected:");
foreach (PersonResponse person_response_from_add in person_response_list_from_add)
{
    // অবজেক্টটিকে স্ট্রিং এ কনভার্ট করে প্রিন্ট করা হচ্ছে
    _testOutputHelper.WriteLine(person_response_from_add.ToString()); 
}
 
// Actual List প্রিন্ট করা
_testOutputHelper.WriteLine("Actual:");
foreach (PersonResponse person_response_from_get in persons_list_from_get)
{
    _testOutputHelper.WriteLine(person_response_from_get.ToString());
}
 

৩. The ToString() Override Problem and Solution (Priority: 10/10)

Concept & Why: কোডটি লিখে রান করার পর Test Explorer-এ একটি অদ্ভুত জিনিস দেখা যায়। আউটপুটে ডেটার বদলে শুধু ক্লাসের নাম (যেমন: ProjectName.DTO.PersonResponse) প্রিন্ট হচ্ছে।

কারণ: C#-এ যখন কোনো অবজেক্টের ওপর .ToString() কল করা হয়, তখন ডিফল্টভাবে এটি অবজেক্টের ভ্যালু নয়, বরং ওই ক্লাসের পাথ বা নেমস্পেস প্রিন্ট করে। সমাধান: আমাদের PersonResponse ক্লাসে গিয়ে এই ToString() মেথডটিকে Override করে বলে দিতে হবে আমরা ঠিক কী প্রিন্ট করতে চাই।

// DTO/PersonResponse.cs
 
public override string ToString()
{
    // String interpolation ব্যবহার করে সব প্রপার্টি রিটার্ন করা
    return $"Person ID: {PersonId}, Person Name: {PersonName}, Email: {Email}, " +
           $"Date of Birth: {DateOfBirth?.ToString("dd MMM yyyy")}, Gender: {Gender}, " +
           $"Country ID: {CountryId}, Country: {Country}, Address: {Address}, " +
           $"Receive News Letters: {ReceiveNewsLetters}";
}
 

(নোট: DateOfBirth যেহেতু Nullable (DateTime?), তাই ?. ব্যবহার করা হয়েছে, যেন null থাকলে ক্র্যাশ না করে)।

৪. Final Result Analysis (Priority: 8/10)

Concept: ToString() ওভাররাইড করার পর টেস্টটি পুনরায় রান করলে Test Explorer-এর “Standard Output” বা “Details” প্যানেলে সম্পূর্ণ Person-এর ডেটা সুন্দরভাবে প্রিন্ট হয়।

Why it’s useful: যখন কোনো টেস্ট ফেইল করে, তখন শুধুমাত্র “Test Failed” মেসেজ দেখে বোঝা যায় না সমস্যাটা কোথায়। এই ITestOutputHelper এর লগ দেখে আপনি সহজেই Expected এবং Actual ভ্যালুর মধ্যে ঠিক কোন ফিল্ডটি (যেমন, ID বা Date) মিলছে না, তা মিলিয়ে দেখে ডিবাগ করতে পারবেন।


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

লেকচারের পদ্ধতিটি সঠিক। তবে আপনি যদি C# 9 বা তার উপরের ভার্সনে কাজ করেন এবং PersonResponse কে class এর বদলে record হিসেবে ডিক্লেয়ার করেন, তবে আপনার জীবন অনেক সহজ হয়ে যাবে!

The Magic of record: C#-এ record ডিফল্টভাবেই ToString() মেথডটিকে এমনভাবে ওভাররাইড করে রাখে যে, এটি কল করলে অটোমেটিকভাবে ক্লাসের সব প্রপার্টি এবং তাদের ভ্যালু একটি সুন্দর ফরমেটে প্রিন্ট হয়। আপনাকে ম্যানুয়ালি ১০০ লাইনের ToString() লিখতে হবে না!

// যদি এটি একটি record হয়
public record PersonResponse { ... }
 
// তাহলে Test এ সরাসরি WriteLine কাজ করবে, কোনো কাস্টম ToString() লেখার প্রয়োজন নেই!
_testOutputHelper.WriteLine(person_response.ToString());
// Output by default: PersonResponse { PersonId = 123..., PersonName = Smith, ... }
 

JSON Output Alternative: অনেক সময় কনসোলে স্ট্রিং পড়ার চেয়ে JSON পড়া ডেভেলপারদের জন্য বেশি সহজ হয়। আপনি চাইলে ToString() ওভাররাইড না করে অবজেক্টটিকে সরাসরি JSON এ সিরিয়ালাইজ করে প্রিন্ট করতে পারেন:

// Modern Debugging approach using System.Text.Json
_testOutputHelper.WriteLine(System.Text.Json.JsonSerializer.Serialize(person_response_list_from_add));
 

🏆 Best Practices (For xUnit Logging)

  1. Don’t use Console.WriteLine in xUnit: xUnit টেস্টে লগ করার একমাত্র সঠিক উপায় হলো ITestOutputHelper
  2. Use Records for DTOs: DTO ক্লাসের ক্ষেত্রে সব সময় record ব্যবহার করুন, যাতে ToString(), Equals() এবং GetHashCode() এর মতো কাজগুলো C# কম্পাইলার আপনার হয়ে নিজে থেকে করে দেয়।
  3. Clean Up Logs: প্রোডাকশনে কোড পুশ করার আগে টেস্টের ভেতরের অপ্রয়োজনীয় WriteLine মুছে ফেলা বা কমেন্ট করে রাখা ভালো প্র্যাকটিস, কারণ খুব বেশি আউটপুট টেস্ট রানটাইমের পারফরম্যান্স কিছুটা কমিয়ে দিতে পারে।