توضیحات

نظر

نظر

  • امتیاز : 00/10
  • به دیگران توصیه می‌کنم :
  • دوباره می‌خوانم :
  • ایده برجسته :
  • تاثیر در من :
  • نکات مثبت :
  • نکات منفی :

مشخصات

  • نویسنده :
  • انتشارات :

بخش‌هایی از کتاب

بخش اول: مقدمه‌ای بر تست نرم‌افزار در گوگل

در این بخش، جیمز ویتاکر (James Whittaker) یک حقیقت تلخ اما واقعی را بیان می‌کند: “کیفیت با تست کردن به وجود نمی‌آید.” (Quality cannot be tested in).

۱. فلسفه اصلی: کیفیت ≠ تست

در بسیاری از شرکت‌های سنتی، فرآیند به این صورت است: توسعه‌دهندگان (Developers) کد می‌زنند و آن را به تیم تست (QA) “پرتاب” می‌کنند. تیم تست باگ پیدا می‌کند و برمی‌گرداند. گوگل می‌گوید این روش محکوم به شکست است.

نکته عمیق و تخصصی: اگر نرم‌افزار از ابتدا درست معماری و کدنویسی نشده باشد، هیچ مقدار تستی نمی‌تواند آن را باکیفیت کند. مثل این است که ماشینی بسازید که موتورش نقص دارد و بخواهید با رنگ کردن بدنه، کیفیتش را بالا ببرید. در گوگل، توسعه و تست در هم آمیخته‌اند (Blended). شما نمی‌توانید بگویید کجا توسعه تمام شد و کجا تست شروع شد.

درس برای ما: در پروژه‌های .NET Core خودت، تست نباید یک مرحله جداگانه در انتهای اسپرینت باشد. تست باید بخشی از فرآیند نوشتن کد باشد (مثل TDD که اشاره کردی).

۲. نقش‌های کلیدی (The Roles)

گوگل برای حل مشکل مقیاس‌پذیری و کیفیت، نقش‌های مهندسی را بازتعریف کرده است. این قسمت بسیار مهم است چون تفاوت “تستر” و “مهندس تست” را نشان می‌دهد.

الف) مهندس نرم‌افزار (SWE - Software Engineer)

  • وظیفه: توسعه فیچرها و کدهای اصلی محصول.
  • مسئولیت تست: بله، درست خواندی. مسئولیت اصلی کیفیت با خود SWE است. آن‌ها باید کدهای Unit Test خود را بنویسند.
  • دیدگاه: “من چیزی را می‌سازم، پس مسئولیت خراب شدنش با من است.”

ب) مهندس نرم‌افزار در تست (SET - Software Engineer in Test)

  • وظیفه: این نقش یک توسعه‌دهنده است، اما تمرکزش روی تست‌پذیری (Testability) است.
  • کار عملی: SETها فریم‌ورک‌های تست می‌سازند. آن‌ها Mocks و Stubs را ایجاد می‌کنند تا SWEها بتوانند راحت‌تر تست بنویسند. آن‌ها “زیرساخت کیفیت” را می‌سازند.
  • تخصص: این افراد باید به اندازه SWEها در کدنویسی (مثلاً C# یا Java) قوی باشند، اما ذهنیتشان روی شکستن کد و پیدا کردن نقاط ضعف معماری متمرکز است.

ج) مهندس تست (TE - Test Engineer)

  • وظیفه: تمرکز روی کاربر (User).
  • کار عملی: آن‌ها تست‌های End-to-End را مدیریت می‌کنند، ریسک‌های پروژه را تحلیل می‌کنند و مطمئن می‌شوند که تمام اجزا در کنار هم برای کاربر نهایی درست کار می‌کنند.
  • دیدگاه: “آیا این محصول واقعاً نیاز کاربر را برطرف می‌کند؟”

۳. ساختار سازمانی: استقلال مهندسی (Engineering Productivity)

یک نکته بسیار استراتژیک در گوگل این است که تیم‌های تست (SET و TE) معمولاً زیر نظر مدیران محصول (Product Managers) نیستند. آن‌ها در یک سازمان مستقل به نام Engineering Productivity فعالیت می‌کنند.

  • چرا؟ چون اگر تسترها زیر نظر مدیر محصول باشند، وقتی ددلاین (Deadline) نزدیک می‌شود، مدیر ممکن است بگوید “بی‌خیال تست شوید، باید ریلیز کنیم”.
  • نتیجه: در گوگل، تیم تست قدرت “وتو” دارد و می‌تواند بگوید “این محصول از نظر کیفی آماده نیست” و کسی نمی‌تواند آن‌ها را مجبور به تایید کند.

