<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های امید فتح الله زاده</title>
        <link>https://virgool.io/feed/@omidfth</link>
        <description></description>
        <language>fa</language>
        <pubDate>2026-06-17 11:20:52</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/77130/avatar/avatar.png?height=120&amp;width=120</url>
            <title>امید فتح الله زاده</title>
            <link>https://virgool.io/@omidfth</link>
        </image>

                    <item>
                <title>باگ‌آفرین نمونه، یونیتی و دوستان</title>
                <link>https://virgool.io/@omidfth/%D8%A8%D8%A7%DA%AF-%D8%A2%D9%81%D8%B1%DB%8C%D9%86-%D9%86%D9%85%D9%88%D9%86%D9%87-%DB%8C%D9%88%D9%86%DB%8C%D8%AA%DB%8C-%D9%88-%D8%AF%D9%88%D8%B3%D8%AA%D8%A7%D9%86-bkkaeocff8lg</link>
                <description>باگ گیری، تا بینهایت و فراتر از آنگوگل تهدید میکندقضیه از اونجایی شروع شد که ایمیلی از سمت گوگل به ما ارسال شد. چیزی که توی ایمیل نوشته شده بود این بود که اگر پلاگین شرکت Tapjoy رو آپدیت نکنید تا 2 ماه دیگه بازیتون از گوگل پلی حذف می‌شه.حالا جریان چی بود؟پلاگینی که ما استفاده کرده بودیم دیگه از قوانین حریم خصوصی جدیدی که گوگل منتشر کرده تبعیت نمی‌کرد و با آپدیت جدیدی که برای این پلاگین داده بودن قضیه حل و فصل می‌شد.سیاه چالهباری، ما هم کار رو شروع کردیم و برای اینکه در آینده مشکلی نخوریم تمامی پلاگین‌ها و ... که تو پروژه استفاده شده بود رو به آخرین نسخه آپدیت کردیم و نسخه جدید بازی رو در گوگل پلی قرار دادیم.فردای اون روز بازی منتشر شد و با کمال تعجب متوجه شدیم بازی بعد از نصب کرش میکنه. این اتفاق در صورتی افتاده بود که نسخه APK ای که در خروجی گرفته بودیم روی گوشی به راحتی اجرا می‌شد و کرشی در کار نبود.همونطور که میدونید گوگل پلی چند وقتی هست که دیگه فقط فایل‌های با فرمت AAB برای پابلیش قبول می‌کنه و دیگه از نسخه های APK پشتیبانی نمی‌کنه.قبل از اینکه بگم چطور مشکل رو حل کردیم باید بگم که ما بازیمون رو با استفاده از موتور بازیسازی یونیتی ساختیم. اون سالی که بازی رو پابلیش کرده بودیم (اسفند 1396) از یونیتی 5 برای ساخت بازی استفاده کردیم و دو بار به یونیتی های بالاتر یعنی 2017 و 2018 هم مایگریت کرده بودیم و آخرین آپدیت بازی هم با استفاده از یونیتی 2020.3 پابلیش کرده بودیم.داستان رفع مشکل از اونجایی شروع شد که متوجه شدیم خیلی از دولوپرهای دیگه هم به مشکل ما خوردن و این مسئله با کامنت کردن برخی از کدهای mainTemplate.gradle که با استفاده از Resolver گوگل ساخته می‌شد، حل می‌شه. بعد از کامنت کردن کدها متوجه شدیم 20 مگابایت به حجم خروجی اضافه شد و بعد از پابلیش آپدیت در گوگل پلی هم کاربرها مشکلی با اجرا شدن بازی نداشتن.اندروید باورنکردنیچند روزی از آخرین آپدیتی که داده بودیم گذشت و متوجه شدیم بعضی از کاربران بازی، باگ عجیب و غریبی رو گزارش می‌کنن. باگی که این کاربرها دچارش شده بودن این بود که بخشی از یوآی بازی در قسمت گیم پلی بازی دچار اختلال شده بود و نمایش داده نمی‌شد.در این لحظه بزرگترین مشکل تیم ما این بود که در تمامی دیوایس‌هایی که داشیتم بازی به درستی اجرا می‌شد و همچین مشکلی اتفاق نمی‌افتاد. این مسئله می‌تونست باعث حل نشدن مشکل بشه، چون وقتی هیچ دیوایس مشابهی همچین باگی نداره، پیدا کردنش کار به مراتب سخت و زمانبریه.روزگار به همین منوال گذشت تا اینکه فرشته نجات از راه رسید. بعد از نصب بازی روی گوشی یکی از دوستان (فرشته نجات) متوجه باگ شدیم و درجا سیم اتصال به کامپیوتر را وصل و عملیات دیباگ کردن را شروع کردیم. بینگ!!!مشکل حل شده بود!!!دیگه از این بدتر نمیشد. با اتصال گوشی به کامپیوتر مشکل حذف بخشی از یوآی بازی به طور کامل رفع شده بود.برای رفع این مشکل فکر کردیم و فکر کردیم و فکر کردیم تا اینکه فهمیدیم قبل از وصل کردن گوشی به کامپیوتر زبان دستگاه رو به انگلیسی تغییر داده بودیم.بله!فرشته نجات زبان گوشی خودش رو روی فارسی گذاشته بود و برای اینکه من حالت دیباگ مود رو فعال کنم، زبان گوشی رو روی انگلیسی گذاشتم (چون واقعا نمی‌شد پیداش کرد کجاست) و بعد به کامپیوتر وصل کردم.دوباره این فرآیند رو تکرار کردم و زبان گوشی رو به فارسی تغییر دادم و متوجه این شدم مشکل برگشته.این عملیات رو روی دستگاه‌های دیگه هم انجام دادیم و مشکل مشابه اتفاق افتاد.مشکل از زبان دستگاه اندرویدی بود. اگر و فقط اگر زبان دستگاه فارسی بود، این مشکل اتفاق می‌افتاد. جالبیش اینجا بود که با هیچ زبان دیگری مشکل نداشت و فقط با فارسی مشکل داشت، به طور مثال زبان گوشی رو روی عربی گذاشتیم و بازی به طور کامل اجرا شد.کار دیباگ کردن شروع شد و در کمال ناباوری هیچ اروری دریافت نشد!در این مرحله تحقیقات در مورد چرایی قضیه شروع شد و متوجه شدم ساپورت زبان فارسی در دیوایس‌های مختلف از یونیتی 2019.3 حذف شده. علت اینکه در یونیتی های قدیمی‌تر که برای بیلد گرفتن از بازی استفاده شده بود، این مشکل وجود نداشت هم همین بود.خم رنگرزیتحقیقات در حال انجام بود که متوجه مورد غیر طبیعی در Parse شدن دیتاها در هنگام استفاده از دیوایس‌های فارسی شدم.در نسخه های اولیه بازی جنگ کاغذی، کاربران امکان تغییر رنگ‌های متعددی داشتن، یعنی می‌تونستن رنگ‌های دلخواه خودشون رو در تنظیمات بازی انتخاب کنن و برای سیو کردن این دیتا، رنگ انتخاب شده به یک رشته تبدیل و در Playerprefs ذخیره می‌شد. (این دیتا رو سمت سرور نگه نمی‌داشتیم و فقط روی گوشی ذخیره می‌شد)این دیتا به طور مثال به این شکل بود.RGB (0.000, 0.000, 1.000, 1.000)یک رشته از اعداد RGBA ذخیره میشد و برای Parse کردن این string به Color هم یک پارسر کاستوم نوشته بودم.در نسخه‌های جدید جنگ کاغذی این قسمت از بازی تغییراتی کرد و بازیکنان فقط میتونستن دو رنگ قرمز یا آبی انتخاب کنن و از اونجایی که چیزی که کار می‌کنه رو نباید دست بزنیم، من هم کد مربوط به تغییر رنگ رو تغییر ندادم و به همین صورت دیتا در گوشی ذخیره می‌شد.اتفاقی که می‌افتاد این بود که دیتای اشتباهی در هنگام دریافت از Playerprefs به پارسر کاستوم می‌رسید و عملیات پارس کردن دیتا به مشکل می‌خورد و رنگ با آلفای 0 به بیرون برگردانده می‌شد. چون اعداد ورودی از انگلیسی به فارسی در هنگام دریافت دیتا تغییر می‌کرد از LTR به RTL تبدیل میشد و پارسر کار خودش رو به درستی انجام نمیداد و آلفای بخشی از یوآی صفر شده بود.خوب حل کردن این بخش کار ساده‌ای بود ولی رسیدن به نقطه‌ی بوجود آورنده باگ کار دشوار و طاقت فرسایی بود. به هر حال اگر شما هم به مشکلات مشابه خوردید بهتره که تمامی جزئیات کار رو بررسی کنید تا مشکلتون حل بشه.امیدوارم از خوندن این مقاله لذت برده باشید و تونسته باشم کمکی به شما کرده باشم.</description>
                <category>امید فتح الله زاده</category>
                <author>امید فتح الله زاده</author>
                <pubDate>Wed, 29 Dec 2021 08:00:48 +0330</pubDate>
            </item>
                    <item>
                <title>یونیت تست در یونیتی (قسمت دوم)</title>
                <link>https://virgool.io/coderlife/%DB%8C%D9%88%D9%86%DB%8C%D8%AA-%D8%AA%D8%B3%D8%AA-%D8%AF%D8%B1-%DB%8C%D9%88%D9%86%DB%8C%D8%AA%DB%8C-%D9%82%D8%B3%D9%85%D8%AA-%D8%AF%D9%88%D9%85-r8c3sfrqet1l</link>
                <description>در قسمت قبل با مفاهیم ابتدایی Unit Test و نوشتن تست های ابتدایی در یونیتی آشنا شدیم. در این قسمت قصد دارم تا به صورت پیشرفته‌تری یونیت تست نوشتن رو یاد بگیریم. به خاطر همین خیلی سریع میرم سر اصل و مطلب و معطل نمی‌کنیم.نحوه استفاده از Assembly Definitionاسمبلی دفینیشن ها برای تست منوها و ادیتور کاربرد دارن. برای اینکه با نحوه کارشون آشنا بشیم به این صورت عمل می‌کنیم. یک کلاس C# به نام CustomMenu تو فولدر  Main میسازیم و از اتریبیوت MenuItem برای نمایش در منوها استفاده می‌کنیم.using UnityEditor;
