Setup-Fixture-Teardown

Method chaining
Domain Driven Design Summary

Table of Contents

استراتژی مختلف تست نویسی (Fixture Strategies)

Transient : 

داده ها از بین می روند مثلاً روی ram ذخیره شوند

Persistent:

داده ها جایی ذخیره می شوند مثلاً دیتابیس

————————————————————————————–

Fresh:

برای هر تست تازه و جدید تولید می شوند

Shared :

بین چند تا تست Share هستند

Inline and Implicit Setup

در Unit Test، برخی اوقات نیاز است که برای هر تست یک محیط تمیز و آماده کنیم یا برخی مقدارها را مقداردهی اولیه کنیم. در این موارد، استفاده از روش هایی مانند Inline Setup و Implicit Setup می‌تواند مفید باشد.

Inline Setup

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

Implicit Setup

در این روش، تنظیمات ابتدایی یا مقداردهی اولیه برای همه یا برخی از تست‌ها به طور ضمنی و بدون نیاز به تکرار در هر تست انجام می‌شود.

بیایید این دو روش را با مثالی در C# بررسی کنیم:

Sample Code Inline Setup 
				
					
using Xunit;

public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}

public class CalculatorTests
{
    [Fact]
    public void Add_Should_Return_Correct_Result()
    {
        // Arrange
        Calculator calculator = new Calculator();
        int a = 5;
        int b = 3;

        // Act
        int result = calculator.Add(a, b);

        // Assert
        Assert.Equal(8, result);
    }
}
				
			

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

Sample Code Implicit Setup
				
					
using Xunit;

public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}

public class CalculatorTests
{
    private readonly Calculator _calculator;

    public CalculatorTests()
    {
        // ایجاد یک شیء از کلاس Calculator به صورت ضمنی برای همه تست‌ها
        _calculator = new Calculator();
    }

    [Fact]
    public void Add_Should_Return_Correct_Result()
    {
        // Arrange
        int a = 5;
        int b = 3;

        // Act
        int result = _calculator.Add(a, b);

        // Assert
        Assert.Equal(8, result);
    }
}
				
			

در این مثال، ما یک مقداردهی اولیه به صورت ضمنی در کلاس تست انجام داده‌ایم. این بدان معناست که هرگاه کلاس تست ایجاد می‌شود، یک نمونه از `Calculator` نیز ایجاد می‌شود. این شیء سپس در تمامی تست‌های این کلاس تستی مورد استفاده قرار می‌گیرد بدون نیاز به مجدداً ایجاد آن. این کار به تمیزکاری کد کمک می‌کند و از تکرار کد جلوگیری می‌کند.

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

سه مدل استفاده از Implicit 

روش اول : استفاده از Constructor و IDisposable 

در این روش که قبل از هر تست و بعد از هر تست این کار را انجام دهیم

روش دوم : IClassFixture

در زبان سی شارپ ، `IClassFixture` یک ویژگی است که به شما امکان می‌دهد تا یک شیء را به تمامی تست‌های یک کلاس تستی (Test Class) ارسال کنید. این کار معمولاً برای ایجاد یک محیط ثابت و مشترک برای تمامی تست‌ها استفاده می‌شود. در واقع، شیء ارسالی می‌تواند برای تمامی تست‌ها در آن کلاس تستی قابل دسترسی باشد.

برای اینکه بهتر متوجه شوید، یک مثال را مرور می‌کنیم:

فرض کنید که ما یک کلاس `Calculator` داریم که عملیات محاسباتی ساده‌ای انجام می‌دهد. همچنین ما تست‌های واحدی برای این کلاس را پیاده‌سازی کرده‌ایم و می‌خواهیم یک شیء مشترک از `Calculator` را بین تمامی تست‌های این کلاس به اشتراک بگذاریم.

				
					
using System;
using Xunit;

public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }

    public int Subtract(int a, int b)
    {
        return a - b;
    }

    public int Multiply(int a, int b)
    {
        return a * b;
    }

    public int Divide(int a, int b)
    {
        if (b == 0)
            throw new ArgumentException("Divisor cannot be zero.");
        return a / b;
    }
}

public class SharedCalculatorFixture : IDisposable
{
    public Calculator Calculator { get; private set; }