۴. انواع تست‌ها: کوچک، متوسط، بزرگ (Small, Medium, Large)

گوگل به جای استفاده از واژه‌های گیج‌کننده مثل Unit, Integration, System, Functional، از سایز تست استفاده می‌کند که روی Scope (دامنه) تمرکز دارد:

  1. Small Tests (تست‌های کوچک):
    • معمولاً همان Unit Testها هستند.
    • روی یک تابع یا کلاس واحد تمرکز دارند.
    • باید بسیار سریع باشند (زیر ۱۰۰ میلی‌ثانیه).
    • هیچ وابستگی خارجی (دیتابیس، شبکه، فایل سیستم) نباید داشته باشند (همه چیز Mock می‌شود).
    • مسئول: عمدتاً SWE.
  2. Medium Tests (تست‌های متوسط):
    • معمولاً Integration Test هستند.
    • تعامل بین دو یا چند ماژول را چک می‌کنند (مثلاً سرویس و دیتابیس In-Memory).
    • ممکن است از Mock استفاده کنند یا نکنند (مثلاً استفاده از Local Database).
    • مسئول: همکاری SWE و SET.
  3. Large Tests (تست‌های بزرگ):
    • تست‌های End-to-End یا System Tests.
    • سناریوی واقعی کاربر را شبیه‌سازی می‌کنند.
    • از دیتابیس واقعی، شبکه واقعی و سرویس‌های خارجی استفاده می‌کنند.
    • کند هستند و ممکن است ساعت‌ها طول بکشند.
    • مسئول: عمدتاً TE و SET.

۱. اصل “Quality is a Feature”

در معماری تمیز (Clean Architecture)، ما اغلب تست‌ها را در یک پروژه جداگانه (مثلاً MyProject.Tests) می‌گذاریم. گوگل به ما یاد می‌دهد که زیرساخت تست (مثل کلاس‌های Base برای Integration Testها، کانتینرهای Docker برای بالا آوردن دیتابیس تست و …) خودش یک محصول مهندسی است.

  • توصیه: اگر در پروژه‌هایت از EF Core استفاده می‌کنی، یک زیرساخت قوی برای InMemoryDatabase یا بهتر از آن، استفاده از Testcontainers برای بالا آوردن SQL Server واقعی در تست‌های Medium بساز. این کار دقیقاً وظیفه یک SET است.

۲. پرهیز از Mockهای بیش از حد

در بخش تست‌های Small، گوگل تاکید می‌کند که Mockها خوب هستند، اما در تست‌های Medium و Large باید مراقب باشیم. اگر همه چیز را Mock کنیم، در واقع داریم “تست می‌کنیم که آیا Mockهای ما درست کار می‌کنند” نه اینکه کد ما درست کار می‌کند.

  • Best Practice: در .NET Core، سعی کن Logic خالص (Domain Logic) را طوری بنویسی که وابستگی خارجی نداشته باشد تا راحت Small Test شود (بدون نیاز به Mock پیچیده). این دقیقاً با اصول DDD که تو کار می‌کنی همخوانی دارد. Domain Model نباید به Infrastructure وابسته باشد.

۳. اتوماسیون بی‌پایان (Automation)

گوگل می‌گوید اگر کاری را باید دستی انجام دهید، اشتباه است. حتی فرآیند بیلد و ریلیز.

  • اقدام عملی: مطمئن شو که پایپ‌لاین‌های CI/CD (مثلاً با GitHub Actions که کار می‌کنی) طوری تنظیم شده‌اند که به محض Push کردن کد:
    1. Small Tests اجرا شوند.
    2. اگر پاس شدند، بیلد انجام شود.
    3. Medium Tests در یک محیط ایزوله اجرا شوند.

فصل دوم: مهندس نرم‌افزار در تست (SET) - تحلیل عمیق

در این فصل، جیمز ویتاکر (James Whittaker) به تشریح یکی از کلیدی‌ترین نقش‌های مهندسی در گوگل می‌پردازد: مهندس نرم‌افزار در تست یا SET.

۱. مقدمه: تقابل آرمان‌شهر و واقعیت

ویتاکر بحث را با توصیف یک فرآیند توسعه ایده‌آل آغاز می‌کند. در یک دنیای کامل، توسعه‌دهنده پیش از آنکه حتی یک خط کد بنویسد، از خود می‌پرسد: «این قطعه کد چگونه تست خواهد شد؟»