using UnityEngine;
public class : MonoBehaviour
{
      [MenuItem(&amp;quotCustom Menu/Hello World&amp;quot)]
      public static void HelloWorld()
      {
            Debug.Log(&amp;quotHello World!&amp;quot);
      }
}خوب همونطور که میبینید کدی که زدیم باعث میشه یک منو به نام Custom Menu در ادیتور یونیتی تولید بشه و با زدن روی دکمه Hello World کلمه Hello World! در کنسول یونیتی چاپ می‌شه.در پروژه Test Runner رو اجرا کنید و  Create EditMode Test Assembly Folder رو بزنید تا فولدری به نام Tests در پروژه تولید بشه.همونطور که می‌بینید فولدر ایجاد می‌شه و یک فایل asmdef در فوبدر Tests هم ایجاد می‌شه.یک کلاس تست تو این فولدر به نام CustomMenuTests می‌سازیم تا تست کلاس CustomMenu  رو انجام بده.using System.Collections;
using NUnit.Framework;
using UnityEngine.TestTools;
namespace Tests
{
      public class CustomMenuTests
      {
            [Test]
            public void CustomMenuTestsSimplePasses()
            {
                  CustomMenu.HelloWorld();
            }
      }
}همانطور که میبینید ما یک تست ساده نوشتیم که فقط از کلاس CustomMenu متد HelloWorld رو اجرا می‌کنه.حالا برگردید و تست رو ران کنید.دینگ! ارورچه اتفاقی افتاده!کلاس CustomMenu رو پیدا نکرده.چرا و راه حل چیه؟اینجاست که Assembly Definition به کمک ما میاد. Assembly Definition یک فایل با فرمت .asmdef هست که توی این فایل با فرمت JSON پراپرتی های مربوط به کامپایل تمامی اسکریپت های مربوط به فولدری که asmdef توش هست قرار گرفته.دلیل استفاده از Assembly Definition اینه که وقتی اسکریپتی رو توی فولدر خاصی که Asmdef قرار داره تغییر بدید فقط اسکریپت‌های همون مجموعه کامپایل می‌شن و زمان کامپایل شدن به شدت کاهش پیدا می‌کنه.بدون استفاده از Assembly Definition، یونیتی باید با هر تغییر کوچکی تمامی فایل‌های شما رو مدیریت کنه.به صورت معمول یونیتی تمامی اسکریپت های پروژه رو در Assembly-CSharp.dll مدیریت می‌کنه. این مثال نشون میده به جای اینکه به پنج مجموعه مجزا یعنی Main و Stuff و ... تقسیم بشه همه در Assembly-CSharp مدیریت میشه به این معنا که وقتی Main.dll تغییر میکنه یونیتی نیازی به ریکامپایل کردن Stuff.dll یا بقیه چیزا نداره و خیلی سریع این کار انجام میشه.برمی‌گردیم به مشکلمون.به فولدر Main و جایی که CustomMenu.cs هست می‌ریم و با رایت کلیک یه Assembly Definition تولید می‌کنیم.بعد از اینکار به فولدر Tests می‌ریم و روی فایل  Tests.asmdef میزنیم و Assembly خاص خودمون رو بهش اد میکنیماینجا من اسمش رو به همون صورت دیفالت NewAssembly گذاشتم.خوب حالا وقت تست کردنهروی Run All بزنید تا پاس شدن تست رو مشاهده کنید.برای اینکه بفهمیم یونیتی چه Assembly هایی رو استفاده می‌کنه هم می‌تونیم به کلاس CustomMenu بریم و به این شکل کد بزنیم.using UnityEditor;
using UnityEditor.Compilation;
using UnityEngine;
public class CustomMenu : MonoBehaviour
{
      [MenuItem(&amp;quotCustom Menu/Hello World&amp;quot)]
      public static void HelloWorld()
      {
                  Debug.Log(&amp;quotHello World!&amp;quot);
      }
      [MenuItem(&amp;quotCustom Menu/List Player Assemblies in Console&amp;quot)]
      public static void PrintPlayerAssemblyNames()
      {
            UnityEngine.Debug.Log(&amp;quot== Player Assemblies ==&amp;quot);
            Assembly[] playerAssemblies =
            CompilationPipeline.GetAssemblies(AssembliesType.Player);
            foreach (var assembly in playerAssemblies)
            {
                  UnityEngine.Debug.Log(assembly.name);
            }
      }
      [MenuItem(&amp;quotCustom Menu/List Editor Assemblies in Console&amp;quot)]
      public static void PrintEditorAssemblyNames()
      {
            UnityEngine.Debug.Log(&amp;quot== Editor Assemblies ==&amp;quot);
            Assembly[] editorAssemblies =
            CompilationPipeline.GetAssemblies(AssembliesType.Editor);
            foreach (var assembly in editorAssemblies)
            {
                  UnityEngine.Debug.Log(assembly.name);
            }
      }
      [MenuItem(&amp;quotCustom Menu/List Player Without Test Assemblies Assemblies in Console&amp;quot)]
      public static void PrintPlayerWithoutTestAssembliesAssemblyNames()
      {
            UnityEngine.Debug.Log(&amp;quot== Player Without Test Assemblies Assemblies ==&amp;quot);
            Assembly[] playerWithoutTestAssemblies =
            CompilationPipeline.GetAssemblies(AssembliesType.PlayerWithoutTestAssemblies);
            foreach (var assembly in 
            {
                  UnityEngine.Debug.Log(assembly.name);
            }
      }
}حالا به محیط یونیتی برگردید و از منوی Custom Menu روی List Player Assemblies in console کلیک کنید تا نتیجه رو مشاهده کنید.اگر میخواید اطلاعات کامل تری در این زمینه کسب کنید حتما به داکیومنت خود یونیتی مراجعه کنید.یونیت تست ترکیبی (Combinatorial)اگر قصد دارید به صورت ترکیبی و داده‌های پیش فرض، تست خودتون رو انجام بدید، از این اتریبیوت می‌‎تونید استفاده کنید. روش استفاده هم خیلی ساده است.در فولدر Editor یک فایل یونیت تست به نام MyTest.cs ایجاد کنید و به شکل زیر کد بزنید.using NUnit.Framework;
using UnityEngine;
namespace Tests
{
      public class MyTest
      {
            [Test]
            [Combinatorial]
            public void CombinatorialTest([Values(1,2)] int a,[Values(&amp;quotA&amp;quot,&amp;quotB&amp;quot)] string b,[Values(true,false)] bool c)
            {
                  Debug.LogWarningFormat(&amp;quotCombinatorialTest a={0} b={1} c={2}&amp;quot, a, b, c);
            }
      }
}بعد از ران شدن تست نتیجه زیر قابل مشاهده است.همانطور که میبینید تمامی حالت هایی که ممکن بود، تست شده و ترتیب از چپ و راست تغییر کرده به این صورت که اول 1 و بعد A و سپس True تست شده. در تست دوم 1 A False و ...یونیت تست جفتی (Pairwise)این تست به صورت دوتا دوتا عمل می‌کنه و دیتاها رو به صورت جفتی دریافت می‌کنه. این اتریبیوت برای مواردی استفاده می‌شه که قصد داریم از ازدیاد تست های پی در پی جلوگیری کنیم.حالا مطابق تست قبلی از کلاس MyTest.cs استفاده می‌کنیم و به همان‌صورت عمل می‌کنیم تا تفاوت بین این اتریبیوت با ترکیبی مشخص بشه.using NUnit.Framework;
using UnityEngine;
namespace Tests
{
      public class MyTest
      {
            [Test]
            [Combinatorial]
            public void CombinatorialTest([Values(1,2)] int a,[Values(&amp;quotA&amp;quot,&amp;quotB&amp;quot)] string b,[Values(true,false)] bool c)
            {
                  Debug.LogWarningFormat(&amp;quotCombinatorialTest a={0} b={1} c={2}&amp;quot, a, b, c);
            }
            [Test]
            [Pairwise]
            public void PairwiseTest([Values(1, 2)] int a, [Values(&amp;quotA&amp;quot, &amp;quotB&amp;quot)] string b, [Values(true, false)] bool c)
            {
                  Debug.LogWarningFormat(&amp;quotPairwiseTest a={0} b={1} c={2}&amp;quot, a, b, c);
            }
      }
}نتیجه هم بعد از تست به این صورت به نمایش در میاد.همونطور که می‌بینید 4 تست انجام شده و تفاوتش با حالت قبل که همه حالات رو در نظر می‌گرفت کاملاً مشهوده.این روش به این صورت کار می‌کنه که برای مثال 1, “A”, True در تست ها قرار ندارد چون “A” ,True در تست آخر 2, “A”, True مورد تست قرار گرفته و از تکرار تست “A”, Trueجلوگیری شده. این روش جفت، جفت تست ها رو چک می‌کنه تا از ازدیاد یونیت تست جلوگیری کنه.یونیت تست پی در پی (Sequential)در این اتریبیوت هم به ترتیب یعنی ابتدا داده های اول و سپس داده های دوم مورد تست قرار می‌گیره.برای مشاهده این روش هم به شکل زیر عمل می‌کنیم.using NUnit.Framework;
using UnityEngine;
namespace Tests
{
      public class MyTest
      {
            [Test]
            [Combinatorial]
            public void CombinatorialTest([Values(1,2)] int a,[Values(&amp;quotA&amp;quot,&amp;quotB&amp;quot)] string b,[Values(true,false)] bool c)
            {
                  Debug.LogWarningFormat(&amp;quotCombinatorialTest a={0} b={1} c={2}&amp;quot, a, b, c);
            }
            [Test]
            [Pairwise]
            public void PairwiseTest([Values(1, 2)] int a, [Values(&amp;quotA&amp;quot, &amp;quotB&amp;quot)] string b, [Values(true, false)] bool c)
            {
                  Debug.LogWarningFormat(&amp;quotPairwiseTest a={0} b={1} c={2}&amp;quot, a, b, c);
            }
            [Test]
            [Sequential]
            public void SequentialTest([Values(1, 2)] int a, [Values(&amp;quotA&amp;quot, &amp;quotB&amp;quot)] string b, [Values(true, false)] bool c)
            {
                  Debug.LogWarningFormat(&amp;quotSequentialTest a={0} b={1} c={2}&amp;quot, a, b, c);
            }
      }
}و نتیجه تست به صورت زیر خواهد بود.تست تصادفی (Random)برای تست به صورت تصادفی به صورت زیر عمل می‌کنیم.using NUnit.Framework;
using UnityEngine;
namespace Tests
{
      public class MyTest
      {
            [Test]
            public void RandomTest([Random(0,100,5)] int a)
            {
                  Debug.LogWarningFormat(&amp;quotRandomTest a={0}&amp;quot, a);
            }
      }
}در این کد 5 عدد بین 0 تا 100 انتخاب کردیم و چاپ میکنیمتوجه داشته باشید که با هر بار ران کردن تست نتیجه متفاوتی دریافت خواهید کردتست دامنه ای (Range)برای اینکه دامنه ای از اعداد رو مورد تست قرار بدیم می‌تونیم از اتریبیوت Range استفاده کنیم.using NUnit.Framework;
using UnityEngine;
namespace Tests
{
      public class MyTest
      {
            [Test]
            public void RangeTest([NUnit.Framework.Range(0,100,5)] int a)
            {
                  Debug.LogWarningFormat(&amp;quotRangeTest a={0}&amp;quot, a);
            }
      }
}در این تست از عدد 0 تا 100، 5تا 5 تا جلو می‌ریم و تست می‌کنیم.یونیت تست با TestCaseاگر بخوایم موارد مختلف از اعداد رو به صورت پیش‌فرض به یونیت تست بدیم می‌تونیم از اتریبیوت TestCase استفاده کنیم. روش کار هم به صورت زیر هست.using NUnit.Framework;
using UnityEngine;
namespace Tests
{
      public class MyTest
      {      
            [Test]
            [TestCase(1, 2, 2)]
            [TestCase(2, 5, 10)]
            [TestCase(7, 8, 56)]
            public void TestCaseTest(int a, int b, int c)
            {
                  Debug.LogWarningFormat(&amp;quotTestCaseTest {0}x{1}={2}&amp;quot, a,b,c);
                  Assert.AreEqual(a * b, c);
            }
      }
}در تست بالا 3 کیس مختلف به تستمون دادیم و قصد داریم مطمئن شیم که عدد اول ضرب در عدد دوم به ما عدد سوم رو می‌ده.بعد از ران شدن تست تمامی کیس‌های ما مورد تست قرار می‌گیرن و نتیجه پاس شدنشون رو مشاهده می‌کنیم.یونیت تست با TestCaseSourceما می‌تونیم برای دادن کیس تست از اتریبیوت TestCaseSource هم استفاده کنیم. این اتربیوت به ما اجازه میده تا بتونیم از یک سورس مشخص کیس‌ها رو دریافت کنیم. برای استفاده از این اتریبیوت هم به روش زیر عمل می‌کنیم.using NUnit.Framework;
using UnityEngine;
namespace Tests
{
      public class MyTest
      {
            [Test]
            [TestCaseSource(&amp;quotCaseSources&amp;quot)]
            public void TestCaseSourceTest(int a, int b, int c)
            {
                  Debug.LogWarningFormat(&amp;quotTestCaseSourceTest {0}x{1}={2}&amp;quot, a, b, c);
                  Assert.AreEqual(a * b, c);
            }
            static object[] CaseSources = {
                  new object[] {1,2,2},
                  new object[] {2,5,10},
                  new object[] {7,8,56},
            };
      }
}در این حالت یک آرایه از آبجکت ها به نام CaseSources تولید کردیم و درون این آرایه، آرایه هایی از آبجکت نوشتیم تا مورد تست قرار بگیرن.نتیجه هم به صورت بالا قابل مشاهده است.اتریبیوت Theory و DatapointSourceمیشه گفت اتریبیوت Theory یکی از پرکاربرترین و محبوب ترین اتربیوت‌های یونیت تست به شمار می‌ره.برای استفاده از این اتریبیوت‌ها به صورت زیر عمل می‌کنیم.using NUnit.Framework;
using UnityEngine;
namespace Tests
{
      public class MyTest
      {
            [DatapointSource]
            private float[] values = new float[] { -1f, 0f, 1f, 20f, 8f, 7f, 15f, 10f };
            [Test]
            [Theory]
            public void TheoryTest(float value)
            {
                  Assume.That(value &gt;= 0);
                  Debug.LogWarningFormat(&amp;quotTheoryTest {0}&amp;quot, value);
                  float root = Mathf.Sqrt(value);
                  Assert.That(root &gt;= 0);
                  Assert.That(root * root, Is.EqualTo(value).Within(0.000001f));
            }
      }
}در این تست یک دیتاپوینت داریم که سورس value های ماست و در تستمون گفتیم اگر دیتایی که وارد میشه از 0 بزرگتر بود اجازه ورود به تست رو داشته باشه. در خط های بعدی جذر value رو حساب کردیم و دوباره به توان 2 رسونیدم تا مطمئن بشیم که مربع مجذور عدد با خود عدد برابره.نتیجه تست هم به این صورت قابل مشاهده است.همانطور که میبینید عدد 1- اجازه وارد شده به تست رو نداشته و تستش شکست خورده.اتریبیوت ValueSourceاین اتریبیوت هم به ما اجازه میده تا سورس Value ها رو از جای مشخصی به تستمون بدیم.برای این کار به روش زیر عمل می‌کنیم.using NUnit.Framework;
using UnityEngine;
namespace Tests
{
      public class MyTest
      {
            private static float[] values = new float[] { -1f, 0f, 1f, 20f, 8f, 7f, 15f, 10f };
            [Test]
            public void ValueSourceTest([ValueSource(&amp;quotvalues&amp;quot)] float value)
            {
                  Assume.That(value &gt;= 0);
                  Debug.LogWarningFormat(&amp;quotValueSourceTest {0}&amp;quot, value);
                  float root = Mathf.Sqrt(value);
                  Assert.That(root &gt;= 0);
                  Assert.That(root * root, Is.EqualTo(value).Within(0.000001f));
            }
      }
}توجه داشته باشید که در این روش حتما باید سورس ما Static باشه.نوشتن اتریبیوت‌های سفارشی (Custom)برای اینکه اتریبیوت‌های خاص خودمون رو بنویسیم باید از کلاس NUnit.Framework.PropertyAttribute استفاده کنیم.برای این کار به روش زیر عمل می‌کنیم.using NUnit.Framework;
using UnityEngine;
namespace Tests
{
      public class MyTest
      {
            [Test]
            [CustomTestAttribute(&amp;quotHello&amp;quot)]
            public void CustomTestAttributeTest() {}
      }
      public class CustomTestAttribute : NUnit.Framework.PropertyAttribute
      {
            public CustomTestAttribute(string data) : base()
            {
                  Debug.LogWarningFormat(&amp;quotdata {0}&amp;quot, data);
            }
      }
}در این اتربیوت ما یک دیتا به عنوان ورودی در کانستراکتور کلاس دریافت میکنیم و چاپ میکنیم.خوب تا اینجای کار می‌شه گفت تمامی مواردی برای یونیت تست نوشتن در یونیتی لازمه رو فرا گرفتیم و برای حرفه ای تر شدن کار میشه از CLI یونیتی برای تست کردن در محیط بیرون نرم‌افزار استفاده کرد.امیدوارم این مطلب مورد توجه شما قرار گرفته باشه.</description>
                <category>امید فتح الله زاده</category>
                <author>امید فتح الله زاده</author>
                <pubDate>Wed, 08 Jul 2020 01:17:23 +0430</pubDate>
            </item>
                    <item>
                <title>یکپارچه سازی پیوسته در یونیتی با استفاده از Gitlab CI/CD</title>
                <link>https://virgool.io/@omidfth/%DB%8C%DA%A9%D9%BE%D8%A7%D8%B1%DA%86%D9%87-%D8%B3%D8%A7%D8%B2%DB%8C-%D9%BE%DB%8C%D9%88%D8%B3%D8%AA%D9%87-%D8%AF%D8%B1-%DB%8C%D9%88%D9%86%DB%8C%D8%AA%DB%8C-%D8%A8%D8%A7-%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D8%A7%D8%B2-gitlab-cicd-n3agrynuv4mw</link>
                <description>اگر آدمی هستید که همیشه دنبال ویژگی های جدید می‌گرده و تکنولوژی براش مهمه این مطلب رو مطالعه کنید.این مطلب به توسعه بازی با استفاده از موتور بازی‌سازی یونیتی می‌پردازه و روش اتوماتیک کردن تست و خروجی گرفتن از پروژه رو با استفاده از Gitlab CI/CD آموزش میده.لازمه مطالعه این مقاله آشنایی کافی با یونیتی، Unit Test و Git هستش. بقیه چیزا اینجا آموزش داده شده.درباره CIاول از همه یه تعریف کوتاه و مختصری از CI با هم داشته باشیم. CI مخفف  Continuous Integration  هست و به طور خلاصه به فرآیند اتوماتیک تست کردن و خروجی گرفتن اپلیکیشن به طور مداوم گفته میشه.با استفاده از CI هر بار که کدی توی ریپوزیتوری خاصی مثل gitlab مرج میشه، یکبار تست و خروجی گرفته میشه تا از صحت کدهای مرج شده اطمینان حاصل بشه و به این صورت ما همیشه یک Nightly Build خواهیم داشت.یکپارچه‌سازی (Integrate): برای آپلود تغییرات در ریپوزیتوری که کار میکنیم. مثلا گیت‌لب، گیت‌هاب یا هر ریپوزیتوری دیگه ای در برنچ مثلا master.تست (Test): برای تست اپلیکیشنخروجی گرفتن (Build): برای خروجی گرفتن محصول. مثلا فرمت apk، ipa، exe یا هر چیز دیگه ای ...چرا از CI استفاده میکنیم؟وقتی همکارتون پروژه رو روی ریپوزیتوری مرج می‌کنه، یکی باید کدهایی که زده رو تست و کنه و مطمئن شه که برای خروجی گرفتن از پروژه مشکلی پیش نیومده باشه. حالا شما اگر مدیر بخش برنامه نویسی باشید چکار میکنید؟ با هر بار merge شدن پروژه روی گیت‌لب همه این کارها رو به صورت دستی انجام می‌دید؟ مسلماً این کار بسیار زمان بر هست و باعث کند شدن روند توسعه می‌شه. اینجاست که CI به کمک شما میاد. تست‌های سلامت کد به صورت خودکار انجام می‌شه و سریعا در مورد خطاهای کد به صورت خودکار به شما اطلاع رسانی می‌شه. به این وضعیت اصطلاحاً بازخورد سریع یا Fast Feedback میگن. پاس شدن تست‌ها و خروجی گرفتن از پروژه مهمترین بخش تولید هر اپلیکیشنی رو شامل میشه و با اتوماتیک کردن این فرآیند میشه با سرعت و دقت بالاتری بازی خودتون رو توسعه بدید.قدم اولاول از همه اگر در سایت Gitlab حساب ندارید یک حساب کاربری تهیه کنید. البته از اونجایی که گیت‌لب ما رو تحریم کرده برای ورود به سایت می‌تونید از شکن استفاده کنید.نمای بخش ورود به سایت Gitlabبعد از اینکه اکانت رو ساختید وارد Gitlab بشید و یک پروژه بسازید.ساخت پروژه در Gitlabبرای مثال من پروژه‌ای به نام unity-ci-project ساختم. بعد از اینکه پروژه رو ساختید یک کلون از پروژه بگیرید و دو تا فولدر الزامی Assets و ProjectSettings رو به فولدرتون اضافه کنید تا یونیتی بتونه پروژه رو باز کنه.بعد از اینکه پروژه رو باز کردید اولین کاری که می‌کنیم اینه که یک کلاس تست بنویسیم. اگر با یونیت تست نوشتن در یونیتی آشنا نیستید میتونید این مقاله را مطالعه کنید.خوب برای این کار من یک تست خیلی ساده می‌نویسم. (این کلاس رو تو دایرکتوری Assets/Editor تولید کردم)using NUnit.Framework;
public class ExampleTest
{
      [Test]
      public void ExampleTestPassing()
      {
            Assert.Pass();
      }
}این تست داره میگه که هر اتفاقی که افتاد تست رو Pass کن. حالا وقتی برگردیم به محیط یونیتی می‌تونیم تست رو انجام بدیم و از صحتش مطمئن بشیم.صحت تست ExampleTestما کار خاصی در این لحظه قرار نیست انجام بدیم و هدفمون اینه که ببینیم تستی که نوشتیم توسط گیت‌لب صحت سنجی میشه یا نه. برای این کار پروژه رو روی گیت‌لب پوش می‌کنیم. فقط قبلش فراموش نشه که فایل gitignore. به پروژه به شکل زیر اضافه بشه.اگر تو محیط ویندوز هستید با git bash میتونید این فایل رو با دستور vi .gitignore بسازید./[Ll]ibrary/
/[Tt]emp/
/[Oo]bj/
/[Bb]uild/
/[Bb]uilds/
/Assets/AssetStoreTools*

ExportedObj/
.consulo/
*.csproj
*.unityproj
*.sln
*.suo
*.tmp
*.user
*.userprefs
*.pidb
*.booproj
*.svd

# Unity3D generated meta files
*.pidb.meta

# Unity3D Generated File On Crash Reports
sysinfo.txt

# Builds
*.apk
*.unitypackageبعدش با git bash  آدرس ریموت و ... رو بزنید و با یه کامیت مثلا Initial commit پروژه رو پوش کنید.git remote add origin https://gitlab.com/......... /unity-ci-project.git  آدرس ریپوزیتوری گیت شما git add -A
git commit -m &amp;quotInitial commit&amp;quot 
git push -u origin masterتنظیمات CI/CD Gitlabبرای اینکه بتونیم از CI/CD گیت‌لب استفاده کنیم باید یک فایل به نام gitlab-ci.yml. در دایرکتوری روت پروژه بسازیم. این فایل هم می‌تونید به همون روشی که توضیح دادم تولید کنید.برای ادیت کردن فایل yml من از vs code استفاده می‌کنم تا بتونم راحت‌تر خطاهای حین کار رو متوجه بشم.برای نوشتن یک فایل yml که گیت‌لب برای CI ازش استفاده می‌کنه دستورهای خاصی وجود داره که می‌تونید از داکیومنت خود گیت‌لب که بهترین مرجع این کاره استفاده کنید.خوب بیاید یک gitlab-ci.yml. ساده درست کنیم.stages:
  - test
  - build
  - deploy
unit-test:
  stage: test
  script: echo &#039;Testing...&#039;
unity-build:
  stage: build
  script: echo &#039;Building...&#039;
unity-deploy:
  stage: deploy
  script: echo &#039;Deploying...&#039;پارامترها: خط فرمان (Pipeline): محیط کاری رو مشخص میکنه و کانکشن بین Stagesو Works هستش.استیج (Stage): هر مرحله رو مشخص میکنهکارها (Jobs): کارهایی که باید انجام بشهتو فایلی که ما نوشتیم 3 تا Stage به نام Test، buildو deploy وجود داره که هر کدوم کار خاصی رو انجام میده.برای مثال ما گفتیم در هنگام تست که استیجی به نام test داره کار unit-test رو انجام بده و Testing.... رو چاپ کنهاین فایل رو به پروژه اضافه می‌کنیم و پروژه رو روی برنچ master پوش می‌کنیم و گیت‌لب رو روی کروم (یا هر Browser دیگه‌ای که استفاده می‌کنید) باز می‌کنیم.git add -A
git commit -m &#039;Added .gitlab-ci.yml&#039;
git push origin masterحالا  وقتی تب CI/CD رو تو پنل گیت‌لب از سمت چپ باز کنیم متوجه می‌شیم که یکسری پروسس‌ها در حال انجامه.گیت‌لب در حال انجام کارهای استیج هایکم که صبر کنیم 3 تا استیجی که معرفی کرده بودیم تیک دار میشن و صحت انجام کار ما مشخص میشه.پاس شدن همه استیج‌هاهمونطور که در عکس بالا می‌بینید Pipeline به کل محیطی که فرآیند Testو Buildو Deploy کردن انجام می‌پذیره گفته میشه و هر استیج به صورت مجزا (مثلا استیج Test) و کارهایی که باید انجام بده ( مثلا unit-test)،  در زیر هر استیج، مشخص شده.برای مثال تصویری از Job استیج Build رو گذاشتم تا ببینیم که کاری که ازش خواسته بودیم یعنی echo کردن Building انجام شده و صحت کار ما مشخص شده.صحت کار استیج Buildپیش بسوی Gitlab Runnerنمیدونم تا به حال اسم Gitlab Runner به گوشتون خورده یا نه. ولی الآن که میخوایم از Gitlab CI/CD استفاده کنیم زیاد ازش می‌شنویم.گیت‌لب رانر به ما کمک میکنه تا سایت گیت‌لب رو به سیستم شخصیمون متصل کنیم و لاگ ها و پروسس‌ها رو روی کامپیوتر خودمون داشته باشیم.اولین کاری که باید انجام بشه اینه که Gitlab Runner مربوط به سیستم عاملتون رو دانلود کنید. راهنمای دانلود و استفاده قدم به قدم از Gitlab Runner  در اینجا قابل مشاهده‌ هست.برای مثال من که از ویندوز 64 بیتی استفاده میکنم از این لینک آخرین نسخه رو دانلود کردم و فایل دانلودی رو در آدرس C:\Gitlab Runner کپی کردم.شروع کار با Gitlab Runner خیلی ساده است. کافیه با cmd به آدرس Gitlab Runner بریم و به صورت زیر عمل کنیم.cd C:\GitLab-Runner
.\gitlab-runner-windows-amd64.exe install
.\gitlab-runner-windows-amd64.exe startو تمام.با این کار Gitlab Runner برای شما نصب و استارت می‌شه. حالا برای وصل کردن پروژه، روی سایت گیت‌لب و گیت‌لب رانر روی سیستم عامل، باید عملیات رجیستر رو انجام بدیم.برای رجیستر کردن به پنل گیت‌لب برید و از تب Setting بخش CI/CD رو پیدا کنید و از اونجا بخش Runner را Expand کنید تا به Set up a specific Runner manually برسید. (یکم راهش صعب العبوره!)در این بخش آموزش قدم به قدم رجیستر کردن گیت‌لب رانر قرار داده شده.بخش مربوط به رجیستر کردن گیت‌لبدر این مرحله با استفاده از cmd به آدرس Gitlab Runner برید و به شکل زیر عمل کنید.تصویر cmd برای رجیستر کردن گیت‌لب دستور gitlab-runner-windows-amd64.exe register رو می‌زنیم آدرس gitlab-ci رو از ما می‌خواد که از بخش دوم Set up a specific Runner manually کپی می‌کنیم.توکن گیت‌لب رو از ما طلب میکنه که بخش سوم Set up a specific Runner manually رو در اینجا کپی می‌کنیم. توضیحات ازمون میخواد که اسم ماشین اجرا کننده‌مون هست (من اسم ماشین رو FirstRunner زدم) اجرا کننده رو باید مشخص کنیم که shell رو انتخاب می‌کنیم.با انجام این کار رجیستر ما با موفقیت انجام میشه. ?بعد از اینکار فایل gitlab-ci.yml. رو باز کنید و تگ unity رو به stage ها اضافه کنید (برای اینکه Gitlab Runner ما فقط کارهایی که تگ یونیتی دارن رو اجرا کنه). و پروژه رو Push کنید تا نتیجه کارها مشخص بشه.stages: 
  - test
  - build
  - deploy
unit-test: 
  script: &amp;quotecho &#039;Testing...&#039;&amp;quot
  stage: test
  tags: 
  - unity
unity-build: 
  script: &amp;quotecho &#039;Building...&#039;&amp;quot
  stage: build
   tags: 
  - unity
unity-deploy: 
  script: &amp;quotecho &#039;Deploying...&#039;&amp;quot
  stage: deploy
   tags: 
  - unityدستورات گیت هم به صورت زیر می‌زنیمgit add -A
 git commit -m &#039;Added unity tag in .gitlab-ci.yml&#039;
 git push origin masterاجرای گیت‌لب روی ماشینی که ساختیمهمینطور که می‌بینید ماشینی که به نام FirstRunner ساختیم و رجیستر کردیم با استفاده از Gitlab Runner درحال اجر است.حالا که تونستیم با موفقیت تمام این کارها رو انجام بدیم، وقتشه که از Command Line های یونیتی کمال استفاده رو ببریم و یونیت تست های edit mode و خروجی گرفتن رو با استفاده از Gitlab انجام بدیم.اگر در مورد Command Line های یونیتی اطلاع ندارید از این لینک استفاده کنید.الآن وقتشه که gitlab-ci.yml. رو باز کنیم و تغییرات لازمه رو انجام بدیم.stages:
   - test
   - build
   - deploy
unit-test:
   script:
   - C:\&amp;quotProgram Files&amp;quot\Unity\Editor\Unity.exe -batchmode -projectPath -runTests -testPlatform editmode -logFile -testResults C:\Logs\UnitTest.log
   stage: test
   tags:
   - unity
build-android:
   script:
   - C:\&amp;quotProgram Files&amp;quot\Unity\Editor\Unity.exe -batchmode -nographics -executeMethod 
BuildScript.Build -projectPath C:\Users\omidfth\Documents\Projects\UnityProjects\unity-ci- project -quit -logFile C:\Logs\AndroidLog.log
   stage: build
   tags:
   - unityمن بخش deploy رو فعلا پاک کردم و یک job به نام build-android اضافه کردم.برای یونیت تست : کارها در بخش script مربوط به خودش نوشته شده. تو این بخش از Command Line های یونیتی استفاده شده به این صورت که یونیتی رو در حالت batchmode اجرا میشه و یونیت تست های editmode رو انجام میده و لاگش رو در مسیری که مشخص کردیم می‌ریزه.در قسمت build-android هم اسکریپت مربوط به build گرفتن اندروید وجود داره. در Command Line های یونیتی بخشی با عنوان executeMethod وجود داره که مشخص می‌کنه چه متدی از پروژه موقع اجرا یونیتی ران بشه. به عنوان مثال من یک کلاس به نام BuildScript.cs در دایرکتوری Assets/Editor درست کردم و یک متد استاتیک به نام Build رو صدا زدم. در قسمت projectPath هم من آدرس پروژه که قراره خروجی گرفته بشه رو دادم.کلاس BuildScript.cs به شکل زیر نوشته می‌شه.using System;
using UnityEditor;
public class BuildScript
{
      public static readonly string[] scenes = { &amp;quotAssets/Scenes/main.unity&amp;quot,};
      public static void Build()
      {
            Console.Write(&amp;quotbuild for Android&amp;quot);
            BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions
            {
                  scenes = scenes, // add scenes
                  locationPathName = &amp;quot./builds/android.apk&amp;quot, //set location
                  target = BuildTarget.Android, // build for Android
                  options = BuildOptions.None // set default for others
            };
            BuildPipeline.BuildPlayer(buildPlayerOptions); //build
      }
}در این متد گفته شده که بعد از خروجی گرفتن فایل apk ای به نام android.apk در مسیر builds/android.apk (کنار فولدر Assets) ساخته بشه.بعد از این کار پروژه رو کامیت و پوش می‌کنیم روی گیت‌لب و منتظر نتیجه می‌مونیم. تو این لحظه پروژه تستی ما در حال Build گرفتن هست. بعد از پاس شدن همه job ها به آدرس پروژه‌تون برید و بیلد اندرویدتون رو پیدا کنید.خروجی اندروید گرفته شده از پروژه توسط گیت‌لبسپس برنامه رو نصب می‌کنیم تا از صحت خروجی مطمئن بشیم.نصب unity-ci-project روی شبیه ساز memuهمینطور که در تصویر مشاهده می‌کنید unity-ci-project به درستی نصب و اجرا شد.تبریک میگم.شما تا اینجای کار تونستید یک فرآیند اتوماتیک تست و خروجی گرفتن رو انجام بدید و هر بار که پروژه رو روی گیت‌لب پوش و با برنچ master مرج می‌کنید تمام این اتفاقات به صورت اتوماتیک میوفته و اگر وسط کار مشکلی پیش بیاد درجا بهتون ایمیل میاد که یه جای کار مشکل خورده.امیدوارم این مقاله تونسته باشه به درکتون از CI و اتومیت کردن توسعه بازی کمک کرده باشه.</description>
                <category>امید فتح الله زاده</category>
                <author>امید فتح الله زاده</author>
                <pubDate>Mon, 06 Apr 2020 22:54:26 +0430</pubDate>
            </item>
                    <item>
                <title>یونیت تست در یونیتی</title>
                <link>https://virgool.io/coderlife/%DB%8C%D9%88%D9%86%DB%8C%D8%AA-%D8%AA%D8%B3%D8%AA-%D8%AF%D8%B1-%DB%8C%D9%88%D9%86%DB%8C%D8%AA%DB%8C-fnuojhjvbmqf</link>
                <description>اول از همه باید بگم کسایی که قصد مطالعه این مقاله رو دارن نیاز به دانش عمومی در مورد برنامه نویس C# و آشنایی با موتور بازی سازی یونیتی دارن.قبل از اینکه با Unit Test نوشتن در Unity آشنا بشیم بهتره تعریف کلی یونیت تست و اهمیت آن در برنامه نویسی رو با هم مرور کنیم. (البته شاید شما که این مطلب رو مطالعه می‌کنید با مفهوم Unit Test آشنا باشید)یکی از مفاهیمی که در تعریف Unit Test مورد استفاده قرار میگیره، Unit of Code است. Unit of Code به معنی بلوک یا جزئی از کد است. حالا اگر قصد داریم زیرمجموعه ای از کد، در یک پروژه نرم‌افزاری رو مورد تست قرار بدیم، بهش میگن Unit Test.پس به طور کلی به مجموعه ای از کدها که Unit of code رو مورد بررسی و تست قرار میده Unit Test گفته میشه.سناریو بدون Unit Testسناریو با Unit Testخوب حالا وقتشه بریم سراغ موتور بازی سازی یونیتی و نحوه یونیت تست نوشتن در این موتور.در یونیتی دو مود برای تست کردن وجود دارد. اولین مود Edit Mode هست که برای تست اسکریپت‌ها، یونیت ‌کدها، توابع، لاجیک، ادیتور، پلاگین‌ها و ... مورد استفاده قرار می‌گیره. مود دوم هم Play Mode هست که برای تست پریفب‌ها، هوش مصنوعی، یک پارچگی، عملکرد و  ... مورد استفاده قرار می‌گیره.برای شروع به کار از مسیر زیر Unit Test یونیتی را فعال کنید.Window --&gt; General --&gt; Test Runnerهمانطور که مشاهده می‌کنید دو حالت PlayMode و EditMode وجود دارد که با دوبار کلیک کردن بر روی اسکریپت ها یا زدن بر روی Run Selected و یا Run All تست به اجرا در خواهد آمد.نمایی از Test Runner یونیتیحالا وقتشه که یکمی کد بنویسیم.فرض کنید ما یک کلاس به نام Army داریم که در کانستراکتور کلاس، میزان Damage و Health جای‌گذاری میشه. به صورت زیر:public class Army
{
    public int damage;
    public int health;

    public Army(int damage, int health)
    {
        this.damage = damage;
        this.health = health;
    }
}حالا قصد داریم یک تست برای این کلاس بنویسیم تا مطمئن بشیم کانستراکتور کلاس به درستی کار می‌کنه!از مسیر زیر و در فولدر Editor که در یونیتی میسازیم یک کلاس تست تهیه کنید.Create --&gt; Testing --&gt; C# Test Scriptپس از ساخته شدن کلاس تست مشاهده می کنید که به صورت پیش فرض کدهایی به اسکریپت شما اضافه شده. اتریبیوت هایی که در اسکریپت های مربوط به یونیت تست مورد استفاده قرار می گیرند در ادامه توضیح داده می‌شود. (توجه کنید در این آموزش همه موارد مربوط به یونیت تست قرار نگرفته و این آموزش پلی است برای آشنایی اولیه شما با نحوه نگارش یونیت تست در یونیتی و اتصال شما به داکیومنت‌های یونیتی!)برای تست به صورت پیش فرض دو اتریبیوت [Test] و [UnityTest] در کلاس موجود است که اتریبیوت اول جهت تست در لحظه اول و اتربیوت UnityTest جهت تست در یک IEnumerator ، که با یک فاصله زمانی مشخص انجام می‌پذیرد.برای شروع کد زیر را می‌نویسیم using System.Collections;
 using NUnit.Framework;
 using UnityEngine.TestTools;
 
