gpt4 book ai didi

c# - 如何在 Unity 游戏引擎平台上的单元测试中实例化 MonoBehaviour 对象

转载 作者:行者123 更新时间:2023-12-02 13:35:35 32 4
gpt4 key购买 nike

我在 Github 上有以下开源项目 ( game project )。我目前正在尝试对我使用 MSTest 框架编写的代码进行单元测试,但所有测试都返回相同的错误消息:“未处理的异常:System.Security.SecurityException:ECall 方法必须打包到系统模块中。”当我尝试使用 NUnit 模板进行单元测试时,发生了这种情况。

我浏览过 ECall methods post must be packaged找到一些答案,但我没有,因为OP说他的解决方案在调试器区域内有效,但在调试器区域外无效。就我而言,在查看该帖子时,OP 的问题尚未解决。

之后,我将 UnityTestTools 框架导入到我的项目中。我认为这很容易,因为它基于 NUnit 框架。事实证明没有。该测试本身是相当基础的。我有一个名为 BaseCharacterClass:MonoBehavior 的基类,除其他外,它还具有 BaseCharacterStats 类型的属性。在统计数据中,有一个CharacterHealth类型的对象,它负责照顾玩家的健康状况。

现在,我有以下两个堆栈跟踪,当我在测试中尝试以下操作时,我似乎没有得到它们。

单元测试 (NUNIT)

  1. 使用 new 关键字创建 MonoBehavior 对象

    [Test]
    [Category("Mock Character")]
    public void Mock_Character_With_No_Health()
    {
    var mock = new MoqBaseCharacter ();
    Assert.NotNull (mock.BaseStats);
    Assert.NotNull (mock.BaseStats.Health);
    Assert.LessOrEqual (0, mock.BaseStats.Health.CurrentHealth);
    }
    //This is not the full file
    //There "2" classes: 1 for holding tests and that Mock object
    public MoqBaseCharacter()
    {
    this.BaseStats = new BaseCharacterStats ();
    this.BaseStats.Health = new CharacterHealth (0);
    }

堆栈跟踪:

Mock_Character_With_No_Health (0.047s) --- System.NullReferenceException : Object reference not set to an instance of an object --- at Assets.Scripts.CharactersUtil.CharacterHealth..ctor (Int32 sh) [0x0002f] in C:\Users\Kevin\Documents\AndroidPC_Prototype\PC_Augmented_Tactics_Demo\Assets\Scripts\CharactersUtil\CharacterHealth.cs:29

at UnityTest.MoqBaseCharacter..ctor () [0x00011] in C:\Users\Kevin\Documents\AndroidPC_Prototype\PC_Augmented_Tactics_Demo\Assets\UnityTestTools\Examples\UnitTestExamples\Editor\SampleTests.cs:14

