浏览代码

Tidying a bit, especially the tests. Removing uninformative tests and adding a little context. Removing a couple test-only methods.

/main/staging
nathaniel.buck@unity3d.com 3 年前
当前提交
42535b51
共有 14 个文件被更改,包括 93 次插入347 次删除
  1. 15
      Assets/Scripts/Entities/LocalLobby.cs
  2. 18
      Assets/Scripts/Infrastructure/Messenger.cs
  3. 23
      Assets/Scripts/Relay/RelayInterface.cs
  4. 3
      Assets/Scripts/Tests/Editor/AuthTests.cs
  5. 35
      Assets/Scripts/Tests/Editor/LoggerTests.cs
  6. 16
      Assets/Scripts/Tests/Editor/MessengerTests.cs
  7. 19
      Assets/Scripts/Tests/Editor/ObserverTests.cs
  8. 12
      Assets/Scripts/Tests/PlayMode/LobbyRoundtripTests.cs
  9. 92
      Assets/Scripts/Tests/PlayMode/RelayRoundTripTests.cs
  10. 6
      Assets/Scripts/Tests/PlayMode/UpdateSlowTests.cs
  11. 11
      Assets/Scripts/Tests/Editor/LobbyTests.cs.meta
  12. 44
      Assets/Scripts/Tests/Editor/LobbyTests.cs
  13. 11
      Assets/Scripts/Tests/PlayMode/LobbyReadyCheckTests.cs.meta
  14. 135
      Assets/Scripts/Tests/PlayMode/LobbyReadyCheckTests.cs

15
Assets/Scripts/Entities/LocalLobby.cs


using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace LobbyRelaySample

m_data.Color = value;
OnChanged(this);
}
}
/// <summary>
/// Checks if we have n players that have the Status.
/// -1 Count means you need all Lobbyusers
/// </summary>
/// <returns>True if enough players are of the input status.</returns>
public bool PlayersOfState(UserStatus status, int playersCount = -1) // TODO: Remove test-only API.
{
var statePlayers = m_LobbyUsers.Values.Count(user => user.UserStatus == status);
if (playersCount < 0)
return statePlayers == m_LobbyUsers.Count;
return statePlayers == playersCount;
}
public void CopyObserved(LobbyData data, Dictionary<string, LobbyUser> currUsers)

18
Assets/Scripts/Infrastructure/Messenger.cs


JoinLobbyRequest = 2,
CreateLobbyRequest = 3,
QueryLobbies = 4,
PlayerJoinedLobby = 5,
PlayerLeftLobby = 6,
ChangeGameState = 7,
LobbyUserStatus = 8,
HostInitReadyCheck = 9,
LocalUserReadyCheckResponse = 10,
UserSetEmote = 11,
EndGame = 12,
StartCountdown = 13,
CancelCountdown = 14,
ConfirmInGameState = 15,
ChangeGameState = 5,
LobbyUserStatus = 6,
UserSetEmote = 7,
EndGame = 8,
StartCountdown = 9,
CancelCountdown = 10,
ConfirmInGameState = 11,
}
/// <summary>

23
Assets/Scripts/Relay/RelayInterface.cs


} finally {
onComplete?.Invoke(result);
}
// TODO: Ensure that passing null as result is handled.
}
}

/// <param name="maxConnections"></param>
public static void AllocateAsync(int maxConnections, Action<Response<AllocateResponseBody>> onComplete)
public static void AllocateAsync(int maxConnections, Action<Allocation> onComplete)
new InProgressRequest<Response<AllocateResponseBody>>(task, onComplete);
}
new InProgressRequest<Response<AllocateResponseBody>>(task, OnResponse);
public static void AllocateAsync(int maxConnections, Action<Allocation> onComplete)
{
AllocateAsync(maxConnections, a =>
void OnResponse(Response<AllocateResponseBody> response)
if (a == null)
if (response == null)
else if (a.Status >= 200 && a.Status < 300)
onComplete?.Invoke(a.Result.Data.Allocation);
else if (response.Status >= 200 && response.Status < 300)
onComplete?.Invoke(response.Result.Data.Allocation);
Debug.LogError($"Allocation returned a non Success code: {a.Status}");
});
Debug.LogError($"Allocation returned a non Success code: {response.Status}");
};
}
/// <summary>