    public SharedCalculatorFixture()
    {
        Calculator = new Calculator();
    }

    public void Dispose()
    {
        // Clean up code, if any
    }
}


public class CalculatorTests : IClassFixture<SharedCalculatorFixture>
{
    private readonly Calculator _calculator;

    public CalculatorTests(SharedCalculatorFixture fixture)
    {
        _calculator = fixture.Calculator;
    }

    [Fact]
    public void Add_ReturnsCorrectResult()
    {
        // Arrange
        int a = 5;
        int b = 3;

        // Act
        int result = _calculator.Add(a, b);

        // Assert
        Assert.Equal(8, result);
    }

    [Fact]
    public void Subtract_ReturnsCorrectResult()
    {
        // Arrange
        int a = 5;
        int b = 3;

        // Act
        int result = _calculator.Subtract(a, b);

        // Assert
        Assert.Equal(2, result);
    }

    // More test methods can be added for other Calculator operations
}


				
			

در اینجا، کلاس `Calculator` شامل عملیات محاسباتی اساسی است که می‌خواهیم آن‌ها را تست کنیم. سپس یک کلاس تستی به نام `CalculatorTests` ایجاد شده است که از `IClassFixture<SharedCalculatorFixture>` ارث‌بری می‌کند. این به معنی این است که `SharedCalculatorFixture` به عنوان یک آزمون مشترک برای تمامی تست‌ها در `CalculatorTests` استفاده می‌شود.

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

با این روش، همه تست‌ها به یک نمونه از `Calculator` دسترسی دارند و هیچ دومدتری بین این تست‌ها وجود ندارد.

روش سوم : ICollectionFixture

`ICollectionFixture` نیز مانند `IClassFixture` در زبان C# از استفاده‌های متداول در Unit Test است. با استفاده از `ICollectionFixture` می‌توانید یک شیء یکبار برای تمامی تست‌ها در یک مجموعه تست به اشتراک بگذارید.

برای نمونه، فرض کنید که ما یک سیستم مدیریت کاربران داریم و می‌خواهیم در تمامی تست‌های مربوط به این سیستم از یک سیاست مشترک برای مدیریت کاربران استفاده کنیم. برای این کار، از `ICollectionFixture` استفاده می‌کنیم تا یک شیء از کلاسی که سیاست مدیریت کاربران را پیاده‌سازی می‌کند را به تمامی تست‌ها ارسال کنیم.

بیایید این مثال را بررسی کنیم:

				
					
using System;
using Xunit;

// کلاسی برای مدیریت کاربران
public class UserManager
{
    public void AddUser(string username)
    {
        // مثال: عملیات اضافه کردن کاربر
    }

    public void RemoveUser(string username)
    {
        // مثال: عملیات حذف کاربر
    }
}
//*********************************
// تعریف یک ICollectionFixture برای به اشتراک گذاشتن UserManager
public class UserManagerFixture : IDisposable
{
    public UserManager UserManager { get; private set; }

    public UserManagerFixture()
    {
        // می‌توانید هر نوع مقداردهی اولیه مربوط به UserManager را در اینجا انجام دهید
        UserManager = new UserManager();
    }

    public void Dispose()
    {
        // انجام عملیات تمیز‌کاری اگر لازم است
    }
}
//***************************************//
    [CollectionDefinition("UserManager Collection")]
    public class UserManagerFixtureDefinition : ICollectionFixture<UserManagerFixture>
    {
    }
//*********************************
// استفاده از ICollectionFixture در تست‌ها
[Collection("Database Collection")]
public class UserTests 
{
    private readonly UserManager _userManager;

    public UserTests(UserManagerFixture fixture)
    {
        _userManager = fixture.UserManager;
    }

    [Fact]
    public void AddUser_Test()
    {
        // Arrange
        string username = "testuser";

        // Act
        _userManager.AddUser(username);

        // Assert
        // بررسی عملیات اضافه کردن کاربر
    }

    [Fact]
    public void RemoveUser_Test()
    {
        // Arrange
        string username = "testuser";

        // Act
        _userManager.RemoveUser(username);

        // Assert
        // بررسی عملیات حذف کاربر
    }
}