در چنین دنیایی، توسعه‌دهنده برای تمام حالات مرزی (Boundary Cases)، داده‌های ورودی نامعتبر و خطاهای احتمالی، تست می‌نویسد. اما در واقعیت، فشار زمان و ددلاین‌ها باعث می‌شود که توسعه‌دهندگان (SWEs) اغلب روی “نوشتن فیچر” تمرکز کنند و “تست‌پذیری” را فراموش کنند.

اینجاست که نقش SET متولد می‌شود.

۲. تعریف دقیق نقش SET

برخلاف تصور رایج در بسیاری از شرکت‌ها، SET یک “تستر دستی” یا “QA سنتی” نیست.

  • هویت: SET یک توسعه‌دهنده (Developer) تمام‌عیار است. مهارت کدنویسی او باید هم‌تراز با مهندس نرم‌افزار (SWE) باشد.
  • ماموریت: وظیفه اصلی SET، تست کردن محصول نیست؛ بلکه وظیفه او فراهم کردن بستری است که SWEها بتوانند کدهایشان را تست کنند.
  • تمرکز: تمرکز SWE روی کاربر و فیچرهاست، اما تمرکز SET روی تست‌پذیری (Testability)، قابلیت اطمینان (Reliability) و زیرساخت‌های کیفیت است.

به زبان ساده: SWE فیچر را می‌نویسد، و SET کدی را می‌نویسد که نوشتن تست برای آن فیچر را ممکن می‌سازد.

۳. چرخه حیات توسعه و نقش SET

گوگل برای مدیریت مقیاس عظیم کدهایش، فرآیندهای مشخصی دارد که SET در تمام آن‌ها نقش ایفا می‌کند.

الف) فاز پروتوتایپ (The Prototype Phase)

در ابتدای خلق یک محصول جدید، هدف اصلی اثبات کارایی ایده است. در این مرحله، گوگل اجازه می‌دهد که کیفیت فدای سرعت شود.

  • قانون: تا زمانی که مشخص نشود یک محصول ارزش توسعه دارد، صرف منابع برای تست‌های پیچیده، اتلاف وقت است.
  • نقش SET: در این مرحله دخالت چندانی نمی‌کند تا سرعت تیم گرفته نشود.

ب) فاز طراحی (The Design Phase)

زمانی که پروژه رسمی شد، SET وارد می‌شود. این مهم‌ترین نقطه‌ی اثرگذاری SET است. او مستندات طراحی (Design Docs) را بررسی می‌کند. در گوگل، SETها مستندات را بر اساس چهار معیار نقد می‌کنند:

  1. کامل بودن (Completeness): آیا همه وابستگی‌ها و سناریوها دیده شده‌اند؟
  2. صحت (Correctness): آیا منطق سیستم درست است؟ (حتی غلط‌های املایی در مستندات نشانه بی‌دقتی تلقی می‌شوند).
  3. یکپارچگی (Consistency): آیا دیاگرام‌ها با متن توضیحات همخوانی دارند؟
  4. تست‌پذیری (Testability): آیا می‌توان برای این سیستم تست خودکار نوشت؟ آیا وابستگی‌های خارجی (External Dependencies) قابل کنترل هستند؟

تحلیل معماری برای شما: به عنوان یک معمار نرم‌افزار، وقتی Design یک میکروسرویس جدید را بررسی می‌کنید، باید بپرسید: “آیا این سرویس به دیتابیس SQL وابستگی مستقیم (Hard-coded) دارد یا از طریق Interface تزریق می‌شود؟” اگر مستقیم باشد، تست‌پذیری پایین است و SET باید اینجا اعتراض کند.

۴. اتوماسیون و استراتژی تست (Automation Planning)

یکی از وظایف اصلی SET، نوشتن فریم‌ورک‌های تست است. گوگل تاکید دارد که نباید همه چیز را به صورت End-to-End (تست بزرگ) تست کرد، زیرا این تست‌ها کند و شکننده (Brittle) هستند.

استفاده از ماک‌ها و فیک‌ها (Mocks & Fakes)

برای اینکه بتوانیم تست‌های کوچک (Small Tests) و سریع داشته باشیم، باید وابستگی‌ها را شبیه‌سازی کنیم.

  • Mock: شبیه‌سازی رفتار یک تابع (مثلاً: اگر متد X صدا زده شد، مقدار Y را برگردان).
  • Fake: پیاده‌سازی سبک و سریع یک Interface (مثلاً: یک دیتابیس که به جای SQL Server، از یک List<T> در حافظه استفاده می‌کند).