at UnityTest.SampleTests.Mock_Character_With_No_Health () [0x00000] in C:\Users\Kevin\Documents\AndroidPC_Prototype\PC_Augmented_Tactics_Demo\Assets\UnityTestTools\Examples\UnitTestExamples\Editor\SampleTests.cs:32

  • 使用 NSubstitute.For

    [Test]
    [Category("Mock Character")]
    public void Mock_Character_With_No_Health()
    {
    var mock = NSubstitute.Substitute.For<MoqBaseCharacter> ();
    Assert.NotNull (mock.BaseStats);
    Assert.NotNull (mock.BaseStats.Health);
    Assert.LessOrEqual (0, mock.BaseStats.Health.CurrentHealth);
    }
  • 堆栈跟踪

    Mock_Character_With_No_Health (0.137s) --- System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation. ----> System.NullReferenceException : Object reference not set to an instance of an object --- at System.Reflection.MonoCMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x0012c] in /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Reflection/MonoMethod.cs:519

    at System.Reflection.MonoCMethod.Invoke (BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00000] in /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Reflection/MonoMethod.cs:528

    at System.Activator.CreateInstance (System.Type type, BindingFlags bindingAttr, System.Reflection.Binder binder, System.Object[] args, System.Globalization.CultureInfo culture, System.Object[] activationAttributes) [0x001b8] in /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System/Activator.cs:338

    at System.Activator.CreateInstance (System.Type type, System.Object[] args, System.Object[] activationAttributes) [0x00000] in /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System/Activator.cs:268

    at System.Activator.CreateInstance (System.Type type, System.Object[] args) [0x00000] in /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System/Activator.cs:263

    at Castle.DynamicProxy.ProxyGenerator.CreateClassProxyInstance (System.Type proxyType, System.Collections.Generic.List`1 proxyArguments, System.Type classToProxy, System.Object[] constructorArguments) [0x00000] in :0

    at Castle.DynamicProxy.ProxyGenerator.CreateClassProxy (System.Type classToProxy, System.Type[] additionalInterfacesToProxy, Castle.DynamicProxy.ProxyGenerationOptions options, System.Object[] constructorArguments, Castle.DynamicProxy.IInterceptor[] interceptors) [0x00000] in :0

    at NSubstitute.Proxies.CastleDynamicProxy.CastleDynamicProxyFactory.CreateProxyUsingCastleProxyGenerator (System.Type typeToProxy, System.Type[] additionalInterfaces, System.Object[] constructorArguments, IInterceptor interceptor, Castle.DynamicProxy.ProxyGenerationOptions proxyGenerationOptions) [0x00000] in :0

    at NSubstitute.Proxies.CastleDynamicProxy.CastleDynamicProxyFactory.GenerateProxy (ICallRouter callRouter, System.Type typeToProxy, System.Type[] additionalInterfaces, System.Object[] constructorArguments) [0x00000] in :0

    at NSubstitute.Proxies.ProxyFactory.GenerateProxy (ICallRouter callRouter, System.Type typeToProxy, System.Type[] additionalInterfaces, System.Object[] constructorArguments) [0x00000] in :0

    at NSubstitute.Core.SubstituteFactory.Create (System.Type[] typesToProxy, System.Object[] constructorArguments, SubstituteConfig config) [0x00000] in :0

    at NSubstitute.Core.SubstituteFactory.Create (System.Type[] typesToProxy, System.Object[] constructorArguments) [0x00000] in :0

    at NSubstitute.Substitute.For (System.Type[] typesToProxy, System.Object[] constructorArguments) [0x00000] in :0

    at NSubstitute.Substitute.For[MoqBaseCharacter] (System.Object[] constructorArguments) [0x00000] in :0

    at UnityTest.SampleTests.Mock_Character_With_No_Health () [0x00000] in C:\Users\Kevin\Documents\AndroidPC_Prototype\PC_Augmented_Tactics_Demo\Assets\UnityTestTools\Examples\UnitTestExamples\Editor\SampleTests.cs:32 --NullReferenceException

    at Assets.Scripts.CharactersUtil.CharacterHealth..ctor (Int32 sh) [0x0002f] in C:\Users\Kevin\Documents\AndroidPC_Prototype\PC_Augmented_Tactics_Demo\Assets\Scripts\CharactersUtil\CharacterHealth.cs:29

    at UnityTest.MoqBaseCharacter..ctor () [0x00011] in C:\Users\Kevin\Documents\AndroidPC_Prototype\PC_Augmented_Tactics_Demo\Assets\UnityTestTools\Examples\UnitTestExamples\Editor\SampleTests.cs:14

    at Castle.Proxies.MoqBaseCharacterProxy..ctor (ICallRouter , Castle.DynamicProxy.IInterceptor[] ) [0x00000] in :0

    at (wrapper managed-to-native) System.Reflection.MonoCMethod:InternalInvoke (object,object[],System.Exception&)

    at System.Reflection.MonoCMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00119] in /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Reflection/MonoMethod.cs:513

    免责声明

    快速阅读NSubstitute告诉我,我应该更好地使用接口(interface)来代替...在我的情况下,我真的不知道接口(interface)如何更适合我的代码。如果有人对此有想法而不是使用 new 关键字,我会全力以赴!最后,这是 BaseCharacter、BaseStats 和 Health 的源代码

    基本角色实现

    using System;
    using UnityEngine;
    using System.Collections.Generic;
    using JetBrains.Annotations;
    using Random = System.Random;

    namespace Assets.Scripts.CharactersUtil
    {
    public class BaseCharacterClass : MonoBehaviour
    {
    //int[] basicUDLRMovementArray = new int[4];

    public List<BaseCharacterClass> CurrentEnnemies;
    public int StartingHealth = 500;
    public BaseCharacterStats BaseStats { get; set; }

    // Use this for initialization
    private void Start()
    {
    BaseStats = new BaseCharacterStats {Health = new CharacterHealth(StartingHealth)}; //Testing purposes
    BaseStats.ChanceForCriticalStrike = new Random().Next(0,BaseStats.CriticalStrikeCounter);
    }

    // Update is called once per frame

    private void Update()
    {
    //ExecuteBasicMovement();

    }

    //During an attack with any kind of character
    //TODO: Make sure that people from the same team cannot attack themselves (friendly fire)
    private void OnTriggerEnter([NotNull] Collider other)
    {
    if (other == null) throw new ArgumentNullException(other.tag);
    Debug.Log("I'm about to receive some damage");
    var characterStats = other.gameObject.GetComponent<BaseCharacterClass>().BaseStats;
    var heathToAddOrRemove = other.gameObject.tag == "Healer" || other.gameObject.tag == "AIHealer" ? characterStats.Power : -1 * characterStats.Power;
    characterStats.Health.TakeDamageFromCharacter((int)heathToAddOrRemove);
    Debug.Log("I should have received damage from a bastard");
    if (characterStats.Health.CurrentHealth == 500)
    {
    Debug.Log("This is a mistake, I believe I'm a god! INVICIBLE");
    }
    }

    /*
    public void ExecuteBasicMovement()
    {
    var move = new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"), 0);
    transform.position += move * BaseStats.Speed * Time.deltaTime;
    }

    //TODO: Make sure players moves correctly within the environment per cases
    public void ExecuteMovementPerCase()
    {
    }
    */

    public bool CanDoExtraDamage()
    {
    if (BaseStats.ChanceForCriticalStrike*BaseStats.Luck < 50) return false;
    BaseStats.CriticalStrikeCounter--;
    BaseStats.ChanceForCriticalStrike = new Random().Next(0, BaseStats.CriticalStrikeCounter);
    BaseStats.AjustCriticalStrikeChances();
    return true;
    }
    }
    }

    基础统计数据

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using JetBrains.Annotations;

    namespace Assets.Scripts.CharactersUtil
    {
    public class BaseCharacterStats
    {
    public float Power { get; set; }
    public float Defense { get; set; }
    public float Agility { get; set; }
    public float Speed { get; set; }
    public float MagicPower { get; set; }
    public float MagicResist { get; set; }
    public int ChanceForCriticalStrike;
    public int Luck { get; set; }
    public int CriticalStrikeCounter = 20;
    public int TemporaryDefenseBonusValue;
    private Random _randomValueGenerator;

    public BaseCharacterStats()
    {
    _randomValueGenerator= new Random();
    }

    [NotNull]
    public CharacterHealth Health
    {
    get { return _health; }
    set { _health = value; }
    }
    private CharacterHealth _health;

    public void AjustCriticalStrikeChances()
    {
    if (CriticalStrikeCounter <= 5)
    {
    CriticalStrikeCounter = 5;
    }
    }

    public int DetermineDefenseBonusForTurn()
    {
    TemporaryDefenseBonusValue = _randomValueGenerator.Next(10,20);
    return TemporaryDefenseBonusValue;
    }
    }
    }

    健康

    using JetBrains.Annotations;
    using UnityEngine;
    using UnityEngine.UI;

    namespace Assets.Scripts.CharactersUtil
    {
    public class CharacterHealth {
    public int StartingHealth { get; set; }
    public int CurrentHealth { get; set; }
    public Slider HealthSlider { get; set; }
    public bool isDead;
    public Color MaxHealthColor = Color.green;
    public Color MinHealthColor = Color.red;
    private int _counter;
    private const int MaxHealth = 200;
    public Image Fill;


    private void Awake() {
    //HealthSlider = GameObject.GetComponent<Slider>();
    _counter = MaxHealth; // just for testing purposes
    }
    // Use this for initialization

    public CharacterHealth(int sh)
    {
    StartingHealth = sh;
    CurrentHealth = StartingHealth;
    HealthSlider.wholeNumbers = true;
    HealthSlider.minValue = 0f;
    HealthSlider.maxValue = StartingHealth;
    HealthSlider.value = CurrentHealth;
    }

    public void Start()
    {
    HealthSlider.wholeNumbers = true;
    HealthSlider.minValue = 0f;
    HealthSlider.maxValue = MaxHealth;
    HealthSlider.value = MaxHealth;
    }

    public void TakeDamageFromCharacter([NotNull] BaseCharacterClass baseCharacter)
    {
    CurrentHealth -= (int)baseCharacter.BaseStats.Power;
    HealthSlider.value = CurrentHealth;
    UpdateHealthBar ();
    if (CurrentHealth <= 0)
    isDead = true;
    }

    public void TakeDamageFromCharacter(int characterStrength)
    {
    CurrentHealth -= characterStrength;
    HealthSlider.value = CurrentHealth;
    UpdateHealthBar ();
    if (CurrentHealth <= 0)
    isDead = true;
    }

    public void RestoreHealth(BaseCharacterClass bs)
    {
    CurrentHealth += (int)bs.BaseStats.Power;
    HealthSlider.value = CurrentHealth;
    UpdateHealthBar ();
    }
    public void RestoreHealth(int characterStrength)
    {
    CurrentHealth += characterStrength;
    HealthSlider.value = CurrentHealth;
    UpdateHealthBar ();
    }
    public void UpdateHealthBar() {
    Fill.color = Color.Lerp(MinHealthColor, MaxHealthColor, (float)CurrentHealth / MaxHealth);
    }
    }
    }

    最佳答案

    还有另一个选项可以在不调用构造函数的情况下对 MonoBehaviours 进行单元测试(使用 FormatterServices)。这是一个创建可测试的 MonoBehaviours 的小帮助器类:

    public static class TestableObjectFactory {
    public static T Create<T>() {
    return FormatterServices.GetUninitializedObject(typeof(T)).CastTo<T>();
    }
    }

    用法:

    var testableObject = TestableObjectFactory.Create<MyMonoBehaviour>();
    testableObject.Test();

    关于c# - 如何在 Unity 游戏引擎平台上的单元测试中实例化 MonoBehaviour 对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33094653/

    32 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com