 namespace Tests
{
  public class ArmyTestScript
  {
  [Test]
  public void ArmyTestScriptSimplePasses()
  {
  Army army = new Army(100,50);
  Assert.IsTrue(army.damage==100);
  Assert.IsTrue(army.health==50);
  }
 
  [UnityTest]
  public IEnumerator ArmyTestScriptWithEnumeratorPasses()
  {
  yield return null;
  }
  }
}
همانطور که در کد بالا مشاهده می کنید در متد مربوط به اتریبیوت Test، کلاس Army را new می‌کنیم و در ادامه با استفاده از کد Assert.IsTrue چک می‌کنیم کانستراکتور ما درست عمل کرده یا خیر.برای تست می‌تونید وارد محیط یونیتی شده و در پنجره Test Runner بر روی اسکریپتی که به نام کلاس تست شما تولید شده، دوبار کلیک کنید تا از درستی تست خود مطمئن بشید.حالا قصد داریم بعد از مدت 3 ثانیه از لحظه اجرا هم این موارد تست بشه.به این جهت کد یونیت تست را به صورت زیر تغییر می‌دهیم. using System;
 using System.Collections;
 using NUnit.Framework;
 using UnityEngine.TestTools;
 
 namespace Tests
{
  public class ArmyTestScript
  {
  private static Army army = new Army(100,50);
  [Test]
  public void ArmyTestScriptSimplePasses()
  {
  // Use the Assert class to test conditions
  Assert.IsTrue(army.damage==100);
  Assert.IsTrue(army.health==50);
  }
 
