Search Unity

Question How would you do input tests ?

Discussion in 'Testing & Automation' started by bali33, Sep 29, 2022.

  1. bali33

    bali33

    Joined:
    Aug 14, 2011
    Posts:
    232
    Hello,

    I'm working on a project using the new Input System and I'd like to write test to assert specific behaviors. For that I need to simulate user input in order to trigger specific actions but I have no clue how to do it.
    Is it possible ? How would you do it ?

    Thx
     
  2. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,989
    You'd need a robot to press buttons on a physical device such as a gamepad. :p

    But I think I get what you mean, and that is actually pretty easy. Assuming you have an input action map like this one:
    upload_2022-9-29_14-0-47.png

    You will likely have a class like the following which receives the input events and either registers them in a "current state" (as in this example) or it will call some other methods, preferably just calling a delegate Action or event.

    Code (CSharp):
    1. [RequireComponent(typeof(PlayerInput))]
    2. public sealed class FirstPersonInputReceiver : MonoBehaviour
    3. {
    4.     [Serializable]
    5.     public struct InputState
    6.     {
    7.         public Vector2 MoveDir;
    8.         public Vector2 LookDir;
    9.         public bool JumpPressed;
    10.         public bool AttackPressed;
    11.  
    12.         public Vector3 GetMoveDir() => new(MoveDir.x, 0f, MoveDir.y);
    13.         public Vector3 GetLookDir() => new(LookDir.x, 0f, LookDir.y);
    14.     }
    15.  
    16.     [SerializeField] private InputState _currentState;
    17.  
    18.     public bool CancelPressed { get; private set; }
    19.     public InputState CurrentState => _currentState;
    20.  
    21.     public void OnMove(InputValue value) => _currentState.MoveDir = value.Get<Vector2>();
    22.     public void OnLook(InputValue value) => _currentState.LookDir = value.Get<Vector2>();
    23.     public void OnJump(InputValue value) => _currentState.JumpPressed = value.isPressed;
    24.     public void OnAttack(InputValue value) => _currentState.AttackPressed = value.isPressed;
    25.     public void OnCancel(InputValue value) => CancelPressed = value.isPressed;
    26. }
    27.  
    What you need to do is to either make that class have an interface, which just contains the InputState CurrentState property such that you can write a mock class for tests that also implements the interface and artificially generates a state that your tests run against.

    Similarly, if this input receiver class is sending out message, events or calls delegates, you'd abstract those into an interface and test against the interface (and generally code against the interface too), where the tests will work with a mock class that generates input events.

    Meaning between where you receive New Input System events like OnMove(InputValue value) and the processing (use) of the move value you simply code against an interface like IInputProvider which has either the above CurrentState property (polling input) or individual event methods like OnMoveEvent(Vector2 dir) aka event-driven input.
     
    Last edited: Sep 29, 2022
  3. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,989
    Btw, you could even setup a input receiver class that records the input events and serializes them. With the polling approach using InputState you'd only need a List<InputState> either one per frame or you add some timing info to it (ie frame number) so you now when to change the state.

    The same approach can be used to record and playback game demos, both for testing and entertainment (think: Doom or Quake self-running demo loops).
     
  4. bali33

    bali33

    Joined:
    Aug 14, 2011
    Posts:
    232
    Hello,

    Thanks for the answer, I think I get it !
     
  5. Maeslezo

    Maeslezo

    Joined:
    Jun 16, 2015
    Posts:
    332