InputSystem を用いたテストをRunAllした際にテストが失敗する対策

# 概要
TestRunner にて InputSystem を用いた入力処理のテストを InputTestFixture を継承したテストクラスを用いて行った際
個別実行したときには全てのテストが通過するにもかかわらず RunAll で実行すると最初のテスト以外すべて失敗するという現象にぶちあったのでメモ  


## 構成
Unityバージョン : 6000.0.31.f1  
TestFrameworkバージョン : 1.4.5  
InputSystemバージョン : 1.11.2  


# 現象の詳細

以下のコードのテスト、コード量が多くなるので二つのテストのみ   
```cs
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using UnityEngine.SceneManagement;
using UnityEngine.InputSystem;
using UnityEditor.SceneManagement;
    
    
namespace Game.Player.Tests
{
    public class PlayerMoveTests : InputTestFixture
    {

        private Keyboard _keyboard;
        
        [SetUp]
        public override void Setup()
        {
            base.Setup();
            if (_keyboard == null)
            {
                Debug.Log("初期設定");
                _keyboard = InputSystem.AddDevice<UnityEngine.InputSystem.Keyboard>();
            }
        }


        [TearDown]
        public override void TearDown()
        {
            base.TearDown();
        }


        [Description("上下左右の矢印キー入力で正常に InputMove に値が入るか.")]
        [UnityTest]
        public IEnumerator Test_PlayerMove_ArrowKeys_Correct()
        {
            yield return LoadTestScene();
            yield return new WaitForSeconds(1f);

            var player = GameObject.FindObjectsByType<Player>(FindObjectsSortMode.None)[0];

            // 上下左右の入力通りに InputMove に値が入るか否か.
            Press(_keyboard.upArrowKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.up);
            Release(_keyboard.upArrowKey);
            InputSystem.Update();

            Press(_keyboard.downArrowKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.down);
            Release(_keyboard.downArrowKey);
            InputSystem.Update();
            
            Press(_keyboard.leftArrowKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.left);
            Release(_keyboard.leftArrowKey);
            InputSystem.Update();

            Press(_keyboard.rightArrowKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.right);
            Release(_keyboard.rightArrowKey);
            
            GameObject.DestroyImmediate(player.gameObject);
        }

        
        [Description("WASD キー入力で正常に InputMove に値が入るか.")]
        [UnityTest]
        public IEnumerator Test_PlayerMove_WASDKeys_Correct()
        {           
            yield return LoadTestScene();
            yield return new WaitForSeconds(1f);

            var player = GameObject.FindObjectsByType<Player>(FindObjectsSortMode.None)[0];

            // 上下左右の入力通りに InputMove に値が入るか否か.
            Press(_keyboard.wKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.up);
            Release(_keyboard.wKey);
            InputSystem.Update();

            Press(_keyboard.sKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.down);
            Release(_keyboard.sKey);
            InputSystem.Update();
            
            Press(_keyboard.aKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.left);
            Release(_keyboard.aKey);
            InputSystem.Update();

            Press(_keyboard.dKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.right);
            Release(_keyboard.dKey);
            
            GameObject.DestroyImmediate(player.gameObject);
        }
        
        // コード量が多くなるので省略.

        private AsyncOperation LoadTestScene()
        {
            return EditorSceneManager.LoadSceneAsyncInPlayMode("Assets/Game/Scripts/Player/Tests/PlayMode/TestScenes/PlayerTestScene.unity", new LoadSceneParameters(LoadSceneMode.Single));
        }
    }
}
```

このコードでテストを RunAll すると以下の画像のように先頭のテストのみ成功し、後ろのテストは全て失敗する現象が発生した

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212999/df3a9e57-d09a-14dc-00ac-0dc36c86bb90.png)

エラーコード  
```
ArgumentOutOfRangeException: Cannot be negative
Parameter name: value
```


似たような不具合が発生していたようだがこちらでは解決していなかった  
https://discussions.unity.com/t/error-when-running-all-unit-tests/886736/7  

https://discussions.unity.com/t/i-cannot-make-unity-test-framework-work-with-inputtestfixture/893192/2  

ただ、この中でシーン上にある PlayerInput を含むオブジェクトを削除したらうまくいくとの書き込みがあり試しにシーン上にあるオブジェクトを TearDown 時に削除するようにしてみた   

# 試行錯誤 ( テストは失敗する )