  [UnityTest]
  public IEnumerator ArmyTestScriptWithEnumeratorPasses()
  {
  DateTime startTime = System.DateTime.UtcNow;
  while ((System.DateTime.UtcNow - startTime).TotalSeconds &lt; 3)
  {
  yield return null;
  }
  Assert.IsTrue(army.damage==100);
  Assert.IsTrue(army.health==50);
  }
 }
}برای تست این مورد هم به روش قبل عمل می‌کنیم.تست کردن بعد از 3 ثانیه از آغاز تستدر ادامه با دو اتریبیوت SetUp و TearDown آشنا می‌شیم. اتریبیوت SetUp در ابتدای تست، صدا زده میشود و TearDown نیز در انتهای هر تستبرای آشنایی بیشتر با این اتریبیوت‌ می‌تونید کد تست خود را به صورت زیر تغییر بدید و نتیجه تست را مشاهده کنید.using System;
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;

namespace Tests
{
    public class ArmyTestScript
    {
        private static Army army = null;

        [SetUp]
        public void SetUp()
        {
            if (army == null)
            {
                Debug.LogWarning(&amp;quotSetUp&amp;quot);
                army = new Army(100,50);
            }
        }



        [TearDown]
        public void TearDown()
        {
            Debug.LogWarning(&amp;quotTearDown&amp;quot);
        }