SETها وظیفه دارند این کلاس‌های Fake را بنویسند تا SWEها بتوانند به راحتی از آن‌ها استفاده کنند.

۵. مثال عملی با رویکرد Clean Code و C#

کتاب مثالی از یک سرویس AddUrl می‌زند. بیایید این مثال را با استانداردهای .NET Core، اصول SOLID و معماری تمیز بازنویسی کنیم.

گام اول: تعریف قراردادها (Contracts)

قبل از پیاده‌سازی سرویس، باید ورودی و خروجی مشخص شود. در گوگل از Protocol Buffers استفاده می‌شود، اما در .NET ما از DTOها استفاده می‌کنیم.

// Contracts/AddUrlRequest.cs
public class AddUrlRequest
{
    public string Url { get; set; }      // الزامی
    public string Comment { get; set; }  // اختیاری
}

// Contracts/AddUrlResponse.cs
public class AddUrlResponse
{
    public bool IsSuccess { get; set; }
    public string ErrorMessage { get; set; }
    public int RecordId { get; set; }
}

گام دوم: تعریف اینترفیس (Interface Definition)

این مهم‌ترین بخش برای تست‌پذیری است.

// Interfaces/IUrlService.cs
public interface IUrlService
{
    Task<AddUrlResponse> AddUrlAsync(AddUrlRequest request);
}

گام سوم: پیاده‌سازی Fake توسط SET

مهندس SET این کلاس را می‌نویسد تا بقیه تیم بتوانند بدون نیاز به دیتابیس واقعی، کدهایشان را تست کنند.

// Tests/Fakes/FakeUrlService.cs
public class FakeUrlService : IUrlService
{
    // استفاده از یک لیست در حافظه به جای دیتابیس واقعی
    private readonly List<AddUrlRequest> _storedUrls = new();
    
    public Task<AddUrlResponse> AddUrlAsync(AddUrlRequest request)
    {
        if (string.IsNullOrEmpty(request.Url))
        {
            return Task.FromResult(new AddUrlResponse 
            { 
                IsSuccess = false, 
                ErrorMessage = "URL cannot be empty" 
            });
        }

        _storedUrls.Add(request);
        
        return Task.FromResult(new AddUrlResponse 
        { 
            IsSuccess = true, 
            RecordId = _storedUrls.Count 
        });
    }
}

گام چهارم: نوشتن تست واحد (Unit Test)

حالا SWE می‌تواند با استفاده از این Fake، منطق کنترلر یا لایه بالاتر را تست کند.

[TestClass]
public class UrlControllerTests
{
    private UrlController _controller;
    private IUrlService _fakeService;

    [TestInitialize]
    public void Setup()
    {
        // تزریق وابستگی Fake
        _fakeService = new FakeUrlService(); 
        _controller = new UrlController(_fakeService);
    }

    [TestMethod]
    public async Task AddUrl_ValidRequest_ReturnsOk()
    {
        // Arrange
        var request = new AddUrlRequest { Url = "https://site.com" };

        // Act
        var result = await _controller.Post(request);

        // Assert
        Assert.IsInstanceOfType(result, typeof(OkObjectResult));
    }
}

۶. دروازه‌های کیفیت (Quality Gates) و صف ارسال (Submit Queue)

یکی از جذاب‌ترین بخش‌های مهندسی گوگل، نحوه مدیریت کدها قبل از ورود به مخزن اصلی (Main Repository) است.

  1. بررسی کد (Code Review): تمام تغییرات باید توسط حداقل یک نفر دیگر بررسی شود. SETها نیز در بررسی کدها شرکت می‌کنند تا مطمئن شوند کد جدید، تست‌های قبلی را خراب نمی‌کند.
  2. صف ارسال (Submit Queue): وقتی توسعه‌دهنده کد را نهایی می‌کند، کد وارد یک صف خودکار می‌شود.
    • سیستم به صورت خودکار تمام تست‌های مرتبط (Small, Medium) را اجرا می‌کند.
    • اگر حتی یک تست شکست بخورد (Fail شود)، کد رد (Reject) می‌شود و به توسعه‌دهنده برمی‌گردد.
    • این مکانیزم تضمین می‌کند که شاخه اصلی (Main Branch) همیشه سالم و قابل بیلد (Green) باقی بماند.