আমি আপনার এক্সপার্ট সফটওয়্যার ইঞ্জিনিয়ারিং ট্রেইনার। কোর্স আউটলাইন অনুযায়ী, আমরা এখন Section 15: xUnit testing-এর ভেতরে আছি। আগের লেকচারে আমরা UpdatePerson মেথডের জন্য Unit Test লিখেছিলাম। এই লেকচারে আমরা মেথডটির আসল লজিক (Implementation) লিখব, যাতে টেস্টগুলো পাস করে এবং সেই সাথে একটি টেস্ট ফেইল করার কারণ খুঁজে বের করে তা ফিক্স করব (Debugging)।

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

📝 Lecture Summary (Quick Revision)

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

  • Initial Null Check: ইনপুট PersonUpdateRequest অবজেক্টটি null কিনা তা চেক করা।
  • Model Validation: আগের তৈরি করা ValidationHelper.ModelValidation() ব্যবহার করে ইনপুট অবজেক্টের Data Annotations (যেমন: [Required], [EmailAddress]) ভ্যালিডেট করা।
  • Fetch Matching Person: ডাটাবেস (লিস্ট) থেকে FirstOrDefault() ব্যবহার করে নির্দিষ্ট PersonID এর ইউজারকে খুঁজে বের করা এবং না পেলে ArgumentException থ্রো করা।
  • Update Properties: খুঁজে পাওয়া matchingPerson অবজেক্টের প্রপার্টিগুলো (Name, Email, etc.) নতুন রিকোয়েস্টের ডেটা দিয়ে ওভাররাইট করা (ID বাদে)।
  • Return Updated Object: আপডেট হওয়া Person অবজেক্টটিকে ConvertPersonToPersonResponse() হেল্পারের মাধ্যমে DTO-তে কনভার্ট করে রিটার্ন করা।
  • Test Debugging: টেস্ট রান করার পর “PersonNameIsNull” টেস্টটি ফেইল করে। এর কারণ ছিল Email এবং Gender প্রপার্টিগুলো আমরা টেস্ট ডেটায় (Arrange-এ) ফাঁকা রেখেছিলাম, যা Validation এবং Enum Parse এর সময় এরর দিচ্ছিল। পরে সেগুলো ডামি ডেটা দিয়ে ফিক্স করা হয়।

🧠 Comprehensive Breakdown

এই লেকচারে আমরা আপডেট অপারেশনের লাইফসাইকেল ইমপ্লিমেন্ট করেছি। চলুন প্রতিটি ধাপ বিস্তারিতভাবে বুঝে নিই।

১. Null Check & Reusable Validation (Priority: 9/10)

Concept & Why: ডাটাবেসে আপডেট কমান্ড পাঠানোর আগে আমাদের ডেটা ভ্যালিডেট করতে হবে। নতুন করে সবগুলো প্রপার্টির জন্য if স্টেটমেন্ট না লিখে, আমরা আগে তৈরি করা ValidationHelper কল করেছি, যা এক লাইনে পুরো অবজেক্টের সব ভ্যালিডেশন চেক করে ফেলে।

public PersonResponse UpdatePerson(PersonUpdateRequest? personUpdateRequest)
{
    // ১. Null Check
    if (personUpdateRequest == null)
    {
        throw new ArgumentNullException(nameof(personUpdateRequest));
    }
 
    // ২. Model Validation (Data Annotations চেক করা)
    ValidationHelper.ModelValidation(personUpdateRequest);
 
    // ... পরবর্তী লজিক
}
 

২. Fetch and Update (Priority: 10/10)

Concept & Why: আপডেটের সবচেয়ে গুরুত্বপূর্ণ অংশ হলো, যে ডেটাটি আপডেট করতে চাই, সেটি ডাটাবেসে আছে কিনা তা আগে চেক করা। যদি না থাকে, তবে Exception থ্রো করা। আর যদি থাকে, তবে ডাটাবেস থেকে পাওয়া সেই অবজেক্টের ডেটাগুলো নতুন ডেটা দিয়ে রিপ্লেস করে দেওয়া। (নোট: আমরা PersonID আপডেট করি না, কারণ এটি Primary Key)।

    // ৩. ডাটাবেস (লিস্ট) থেকে ম্যাচিং ইউজার খুঁজে বের করা
    Person? matchingPerson = _persons.FirstOrDefault(temp => temp.PersonID == personUpdateRequest.PersonID);
 
    if (matchingPerson == null)
    {
        throw new ArgumentException("Given person id doesn't exist");
    }
 
    // ৪. ডেটা আপডেট (ওভাররাইট) করা
    matchingPerson.PersonName = personUpdateRequest.PersonName;
    matchingPerson.Email = personUpdateRequest.Email;
    matchingPerson.DateOfBirth = personUpdateRequest.DateOfBirth;
    
    // Enum থেকে String-এ কনভার্ট করা
    matchingPerson.Gender = personUpdateRequest.Gender.ToString(); 
    
    matchingPerson.CountryID = personUpdateRequest.CountryID;
    matchingPerson.Address = personUpdateRequest.Address;
    matchingPerson.ReceiveNewsLetters = personUpdateRequest.ReceiveNewsLetters;
 
    // ৫. DTO তে কনভার্ট করে রিটার্ন করা
    return ConvertPersonToPersonResponse(matchingPerson);
 