        [Test]
        public void ArmyTestScriptSimplePasses()
        {
            Assert.IsTrue(army.damage==100);
            Assert.IsTrue(army.health==50);
        }

        [UnityTest]
        public IEnumerator ArmyTestScriptWithEnumeratorPasses()
        {
            DateTime startTime = System.DateTime.UtcNow;
            while ((System.DateTime.UtcNow - startTime).TotalSeconds &lt; 3)
            {
                yield return null;
            }
            Assert.IsTrue(army.damage==100);
            Assert.IsTrue(army.health==50);
        }
    }
}بعد از تست کد بالا مشاهده خواهید کرد در ابتدا کلاس Army ساخته شده و در انتهای هر تست TearDown یکبار صدا زده میشه.یک نکته هم بگم که اگر قصد چاپ کردن ارور در تست رو دارید با استفاده از Debug.LogError به تنهایی نمی‌تونید ارور چاپ کنید و تست شما بعد از چاپ ارور قطع میشه. برای استفاده از Debug.LogError باید به صورت زیر عمل کنیم.LogAssert.Expect(LogType.Error, &amp;quotError.&amp;quot);
Debug.LogError(&amp;quotError.&amp;quot);دقت داشته باشید برای هر بار استفاده از Debug.LogError باید از LogAssert.Expect در بالای اون خط کد استفاده بشه.آموزش من دراینجا تموم شد ولی یونیت تست نوشتن در یونیتی موارد بیشتری رو شامل می‌شه. برای ادامه می‌تونید در مورد Assembly Definition و نحوه یونیت تست نوشتن برای Editor یونیتی و موارد دیگه اطلاعات کسب کنید.موفق باشید :)</description>
                <category>امید فتح الله زاده</category>
                <author>امید فتح الله زاده</author>
                <pubDate>Fri, 04 Oct 2019 21:30:23 +0330</pubDate>
            </item>
            </channel>
</rss>