3
Assets/Scripts/Tests/Editor/AuthTests.cs


{
public class AuthTests
{
/// <summary>
/// Ensure that any changes to a flavor of SubIdentity are automatically updated.
/// </summary>
[Test]
public void IdentityBasicSubidentity()
{

35
Assets/Scripts/Tests/Editor/LoggerTests.cs


namespace LobbyRelaySample.Tests
{
/// <summary>
/// Tests of the LogHandler overriding debug logging.
/// </summary>
/// <summary>Reset the console between tests.</summary>
Debug.ClearDeveloperConsole(); // Reset Console between tests
Debug.ClearDeveloperConsole();
/// <summary>
/// Only display Log messages when set to Verbose.
/// </summary>
public void TestLog() // Should not show when not Verbose
public void TestLog()
LogAssert.NoUnexpectedReceived(); //Checks to see if there is anything here, there should not be
LogAssert.NoUnexpectedReceived(); // Ensure that we haven't received any unexpected logs.
LogHandler.Get().mode = LogMode.Warnings;
Debug.Log("WarningLog");

LogAssert.Expect(LogType.Log, "VerbLog");
}
/// <summary>
/// Only display Warning messages when set to Verbose or Warnings.
/// </summary>
public void TestWarning() // Should not show when Critical
public void TestWarning()
LogAssert.NoUnexpectedReceived(); //Checks to see if there is anything here, there should not be
LogAssert.NoUnexpectedReceived();
LogHandler.Get().mode = LogMode.Warnings;
Debug.LogWarning("WarningWarning");

LogAssert.Expect(LogType.Warning, "VerbWarning");
}
/// <summary>
/// Always display Error messages.
/// </summary>
public void TestError() // Should show regardless.
public void TestError()
{
LogHandler.Get().mode = LogMode.Critical;
Debug.LogError("CritError");

LogAssert.Expect(LogType.Error, "VerbError");
}
/// <summary>
/// Always display Assert messages.
/// </summary>
public void TestAssert() //Should Show regardless
public void TestAssert()
{
LogHandler.Get().mode = LogMode.Critical;
Debug.LogAssertion(true);

LogAssert.Expect(LogType.Assert, "True");
}
/// <summary>
/// Always display Exception messages.
/// </summary>
public void TestException() //Should Show regardless
public void TestException()
{
LogHandler.Get().mode = LogMode.Critical;
LogAssert.Expect(LogType.Exception, "Exception: CriticalException");

16
Assets/Scripts/Tests/Editor/MessengerTests.cs


public class MessengerTests
{
/// <summary>Trivial message recipient that will run some action on any message.</summary>
public Subscriber(Action thingToDo)
{
m_thingToDo = thingToDo;
}
public void OnReceiveMessage(MessageType type, object msg)
{
m_thingToDo?.Invoke();
}
public Subscriber(Action thingToDo) { m_thingToDo = thingToDo; }
public void OnReceiveMessage(MessageType type, object msg) { m_thingToDo?.Invoke(); }
/// <summary>
/// If a message recipient takes a long time to process a message, we want to be made aware.
/// </summary>
[Test]
public void WhatIfAMessageIsVerySlow()
{

19
Assets/Scripts/Tests/Editor/ObserverTests.cs


{
public class ObserverTests
{
/// <summary>
/// When an observed value changes, the Observer should automatically update.
/// </summary>
public IEnumerator ObserverChangeWhenObservedChanged() // Test if Observer changes when StringField gets set
public IEnumerator ObserverChangeWhenObservedChanged()
{
var observed = new TestObserved();
var observer = new GameObject("PlayerObserver").AddComponent<TestObserverBehaviour>();

Assert.AreEqual(observed.StringField, observer.displayStringField);
}
/// <summary>
/// When an Observer is registered, it should receive the observed field's initial value.
/// </summary>
/// <returns></returns>
public IEnumerator ObserverRegistersInitialChanges() // Test if Observer changes on Initialization
public IEnumerator ObserverRegistersInitialChanges()
observed.StringField = "NewName"; // Set the field before we begin observing
observed.StringField = "NewName";
var observer = new GameObject("PlayerObserver").AddComponent<TestObserverBehaviour>();
Assert.AreNotEqual(observed.StringField, observer.displayStringField);

Assert.AreEqual(observed.StringField, observer.displayStringField);
}
class TestObserved : Observed<TestObserved>
// We just have a couple Observers that update some arbitrary member, in this case a string.
private class TestObserved : Observed<TestObserved>
{
string m_stringField;

}
}
//Mock UI Observer
class TestObserverBehaviour : ObserverBehaviour<TestObserved>
private class TestObserverBehaviour : ObserverBehaviour<TestObserved>
{
public string displayStringField;

12
Assets/Scripts/Tests/PlayMode/LobbyRoundtripTests.cs


namespace Test
{
/// <summary>
/// Hits the Authentication and Lobbies services in order to ensure lobbies can be created and deleted.
/// The actual code accessing lobbies should go through LobbyAsyncRequests. This serves to ensure the connection to the Lobby service is functional.
/// Accesses the Authentication and Lobby services in order to ensure lobbies can be created and deleted.
/// LobbyAsyncRequests wraps the Lobby API, so go through that in practice. This simply ensures the connection to the Lobby service is functional.
/// </summary>
public class LobbyRoundtripTests
{

LogAssert.ignoreFailingMessages = false;
}
/// <summary>
/// Make sure the entire roundtrip for Lobby works: Once signed in, create a lobby, query to make sure it exists, then delete it.
/// </summary>
#region Setup
LogAssert.ignoreFailingMessages = true; // Not sure why, but when auth logs in, it sometimes generates an error: "A Native Collection has not been disposed[...]." We don't want this to cause test failures, since in practice it *seems* to not negatively impact behavior.
// Wait a reasonable amount of time for sign-in to complete.

Assert.Greater(timeout, 0, "Timeout check (query #0)");
Assert.IsTrue(queryResponse.Status >= 200 && queryResponse.Status < 300, "QueryAllLobbiesAsync should return a success code. (#0)");
int numLobbiesIni = queryResponse.Result.Results?.Count ?? 0;
#endregion
// Create a test lobby.
Response<Lobby> createResponse = null;

LogAssert.ignoreFailingMessages = false;
}
/// <summary>
/// If the Lobby create call fails, we want to ensure we call onComplete so we can act on the failure.
/// </summary>
[UnityTest]
public IEnumerator OnCompletesOnFailure()
{

92
Assets/Scripts/Tests/PlayMode/RelayRoundTripTests.cs


namespace Test
{
/// <summary>
/// Accesses the Authentication and Relay services in order to ensure we can connect to Relay and retrieve a join code.
/// RelayUtp* wraps the Relay API, so go through that in practice. This simply ensures the connection to the Lobby service is functional.
/// </summary>
public class RelayRoundTripTests
{
private LobbyRelaySample.Auth.SubIdentity_Authentication m_auth;

LogAssert.ignoreFailingMessages = false;
}
/// <summary>
/// Create a Relay allocation, request a join code, and then join. Note that this is purely to ensure the service is functioning;
/// in practice, the RelayUtpSetup does more work to bind to the allocation and has slightly different logic for hosts vs. clients.
/// </summary>
[UnityTest]
public IEnumerator DoBaseRoundTrip()
{

if (!m_didSigninComplete)
Assert.Fail("Did not sign in.");
//Allocation
float timeout = 5;
Response<AllocateResponseBody> allocationResponse = null;
RelayInterface.AllocateAsync(4, (a) => { allocationResponse = a; });
while (allocationResponse == null && timeout > 0)
{
yield return new WaitForSeconds(0.25f);
timeout -= 0.25f;
}
Assert.Greater(timeout, 0, "Timeout Check (Allocate)");
Assert.IsTrue(allocationResponse.Status >= 200 && allocationResponse.Status < 300, "AllocationResponse should return a success code.");
Guid allocationId = allocationResponse.Result.Data.Allocation.AllocationId;
var allocationIP = allocationResponse.Result.Data.Allocation.RelayServer.IpV4;
var allocationPort = allocationResponse.Result.Data.Allocation.RelayServer.Port;
Assert.NotNull(allocationId);
Assert.NotNull(allocationIP);
Assert.NotNull(allocationPort);
//Join Code Fetch
timeout = 5;
Response<JoinCodeResponseBody> joinCodeResponse = null;
RelayInterface.GetJoinCodeAsync(allocationId, (j) => { joinCodeResponse = j; });
while (joinCodeResponse == null && timeout > 0)
{
yield return new WaitForSeconds(0.25f);
timeout -= 0.25f;
}
Assert.Greater(timeout, 0, "Timeout Check (JoinCode)");
Assert.IsTrue(allocationResponse.Status >= 200 && allocationResponse.Status < 300, "JoinCodeResponse should return a success code.");
string joinCode = joinCodeResponse.Result.Data.JoinCode;
Assert.False(string.IsNullOrEmpty(joinCode));
//Join Via Join Code
timeout = 5;
Response<JoinResponseBody> joinResponse = null;
RelayInterface.JoinAsync(joinCode, (j) => { joinResponse = j; });
while (joinResponse == null && timeout > 0)
{
yield return new WaitForSeconds(0.25f);
timeout -= 0.25f;
}
Assert.Greater(timeout, 0, "Timeout Check (Join)");
Assert.IsTrue(allocationResponse.Status >= 200 && allocationResponse.Status < 300, "JoinResponse should return a success code.");
var codeIp = joinResponse.Result.Data.Allocation.RelayServer.IpV4;
var codePort = joinResponse.Result.Data.Allocation.RelayServer.Port;
Assert.AreEqual(codeIp, allocationIP);
Assert.AreEqual(codePort, allocationPort);
}
[UnityTest]
public IEnumerator DoShortcutRoundtrip()
{
LogAssert.ignoreFailingMessages = true;
if (!m_didSigninComplete)
yield return new WaitForSeconds(3);
if (!m_didSigninComplete)
Assert.Fail("Did not sign in.");
//Allocation
// Allocation
{
yield return new WaitForSeconds(0.25f);
{ yield return new WaitForSeconds(0.25f);
timeout -= 0.25f;
}

Assert.NotNull(allocationIP);
Assert.NotNull(allocationPort);
//Join Code Fetch
// Join code retrieval
{
yield return new WaitForSeconds(0.25f);
{ yield return new WaitForSeconds(0.25f);
//Join Via Join Code
// Joining with the join code
{
yield return new WaitForSeconds(0.25f);
{ yield return new WaitForSeconds(0.25f);
Assert.Greater(timeout, 0, "Timeout Check (Join)");
var codeIp = joinResponse.Result.Data.Allocation.RelayServer.IpV4;
var codePort = joinResponse.Result.Data.Allocation.RelayServer.Port;

6
Assets/Scripts/Tests/PlayMode/UpdateSlowTests.cs


using UnityEngine.TestTools;
namespace Test
{
{
/// <summary>
/// Testing some edge cases with the UpdateSlow.
/// </summary>
public class UpdateSlowTests
{
private GameObject m_updateSlowObj;

/// <summary>Trivial Subscriber to do some action every UpdateSlow.</summary>
private class Subscriber : IDisposable
{
private Action m_thingToDo;

11
Assets/Scripts/Tests/Editor/LobbyTests.cs.meta


fileFormatVersion: 2
guid: 9d49868c0d767434f95879cf758a4fb2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

44
Assets/Scripts/Tests/Editor/LobbyTests.cs


using LobbyRelaySample;
using NUnit.Framework;
namespace Test
{
public class LobbyTests
{
LocalLobby m_LocalLobby;
const int k_TestUserCount = 3;
[SetUp]
public void Setup()
{
m_LocalLobby = new LocalLobby();
for (int i = 0; i < k_TestUserCount; i++)
{
m_LocalLobby.AddPlayer(new LobbyUser
{
ID = i.ToString()
});
}
}
[Test]
public void LobbyPlayerStateTest()
{
Assert.False(m_LocalLobby.PlayersOfState(UserStatus.Ready));
m_LocalLobby.LobbyUsers["0"].UserStatus = UserStatus.Ready;
Assert.False(m_LocalLobby.PlayersOfState(UserStatus.Ready));
Assert.True(m_LocalLobby.PlayersOfState(UserStatus.Ready, 1));
m_LocalLobby.LobbyUsers["1"].UserStatus = UserStatus.Ready;
Assert.False(m_LocalLobby.PlayersOfState(UserStatus.Ready));
Assert.True(m_LocalLobby.PlayersOfState(UserStatus.Ready, 2));
m_LocalLobby.LobbyUsers["2"].UserStatus = UserStatus.Ready;
Assert.True(m_LocalLobby.PlayersOfState(UserStatus.Ready));
}
}
}

11
Assets/Scripts/Tests/PlayMode/LobbyReadyCheckTests.cs.meta


fileFormatVersion: 2
guid: 851dd0cdcc3b6c84398f6db2aaf4c35a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

135
Assets/Scripts/Tests/PlayMode/LobbyReadyCheckTests.cs


// TODO: Obsolete. Replace with something accurate to Relay with transport.
//using LobbyRelaySample;
//using NUnit.Framework;
//using System.Collections;
//using Unity.Services.Lobbies;
//using Unity.Services.Lobbies.Models;
//using UnityEngine;
//using UnityEngine.TestTools;
//using LobbyAPIInterface = LobbyRelaySample.lobby.LobbyAPIInterface;
//namespace Test
//{
// public class LobbyReadyCheckTests
// {
// private string m_workingLobbyId;
// private LobbyRelaySample.Auth.Identity m_auth;
// private bool m_didSigninComplete = false;
// private GameObject m_updateSlowObj;
// [OneTimeSetUp]
// public void Setup()
// {
// m_auth = new LobbyRelaySample.Auth.Identity(() => { m_didSigninComplete = true; });
// Locator.Get.Provide(m_auth);
// m_updateSlowObj = new GameObject("UpdateSlowTest");
// m_updateSlowObj.AddComponent<UpdateSlow>();
// }
// [UnityTearDown]
// public IEnumerator PerTestTeardown()
// {
// if (m_workingLobbyId != null)
// { LobbyAPIInterface.DeleteLobbyAsync(m_workingLobbyId, null);
// m_workingLobbyId = null;
// }
// yield return new WaitForSeconds(0.5f); // We need a yield anyway, so wait long enough to probably delete the lobby. There currently (6/22/2021) aren't other tests that would have issues if this took longer.
// }
// [OneTimeTearDown]
// public void Teardown()
// {
// Locator.Get.Provide(new LobbyRelaySample.Auth.IdentityNoop());
// m_auth.Dispose();
// LogAssert.ignoreFailingMessages = false;
// LobbyAsyncRequests.Instance.EndTracking();
// GameObject.Destroy(m_updateSlowObj);
// }
// private IEnumerator WaitForSignin()
// {
// // Wait a reasonable amount of time for sign-in to complete.
// if (!m_didSigninComplete)
// yield return new WaitForSeconds(3);
// if (!m_didSigninComplete)
// Assert.Fail("Did not sign in.");
// }
// private IEnumerator CreateLobby(string lobbyName, string userId)
// {
// Response<Lobby> createResponse = null;
// float timeout = 5;
// LobbyAPIInterface.CreateLobbyAsync(userId, lobbyName, 4, false, (r) => { createResponse = r; });
// while (createResponse == null && timeout > 0)
// { yield return new WaitForSeconds(0.25f);
// timeout -= 0.25f;
// }
// Assert.Greater(timeout, 0, "Timeout check (lobby creation).");
// m_workingLobbyId = createResponse.Result.Id;
// }
// private IEnumerator PushPlayerData(LobbyUser player)
// {
// bool hasPushedPlayerData = false;
// float timeout = 5;
// LobbyAsyncRequests.Instance.UpdatePlayerDataAsync(LobbyRelaySample.lobby.ToLocalLobby.RetrieveUserData(player), () => { hasPushedPlayerData = true; }); // LobbyContentHeartbeat normally does this.
// while (!hasPushedPlayerData && timeout > 0)
// { yield return new WaitForSeconds(0.25f);
// timeout -= 0.25f;
// }
// Assert.Greater(timeout, 0, "Timeout check (push player data).");
// }
// /// <summary>
// /// After creating a lobby and a player, signal that the player is Ready. This should lead to a countdown time being set for all players.
// /// </summary>
// [UnityTest]
// public IEnumerator SetCountdownTimeSinglePlayer()
// {
// LogAssert.ignoreFailingMessages = true; // Not sure why, but when auth logs in, it sometimes generates an error: "A Native Collection has not been disposed[...]." We don't want this to cause test failures, since in practice it *seems* to not negatively impact behavior.
// ReadyCheck readyCheck = new ReadyCheck(5); // This ready time is used for the countdown target end, not for any of the timing of actually detecting readies.
// yield return WaitForSignin();
// string userId = m_auth.GetSubIdentity(LobbyRelaySample.Auth.IIdentityType.Auth).GetContent("id");
// yield return CreateLobby("TestReadyLobby1", userId);
// LobbyAsyncRequests.Instance.BeginTracking(m_workingLobbyId);
// yield return new WaitForSeconds(2); // Allow the initial lobby retrieval.
// LobbyUser user = new LobbyUser();
// user.ID = userId;
// user.UserStatus = UserStatus.Ready;
// yield return PushPlayerData(user);
// readyCheck.BeginCheckingForReady();
// float timeout = 5; // Long enough for two slow updates
// yield return new WaitForSeconds(timeout);
// readyCheck.Dispose();
// LobbyAsyncRequests.Instance.EndTracking();
// yield return new WaitForSeconds(2); // Buffer to prevent a 429 on the upcoming Get, since there's a Get request on the slow upate loop when that's active.
// Response<Lobby> getResponse = null;
// timeout = 5;
// LobbyAPIInterface.GetLobbyAsync(m_workingLobbyId, (r) => { getResponse = r; });
// while (getResponse == null && timeout > 0)
// { yield return new WaitForSeconds(0.25f);
// timeout -= 0.25f;
// }
// Assert.Greater(timeout, 0, "Timeout check (get lobby).");
// Assert.NotNull(getResponse.Result, "Retrieved lobby successfully.");
// Assert.NotNull(getResponse.Result.Data, "Lobby should have data.");
// Assert.True(getResponse.Result.Data.ContainsKey("AllPlayersReady"), "Check for AllPlayersReady key.");
// string readyString = getResponse.Result.Data["AllPlayersReady"]?.Value;
// Assert.NotNull(readyString, "Check for non-null AllPlayersReady.");
// Assert.True(long.TryParse(readyString, out long ticks), "Check for ticks value in AllPlayersReady."); // This will be based on the current time, so we won't check for a specific value.
// }
// // Can't test with multiple players on one machine, since anonymous UAS credentials can't be manually supplied.
// }
//}
正在加载...
取消
保存