What is Game Feel about?
Name 3 ways of achieving good game feel
Having 100% code coverage(the % of code covered by tests) does not mean your code works as intended.
That can only be achieved through formal verification.
(But formal verification is very hard 😢)
The AAA (Arrange, Act, Assert) pattern is a common way of writing unit tests
public static bool IsPalindrome(string str) {
char[] strReverse = str.ToCharArray();
Array.Reverse(strReverse);
return str.Equals(new string(strReverse));
}
//...
[Test]
public void ababaIsAPalindrome() {
// Arrange
string palindrome = "ababa";
// Act
bool actualResult = IsPalindrome(palindrome);
// Assert
Assert.IsTrue(actualResult);
}
// Instantiating a MonoBehaviour with new gives a warning
// Instead you should use:
MyComponent x = new GameObject().AddComponent<MyComponent>();
When testing you generally only look if the state changed properly. When mocking you can also verify if the expeced methods were called or the expected events triggered.
// To create mocks
MyInterface myObj = Substitute.For<MyInterface>();
// To mock method return values
myObj.MyMethod().Returns(expectedResult);
// To mock void methods
myObj.When(x => x.MyMethod()).Do(x => {...});
// To check if a method was called
myObj.Received().MyMethod();
// Custom Input interface intended to be mocked when testing
public interface ICustomInput {
float GetVerticalInput();
}
// A default implementation of the above interface
public class CustomInput : ICustomInput {
public float GetVerticalInput() {
return Input.GetAxisRaw("Vertical");
}
}
public class Movement : MonoBehaviour {
// The field which willbe mocked when testing
public ICustomInput customInput { get; set; }
= new CustomInput();
void Update() {
transform.position += Vector3.up
* customInput.GetVerticalInput()
* Time.deltaTime;
}
}
[UnityTest]
public IEnumerator TestMovementUp() {
// Setup the scene by instantiating a prefab
// form the Resources folder
GameObject player = GameObject.Instantiate(
Resources.Load<GameObject>("Prefabs/Player"));
// Add the Movement script to it (which we are testing)
Movement movement = player.AddComponent<Movement>();
// Get a mock of ICustomInput
ICustomInput customInput = Substitute.For<ICustomInput>();
// Mock the GetVerticalInput method to always return 1
customInput.GetVerticalInput().Returns(1);
// Inject the mocked customInput into the object
// that we are testing
movement.customInput = customInput;
// Wait a bit so that the Update method inside
// the Movement script can execute a few times
yield return new WaitForSeconds(1);
// Assert thet we have actually moved up
Assert.IsTrue(player.transform.position.y > 0,
"Did not move");
// Verify that GetVerticalInput was actually called
customInput.Received().GetVerticalInput();
}
There are two major testing styles concerning dependency handling. The first is using real objects for every test and the second is testing in isolation using mocks/stubs. Neither is perfect.