৩. Test Debugging & Fixing (Priority: 8/10)

Concept & Why: লজিক লেখা শেষে টেস্ট রান করলে UpdatePerson_PersonNameIsNull টেস্টটি ফেইল করে। অথচ আমরা ArgumentException ই আশা করছিলাম। সমস্যা ১: টেস্টের Details-এ দেখা যায় Email cannot be blank। কারণ আমরা টেস্ট ডেটায় Email ফাঁকা রেখেছিলাম। সমস্যা ২: Email দেওয়ার পরও টেস্ট ফেইল করে Must specify valid information for parsing এরর দিয়ে। এর কারণ হলো, PersonResponse থেকে UpdateRequest-এ কনভার্ট করার সময় Gender স্ট্রিংটি Enum.Parse করার চেষ্টা করছিল, কিন্তু সেটি null থাকায় অ্যাপ্লিকেশন ক্র্যাশ করছিল।

সমাধান: টেস্টের Arrange ব্লকে Email এবং Gender প্রপার্টিগুলোতে ডামি ডেটা দিয়ে টেস্টটি পাস করানো হয়। এটি একটি দারুণ রিয়েল-লাইফ ডিবাগিং লেসন।

// Test Data-তে মিসিং ভ্যালু অ্যাড করা হলো
person_update_request.Email = "someone@example.com";
person_update_request.Gender = GenderOptions.Male;
 

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

লেকচারের পদ্ধতিটি ইন-মেমরি লিস্টের জন্য শতভাগ কাজ করে। কিন্তু যখন আপনি রিয়েল-ওয়ার্ল্ড প্রজেক্টে Entity Framework Core (EF Core) নিয়ে কাজ করবেন, তখন এভাবে একটি একটি করে প্রপার্টি অ্যাসাইন করার চেয়ে আরও স্মার্ট পদ্ধতি আছে।

1. AutoMapper for Updating Entities: যেমনটা আমরা আগের লেকচারগুলোতে বলেছি, ২০-৩০টি প্রপার্টি হাতে ধরে ধরে অ্যাসাইন করা খুব বিরক্তিকর এবং ভুল হওয়ার সম্ভাবনা থাকে। AutoMapper দিয়ে আপনি চাইলে এক লাইনে DTO থেকে Entity-তে ডেটা ওভাররাইট করতে পারেন।

Modern Code with AutoMapper:

    // AutoMapper নিজে থেকেই Name, Email সহ সব প্রপার্টি matchingPerson-এ ওভাররাইট করে দেবে
    _mapper.Map(personUpdateRequest, matchingPerson);
    
    return _mapper.Map<PersonResponse>(matchingPerson);
 

2. Modern Exception Throwing: C# 10+ এ ArgumentNullException.ThrowIfNull(personUpdateRequest); ব্যবহার করাটা স্ট্যান্ডার্ড।


🏆 Best Practices (For Update Operations)

  1. Don’t Update Primary Keys: লেকচারার যেমন PersonID বাদে বাকি সব আপডেট করেছেন, এটি ডাটাবেস ম্যানেজমেন্টের অন্যতম প্রধান রুল। প্রাইমারি কী (Primary Key) কখনোই আপডেট করা উচিত নয়।
  2. Partial Updates (Advanced): রিয়েল প্রজেক্টে ইউজার সবসময় সব ডেটা আপডেট করে না। হয়তো সে শুধু ইমেইল চেঞ্জ করতে চায়। সেক্ষেত্রে HTTP PATCH রিকোয়েস্ট এবং JSON Patch ব্যবহার করা হয়। তবে বর্তমানের HTTP PUT স্টাইলের ফুল-আপডেট (Full-update) শেখার জন্য এই অ্যাপ্রোচটি পারফেক্ট।
  3. Debugging Tests: যখন কোনো টেস্ট ফেইল করে, তখন সাথে সাথে লজিক চেঞ্জ না করে Test Explorer-এর “Test Detail Summary” পড়ে দেখা উচিত যে ঠিক কোন লাইনে এবং কী কারণে টেস্টটি ফেইল করেছে (যেমনটা লেকচারার করেছেন)। এটি ডিবাগিং স্কিল অনেক বাড়িয়ে দেয়।