یونیت تست در یونیتی

اول از همه باید بگم کسایی که قصد مطالعه این مقاله رو دارن نیاز به دانش عمومی در مورد برنامه نویس 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


سناریو با Unit Test
سناریو با Unit Test


خوب حالا وقتشه بریم سراغ موتور بازی سازی یونیتی و نحوه یونیت تست نوشتن در این موتور.
در یونیتی دو مود برای تست کردن وجود دارد. اولین مود Edit Mode هست که برای تست اسکریپت‌ها، یونیت ‌کدها، توابع، لاجیک، ادیتور، پلاگین‌ها و ... مورد استفاده قرار می‌گیره. مود دوم هم Play Mode هست که برای تست پریفب‌ها، هوش مصنوعی، یک پارچگی، عملکرد و ... مورد استفاده قرار می‌گیره.

برای شروع به کار از مسیر زیر Unit Test یونیتی را فعال کنید.
Window --> General --> Test Runner

همانطور که مشاهده می‌کنید دو حالت PlayMode و EditMode وجود دارد که با دوبار کلیک کردن بر روی اسکریپت ها یا زدن بر روی Run Selected و یا Run All تست به اجرا در خواهد آمد.

نمایی از Test Runner یونیتی
نمایی از 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 --> Testing --> 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 < 3)
  {
  yield return null;
  }
  Assert.IsTrue(army.damage==100);
  Assert.IsTrue(army.health==50);
  }
 }
}

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

تست کردن بعد از 3 ثانیه از آغاز تست
تست کردن بعد از 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(&quotSetUp&quot);
                army = new Army(100,50);
            }
        }



        [TearDown]
        public void TearDown()
        {
            Debug.LogWarning(&quotTearDown&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 < 3)
            {
                yield return null;
            }
            Assert.IsTrue(army.damage==100);
            Assert.IsTrue(army.health==50);
        }
    }
}

بعد از تست کد بالا مشاهده خواهید کرد در ابتدا کلاس Army ساخته شده و در انتهای هر تست TearDown یکبار صدا زده میشه.

یک نکته هم بگم که اگر قصد چاپ کردن ارور در تست رو دارید با استفاده از Debug.LogError به تنهایی نمی‌تونید ارور چاپ کنید و تست شما بعد از چاپ ارور قطع میشه. برای استفاده از Debug.LogError باید به صورت زیر عمل کنیم.

LogAssert.Expect(LogType.Error, &quotError.&quot);
Debug.LogError(&quotError.&quot);

دقت داشته باشید برای هر بار استفاده از Debug.LogError باید از LogAssert.Expect در بالای اون خط کد استفاده بشه.

آموزش من دراینجا تموم شد ولی یونیت تست نوشتن در یونیتی موارد بیشتری رو شامل می‌شه. برای ادامه می‌تونید در مورد Assembly Definition و نحوه یونیت تست نوشتن برای Editor یونیتی و موارد دیگه اطلاعات کسب کنید.

موفق باشید :)