```cs
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using UnityEngine.SceneManagement;
using UnityEngine.InputSystem;
using UnityEditor.SceneManagement;


namespace Game.Player.Tests
{
    public class PlayerMoveTests : InputTestFixture
    {

        private Keyboard _keyboard;
        

        [SetUp]
        public override void Setup()
        {
            base.Setup();
            if (_keyboard == null)
            {
                _keyboard = InputSystem.AddDevice<UnityEngine.InputSystem.Keyboard>();
            }
        }


        [TearDown]
        public override void TearDown()
        {
            CleanUp();
            base.TearDown();
        }


        [Description("上下左右の矢印キー入力で正常に InputMove に値が入るか.")]
        [UnityTest]
        public IEnumerator Test_PlayerMove_ArrowKeys_Correct()
        {
            yield return LoadTestScene();
            yield return new WaitForSeconds(1f);

            var player = GameObject.FindObjectsByType<Player>(FindObjectsSortMode.None)[0];

            // 上下左右の入力通りに InputMove に値が入るか否か.
            Press(_keyboard.upArrowKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.up);
            Release(_keyboard.upArrowKey);
            InputSystem.Update();

            Press(_keyboard.downArrowKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.down);
            Release(_keyboard.downArrowKey);
            InputSystem.Update();
            
            Press(_keyboard.leftArrowKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.left);
            Release(_keyboard.leftArrowKey);
            InputSystem.Update();

            Press(_keyboard.rightArrowKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.right);
            Release(_keyboard.rightArrowKey);
        }

        
        [Description("WASD キー入力で正常に InputMove に値が入るか.")]
        [UnityTest]
        public IEnumerator Test_PlayerMove_WASDKeys_Correct()
        {           
            yield return LoadTestScene();
            yield return new WaitForSeconds(1f);

            var player = GameObject.FindObjectsByType<Player>(FindObjectsSortMode.None)[0];

            // 上下左右の入力通りに InputMove に値が入るか否か.
            Press(_keyboard.wKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.up);
            Release(_keyboard.wKey);
            InputSystem.Update();

            Press(_keyboard.sKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.down);
            Release(_keyboard.sKey);
            InputSystem.Update();
            
            Press(_keyboard.aKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.left);
            Release(_keyboard.aKey);
            InputSystem.Update();

            Press(_keyboard.dKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.right);
            Release(_keyboard.dKey);
        }
        

        // 省略.        

        private AsyncOperation LoadTestScene()
        {
            return EditorSceneManager.LoadSceneAsyncInPlayMode("Assets/Game/Scripts/Player/Tests/PlayMode/TestScenes/PlayerTestScene.unity", new LoadSceneParameters(LoadSceneMode.Single));
        }

        
        // シーン上にあるオブジェクトを全て破棄する.
        private void CleanUp()
        {
            var activeScene = SceneManager.GetActiveScene();
            foreach (var go in activeScene.GetRootGameObjects())
            {
                GameObject.DestroyImmediate(go);
            }
        }
    }
}
```

このコードにしてもエラーが出てテストが失敗したのであきらめかけていたがエラーコードの内容が  
```
ArgumentOutOfRangeException: Cannot be negative
Parameter name: value
```
から
```
System.ArgumentNullException : Value cannot be null.
Parameter name: source
```
に変わっていたので、シーン上にあるオブジェクトを削除したのが原因 -> _keyboard が null のままになってると推測を立てて   
SetUp() 時に毎回 _keyboard に値を入れ直すようにしたところ RunAll でテストが全て通過するようになった   

```cs
        [SetUp]
        public override void Setup()
        {
            base.Setup();
            //if (_keyboard == null)
            //{
                _keyboard = InputSystem.AddDevice<UnityEngine.InputSystem.Keyboard>();
            //}
        }


```
2回目以降は _keyboard == null で _keyboard 自体は null じゃなくて内部で利用している何かしらのインスタンスが null になってた?  
ような挙動っぽい    

# 解決したコード   

```cs
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using UnityEngine.SceneManagement;
using UnityEngine.InputSystem;
using UnityEditor.SceneManagement;


namespace Game.Player.Tests
{
    public class PlayerMoveTests : InputTestFixture
    {

        private Keyboard _keyboard;
        
        
        [SetUp]
        public override void Setup()
        {
            base.Setup();
            _keyboard = InputSystem.AddDevice<UnityEngine.InputSystem.Keyboard>();
        }


        [TearDown]
        public override void TearDown()
        {
            // TearDown前にシーン上のオブジェクトを全て破棄.
            CleanUp();
            base.TearDown();
        }


        [Description("上下左右の矢印キー入力で正常に InputMove に値が入るか.")]
        [UnityTest]
        public IEnumerator Test_PlayerMove_ArrowKeys_Correct()
        {
            yield return LoadTestScene();
            yield return new WaitForSeconds(1f);

            var player = GameObject.FindObjectsByType<Player>(FindObjectsSortMode.None)[0];

            // 上下左右の入力通りに InputMove に値が入るか否か.
            Press(_keyboard.upArrowKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.up);
            Release(_keyboard.upArrowKey);
            InputSystem.Update();

            Press(_keyboard.downArrowKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.down);
            Release(_keyboard.downArrowKey);
            InputSystem.Update();
            
            Press(_keyboard.leftArrowKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.left);
            Release(_keyboard.leftArrowKey);
            InputSystem.Update();

            Press(_keyboard.rightArrowKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.right);
            Release(_keyboard.rightArrowKey);
        }

        
        [Description("WASD キー入力で正常に InputMove に値が入るか.")]
        [UnityTest]
        public IEnumerator Test_PlayerMove_WASDKeys_Correct()
        {           
            yield return LoadTestScene();
            yield return new WaitForSeconds(1f);

            var player = GameObject.FindObjectsByType<Player>(FindObjectsSortMode.None)[0];

            // 上下左右の入力通りに InputMove に値が入るか否か.
            Press(_keyboard.wKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.up);
            Release(_keyboard.wKey);
            InputSystem.Update();

            Press(_keyboard.sKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.down);
            Release(_keyboard.sKey);
            InputSystem.Update();
            
            Press(_keyboard.aKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.left);
            Release(_keyboard.aKey);
            InputSystem.Update();

            Press(_keyboard.dKey);
            InputSystem.Update();
            Assert.AreEqual(player.MoveInput, Vector2.right);
            Release(_keyboard.dKey);
        }
 

        private AsyncOperation LoadTestScene()
        {
            return EditorSceneManager.LoadSceneAsyncInPlayMode("Assets/Game/Scripts/Player/Tests/PlayMode/TestScenes/PlayerTestScene.unity", new LoadSceneParameters(LoadSceneMode.Single));
        }


        private void CleanUp()
        {
            var activeScene = SceneManager.GetActiveScene();
            foreach (var go in activeScene.GetRootGameObjects())
            {
                GameObject.DestroyImmediate(go);
            }
        }
    }
}
```

テストが全て通過するようになった   
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212999/3b0ef601-0bd4-06b1-60f2-396f38032434.png)


今後のバージョンアップでシーン上のオブジェクトを全削除しなくてもよくなってほしい...