Task.ContinueWIth 逻辑的自动测试
Autotests for Task.ContinueWIth logic
我试图用单元测试来覆盖一些逻辑,但 Task.ContinueWith
方法有问题。问题是我在 ContinueWith
任务中有一个重要的逻辑。 ContinueWith
任务将在指定任务之后执行,但不保证它会立即执行。所以结果,我的测试时而失败时而成功。
有我的代码:
方法:
public IPromise CreateFromTask(Task task)
{
var promise = new ControllablePromise(_unityExecutor);
task.ContinueWith(t =>
{
if (t.IsCanceled)
Debug.LogWarning("Promises doesn't support task canceling");
if (t.Exception == null)
promise.Success();
else
{
Debug.Log(t.Exception.InnerException);
promise.Fail(t.Exception.InnerException);
}
}, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.PreferFairness);
return promise;
}
测试:
private PromiseFactory CreateFactory(out IUnityExecutor unityExecutor)
{
unityExecutor = Substitute.For<IUnityExecutor>();
return new PromiseFactory(unityExecutor);
}
[Test]
public void CreateFromTask_FailedTask_OnFailExecuted()
{
// Arrange
var factory = CreateFactory(out var unityExecutor);
var testException = new Exception("Test exception");
void TaskAction()
{
throw testException;
}
Exception failException = null;
void FailCallback(Exception exception)
{
failException = exception;
}
unityExecutor.ExecuteOnFixedUpdate(Arg.Do<Action>(x => x?.Invoke()));
// Act
Debug.Log("About to run a task");
var task = Task.Run(TaskAction);
Debug.Log("The task run");
var promise = factory.CreateFromTask(task);
Debug.Log("Promise created");
Task.WaitAny(task.ContinueWith(t => { },
TaskContinuationOptions.PreferFairness | TaskContinuationOptions.ExecuteSynchronously));
Debug.Log("Task awaited");
promise.OnFail(FailCallback);
// Assert
Assert.AreEqual(testException, failException);
}
接下来是日志输出:
如您所见,我已经尝试使用 TaskContinuationOptions.ExecuteSynchronously
和 TaskContinuationOptions.PreferFairness
来解决此问题,但没有帮助。我很惊讶,即使有这些选项,我的测试也没有成功。
如果这很重要,我将使用其标准测试框架在 Unity3d 中完成所有这些工作。
错误总是应该在“等待任务”日志之前记录的预期结果。
您不需要再创建一个ContinueWith任务,您只需要等待之前创建的任务即可。
public IPromise CreateFromTask(ref Task task)
{
var promise = new ControllablePromise(_unityExecutor);
/* Replace the task with the new one */
task = task.ContinueWith(t =>
{
if (t.IsCanceled)
Debug.LogWarning("Promises doesn't support task canceling");
if (t.Exception == null)
promise.Success();
else
{
Debug.Log(t.Exception.InnerException);
promise.Fail(t.Exception.InnerException);
}
});
return promise;
}
// Act
Debug.Log("About to run a task");
var task = Task.Run(TaskAction);
Debug.Log("The task run");
var promise = factory.CreateFromTask(ref task);
Debug.Log("Promise created");
Task.WaitAny(task); /* Waiting for the task */
Debug.Log("Task awaited");
promise.OnFail(FailCallback);
如果要等待多任务
// Act
Debug.Log("About to run a task");
var task = Task.Run(TaskAction);
var task2 = Task.Run(TaskAction);
Debug.Log("The task run");
var promise = factory.CreateFromTask(ref task);
Debug.Log("Promise created");
Task.WaitAll(task, task2.ContinueWith(t => { })); /* Waiting for all tasks */
Debug.Log("Task awaited");
promise.OnFail(FailCallback);
随机猜测,在 Mono Behaviors 中使用 ContinueWith 和 Unity 时,将其保持在主线程中很重要。您可以使用以下代码段。在 firebase article.
中有更好的描述
var taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
task.ContinueWith(t => { // perform work here }, taskScheduler);
我宁愿以某种方式等待继续任务,它现在在 CreateFromTask 方法中被放弃了。但是,如果这是不可能的:
1.如果Promise支持多个订阅,你可以在你的单元测试项目中创建一个扩展方法,如下所示:
public static class PromiseExtensions
{
public static Task AsTask(this IPromise promise)
{
var tcs = new TaskCompletionSource();
promise.OnFail(exception => { tcs.TrySetResult(); });
promise.OnSuccess(() => { tcs.TrySetResult(); });
return tcs.Task;
}
}
[Test]
public void CreateFromTask_FailedTask_OnFailExecuted()
{
// Arrange
var factory = CreateFactory(out var unityExecutor);
var testException = new Exception("Test exception");
void TaskAction()
{
throw testException;
}
Exception failException = null;
void FailCallback(Exception exception)
{
failException = exception;
}
unityExecutor.ExecuteOnFixedUpdate(Arg.Do<Action>(x => x?.Invoke()));
// Act
var task = Task.Run(TaskAction);
var promise = factory.CreateFromTask(task);
Task.WaitAny(promise.AsTask(), Task.Delay(TimeSpan.FromSeconds(1)));
promise.OnFail(FailCallback);
// Assert
Assert.AreEqual(testException, failException);
}
2.如果Promise不支持多订阅,等FailCallback执行完即可:
[Test]
public void CreateFromTask_FailedTask_OnFailExecuted()
{
// Arrange
var factory = CreateFactory(out var unityExecutor);
var testException = new Exception("Test exception");
void TaskAction()
{
throw testException;
}
TaskCompletionSource failCallbackCompletionSource = new TaskCompletionSource();
Exception failException = null;
void FailCallback(Exception exception)
{
failException = exception;
failCallbackCompletionSource.TrySetResult();
}
unityExecutor.ExecuteOnFixedUpdate(Arg.Do<Action>(x => x?.Invoke()));
// Act
var task = Task.Run(TaskAction);
var promise = factory.CreateFromTask(task);
promise.OnFail(FailCallback);
Task.WaitAny(failCallbackCompletionSource.Task, Task.Delay(TimeSpan.FromSeconds(1)));
// Assert
Assert.AreEqual(testException, failException);
}
我试图用单元测试来覆盖一些逻辑,但 Task.ContinueWith
方法有问题。问题是我在 ContinueWith
任务中有一个重要的逻辑。 ContinueWith
任务将在指定任务之后执行,但不保证它会立即执行。所以结果,我的测试时而失败时而成功。
有我的代码:
方法:
public IPromise CreateFromTask(Task task)
{
var promise = new ControllablePromise(_unityExecutor);
task.ContinueWith(t =>
{
if (t.IsCanceled)
Debug.LogWarning("Promises doesn't support task canceling");
if (t.Exception == null)
promise.Success();
else
{
Debug.Log(t.Exception.InnerException);
promise.Fail(t.Exception.InnerException);
}
}, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.PreferFairness);
return promise;
}
测试:
private PromiseFactory CreateFactory(out IUnityExecutor unityExecutor)
{
unityExecutor = Substitute.For<IUnityExecutor>();
return new PromiseFactory(unityExecutor);
}
[Test]
public void CreateFromTask_FailedTask_OnFailExecuted()
{
// Arrange
var factory = CreateFactory(out var unityExecutor);
var testException = new Exception("Test exception");
void TaskAction()
{
throw testException;
}
Exception failException = null;
void FailCallback(Exception exception)
{
failException = exception;
}
unityExecutor.ExecuteOnFixedUpdate(Arg.Do<Action>(x => x?.Invoke()));
// Act
Debug.Log("About to run a task");
var task = Task.Run(TaskAction);
Debug.Log("The task run");
var promise = factory.CreateFromTask(task);
Debug.Log("Promise created");
Task.WaitAny(task.ContinueWith(t => { },
TaskContinuationOptions.PreferFairness | TaskContinuationOptions.ExecuteSynchronously));
Debug.Log("Task awaited");
promise.OnFail(FailCallback);
// Assert
Assert.AreEqual(testException, failException);
}
接下来是日志输出:
如您所见,我已经尝试使用 TaskContinuationOptions.ExecuteSynchronously
和 TaskContinuationOptions.PreferFairness
来解决此问题,但没有帮助。我很惊讶,即使有这些选项,我的测试也没有成功。
如果这很重要,我将使用其标准测试框架在 Unity3d 中完成所有这些工作。
错误总是应该在“等待任务”日志之前记录的预期结果。
您不需要再创建一个ContinueWith任务,您只需要等待之前创建的任务即可。
public IPromise CreateFromTask(ref Task task)
{
var promise = new ControllablePromise(_unityExecutor);
/* Replace the task with the new one */
task = task.ContinueWith(t =>
{
if (t.IsCanceled)
Debug.LogWarning("Promises doesn't support task canceling");
if (t.Exception == null)
promise.Success();
else
{
Debug.Log(t.Exception.InnerException);
promise.Fail(t.Exception.InnerException);
}
});
return promise;
}
// Act
Debug.Log("About to run a task");
var task = Task.Run(TaskAction);
Debug.Log("The task run");
var promise = factory.CreateFromTask(ref task);
Debug.Log("Promise created");
Task.WaitAny(task); /* Waiting for the task */
Debug.Log("Task awaited");
promise.OnFail(FailCallback);
如果要等待多任务
// Act
Debug.Log("About to run a task");
var task = Task.Run(TaskAction);
var task2 = Task.Run(TaskAction);
Debug.Log("The task run");
var promise = factory.CreateFromTask(ref task);
Debug.Log("Promise created");
Task.WaitAll(task, task2.ContinueWith(t => { })); /* Waiting for all tasks */
Debug.Log("Task awaited");
promise.OnFail(FailCallback);
随机猜测,在 Mono Behaviors 中使用 ContinueWith 和 Unity 时,将其保持在主线程中很重要。您可以使用以下代码段。在 firebase article.
中有更好的描述var taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
task.ContinueWith(t => { // perform work here }, taskScheduler);
我宁愿以某种方式等待继续任务,它现在在 CreateFromTask 方法中被放弃了。但是,如果这是不可能的:
1.如果Promise支持多个订阅,你可以在你的单元测试项目中创建一个扩展方法,如下所示:
public static class PromiseExtensions
{
public static Task AsTask(this IPromise promise)
{
var tcs = new TaskCompletionSource();
promise.OnFail(exception => { tcs.TrySetResult(); });
promise.OnSuccess(() => { tcs.TrySetResult(); });
return tcs.Task;
}
}
[Test]
public void CreateFromTask_FailedTask_OnFailExecuted()
{
// Arrange
var factory = CreateFactory(out var unityExecutor);
var testException = new Exception("Test exception");
void TaskAction()
{
throw testException;
}
Exception failException = null;
void FailCallback(Exception exception)
{
failException = exception;
}
unityExecutor.ExecuteOnFixedUpdate(Arg.Do<Action>(x => x?.Invoke()));
// Act
var task = Task.Run(TaskAction);
var promise = factory.CreateFromTask(task);
Task.WaitAny(promise.AsTask(), Task.Delay(TimeSpan.FromSeconds(1)));
promise.OnFail(FailCallback);
// Assert
Assert.AreEqual(testException, failException);
}
2.如果Promise不支持多订阅,等FailCallback执行完即可:
[Test]
public void CreateFromTask_FailedTask_OnFailExecuted()
{
// Arrange
var factory = CreateFactory(out var unityExecutor);
var testException = new Exception("Test exception");
void TaskAction()
{
throw testException;
}
TaskCompletionSource failCallbackCompletionSource = new TaskCompletionSource();
Exception failException = null;
void FailCallback(Exception exception)
{
failException = exception;
failCallbackCompletionSource.TrySetResult();
}
unityExecutor.ExecuteOnFixedUpdate(Arg.Do<Action>(x => x?.Invoke()));
// Act
var task = Task.Run(TaskAction);
var promise = factory.CreateFromTask(task);
promise.OnFail(FailCallback);
Task.WaitAny(failCallbackCompletionSource.Task, Task.Delay(TimeSpan.FromSeconds(1)));
// Assert
Assert.AreEqual(testException, failException);
}