150 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			150 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System; | |||
|  | using System.Collections; | |||
|  | using System.Collections.Generic; | |||
|  | using NUnit.Framework; | |||
|  | using NUnit.Framework.Interfaces; | |||
|  | using NUnit.Framework.Internal; | |||
|  | using NUnit.Framework.Internal.Builders; | |||
|  | 
 | |||
|  | namespace UnityEngine.TestTools | |||
|  | { | |||
|  |     /// <summary> | |||
|  |     /// `UnityTest` attribute is the main addition to the standard [NUnit](http://www.nunit.org/) library for the Unity Test Framework. This type of unit test allows you to skip a frame from within a test (so background tasks can finish) or give certain commands to the Unity **Editor**, such as performing a domain reload or entering **Play Mode** from an **Edit Mode** test. | |||
|  |     /// In Play Mode, the `UnityTest` attribute runs as a [coroutine](https://docs.unity3d.com/Manual/Coroutines.html). Whereas Edit Mode tests run in the [EditorApplication.update](https://docs.unity3d.com/ScriptReference/EditorApplication-update.html) callback loop. | |||
|  |     /// The `UnityTest` attribute is, in fact, an alternative to the `NUnit` [Test attribute](https://github.com/nunit/docs/wiki/Test-Attribute), which allows yielding instructions back to the framework. Once the instruction is complete, the test run continues. If you `yield return null`, you skip a frame. That might be necessary to ensure that some changes do happen on the next iteration of either the `EditorApplication.update` loop or the [game loop](https://docs.unity3d.com/Manual/ExecutionOrder.html). | |||
|  |     /// </summary> | |||
|  |     /// <example> | |||
|  |     /// <para> | |||
|  |     /// ## Edit Mode example | |||
|  |     /// The most simple example of an Edit Mode test could be the one that yields `null` to skip the current frame and then continues to run: | |||
|  |     /// </para> | |||
|  |     /// <code> | |||
|  |     /// [UnityTest] | |||
|  |     /// public IEnumerator EditorUtility_WhenExecuted_ReturnsSuccess() | |||
|  |     /// { | |||
|  |     ///     var utility = RunEditorUtilityInTheBackground(); | |||
|  |     /// | |||
|  |     ///     while (utility.isRunning) | |||
|  |     ///     { | |||
|  |     ///         yield return null; | |||
|  |     ///     } | |||
|  |     /// | |||
|  |     ///     Assert.IsTrue(utility.isSuccess); | |||
|  |     /// } | |||
|  |     /// </code> | |||
|  |     /// <para> | |||
|  |     /// ## Play Mode example | |||
|  |     /// | |||
|  |     /// In Play Mode, a test runs as a coroutine attached to a [MonoBehaviour](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html). So all the yield instructions available in coroutines, are also available in your test. | |||
|  |     /// | |||
|  |     /// From a Play Mode test you can use one of Unity’s [Yield Instructions](https://docs.unity3d.com/ScriptReference/YieldInstruction.html): | |||
|  |     /// | |||
|  |     /// - [WaitForFixedUpdate](https://docs.unity3d.com/ScriptReference/WaitForFixedUpdate.html): to ensure changes expected within the next cycle of physics calculations. | |||
|  |     /// - [WaitForSeconds](https://docs.unity3d.com/ScriptReference/WaitForSeconds.html): if you want to pause your test coroutine for a fixed amount of time. Be careful about creating long-running tests. | |||
|  |     /// | |||
|  |     /// The simplest example is to yield to `WaitForFixedUpdate`: | |||
|  |     /// </para> | |||
|  |     /// <code> | |||
|  |     /// [UnityTest] | |||
|  |     /// public IEnumerator GameObject_WithRigidBody_WillBeAffectedByPhysics() | |||
|  |     /// { | |||
|  |     ///     var go = new GameObject(); | |||
|  |     ///     go.AddComponent<Rigidbody>(); | |||
|  |     ///     var originalPosition = go.transform.position.y; | |||
|  |     /// | |||
|  |     ///     yield return new WaitForFixedUpdate(); | |||
|  |     /// | |||
|  |     ///     Assert.AreNotEqual(originalPosition, go.transform.position.y); | |||
|  |     /// } | |||
|  |     /// </code> | |||
|  |     /// </example> | |||
|  |     [AttributeUsage(AttributeTargets.Method)] | |||
|  |     public class UnityTestAttribute : CombiningStrategyAttribute, IImplyFixture, ISimpleTestBuilder, ITestBuilder, IApplyToTest | |||
|  |     { | |||
|  |         private const string k_MethodMarkedWithUnitytestMustReturnIenumerator = "Method marked with UnityTest must return IEnumerator."; | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// Initializes and returns an instance of UnityTestAttribute. | |||
|  |         /// </summary> | |||
|  |         public UnityTestAttribute() : base(new UnityCombinatorialStrategy(), new ParameterDataSourceProvider()) {} | |||
|  | 
 | |||
|  |         private readonly NUnitTestCaseBuilder _builder = new NUnitTestCaseBuilder(); | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// This method builds the TestMethod from the Test and the method info. In addition it removes the expected result of the test. | |||
|  |         /// </summary> | |||
|  |         /// <param name="method">The method info.</param> | |||
|  |         /// <param name="suite">The test.</param> | |||
|  |         /// <returns>A TestMethod object</returns> | |||
|  |         TestMethod ISimpleTestBuilder.BuildFrom(IMethodInfo method, Test suite) | |||
|  |         { | |||
|  |             var t = CreateTestMethod(method, suite); | |||
|  | 
 | |||
|  |             AdaptToUnityTestMethod(t); | |||
|  | 
 | |||
|  |             return t; | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// This method hides the base method from CombiningStrategyAttribute. | |||
|  |         /// It builds a TestMethod from a Parameterized Test and the method info. | |||
|  |         /// In addition it removes the expected result of the test. | |||
|  |         /// </summary> | |||
|  |         /// <param name="method">The method info.</param> | |||
|  |         /// <param name="suite">The test.</param> | |||
|  |         /// <returns>A TestMethod object</returns> | |||
|  |         IEnumerable<TestMethod> ITestBuilder.BuildFrom(IMethodInfo method, Test suite) | |||
|  |         { | |||
|  |             var testMethods  = base.BuildFrom(method, suite); | |||
|  | 
 | |||
|  |             foreach (var t in testMethods) | |||
|  |             { | |||
|  |                 AdaptToUnityTestMethod(t); | |||
|  |             } | |||
|  | 
 | |||
|  |             return testMethods; | |||
|  |         } | |||
|  | 
 | |||
|  |         private TestMethod CreateTestMethod(IMethodInfo method, Test suite) | |||
|  |         { | |||
|  |             TestCaseParameters parms = new TestCaseParameters | |||
|  |             { | |||
|  |                 ExpectedResult = new object(), | |||
|  |                 HasExpectedResult = true | |||
|  |             }; | |||
|  | 
 | |||
|  |             var t = _builder.BuildTestMethod(method, suite, parms); | |||
|  |             return t; | |||
|  |         } | |||
|  | 
 | |||
|  |         private static void AdaptToUnityTestMethod(TestMethod t) | |||
|  |         { | |||
|  |             if (t.parms != null) | |||
|  |             { | |||
|  |                 t.parms.HasExpectedResult = false; | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         private static bool IsMethodReturnTypeIEnumerator(IMethodInfo method) | |||
|  |         { | |||
|  |             return !method.ReturnType.IsType(typeof(IEnumerator)); | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// This method hides the base method ApplyToTest from CombiningStrategyAttribute. | |||
|  |         /// In addition it ensures that the test with the `UnityTestAttribute` has an IEnumerator as return type. | |||
|  |         /// </summary> | |||
|  |         /// <param name="test">The test.</param> | |||
|  |         public new void ApplyToTest(Test test) | |||
|  |         { | |||
|  |             if (IsMethodReturnTypeIEnumerator(test.Method)) | |||
|  |             { | |||
|  |                 test.RunState = RunState.NotRunnable; | |||
|  |                 test.Properties.Set(PropertyNames.SkipReason, k_MethodMarkedWithUnitytestMustReturnIenumerator); | |||
|  |             } | |||
|  | 
 | |||
|  |             base.ApplyToTest(test); | |||
|  |         } | |||
|  |     } | |||
|  | } |