我正在撰寫一個 Job 類,為了確保這個作業只能執行一次,我引入了一個自定義的“鎖定”機制。
該函式如下所示:
public async Task StartAsync(CancellationToken cancellationToken)
{
if (this[email protected]())
{
return;
}
this[email protected]();
await this.ExecuteAsync(new JobExecutionContext(cancellationToken))
.ConfigureAwait(false);
this[email protected]();
}
現在,當我撰寫測驗時,我應該測驗外部可觀察行為,而不是測驗實作細節,所以我目前有以下測驗:
[Theory(DisplayName = "Starting a `Job` (when the lock is locked), does NOT execute it.")]
[AutoDomainData]
public async Task StartingWithLockedLockDoesLockNotExecuteIt([Frozen] Mock<ILock> lockMock,
[Frozen] Mock<Job> jobMock)
{
// VALIDATION.
ILock @lock = lockMock?.Object ?? throw new ArgumentNullException(nameof(lockMock));
Job job = jobMock?.Object ?? throw new ArgumentNullException(nameof(jobMock));
// MOCK SETUP.
_ = lockMock.Setup(x => x.IsLocked())
.Returns(true);
// ACT.
await job.StartAsync(new CancellationToken())
.ConfigureAwait(false);
// ASSERT.
jobMock.Verify(job => job.ExecuteAsync(It.IsAny<IExecutionContext>()), Times.Never);
}
[Theory(DisplayName = "Starting a `Job` (when the lock is NOT locked), does lock the lock.")]
[AutoDomainData]
public async Task StartingWithNotLockedLockDoesExecuteIt([Frozen] Mock<ILock> lockMock,
[Frozen] Mock<Job> jobMock)
{
// VALIDATION.
ILock @lock = lockMock?.Object ?? throw new ArgumentNullException(nameof(lockMock));
Job job = jobMock?.Object ?? throw new ArgumentNullException(nameof(jobMock));
// MOCK SETUP.
_ = lockMock.Setup(x => x.IsLocked())
.Returns(false);
// ACT.
await job.StartAsync(new CancellationToken())
.ConfigureAwait(false);
// ASSERT.
jobMock.Verify(job => job.ExecuteAsync(It.IsAny<IExecutionContext>()), Times.Once);
}
注意:我使用的是AutoFixture,但我留下了樣板代碼。
現在,我涵蓋了以下案例:
- 當鎖被鎖定時,作業不會被執行。
- 當鎖未鎖定時,作業被執行。
但我錯過了以下重要案例:
- 保證,在執行期間,鎖是活動的。
我怎樣才能正確測驗這個?我覺得設計應該更新,但我不知道如何。
有什么建議嗎?
uj5u.com熱心網友回復:
“執行一次”是指永遠一次,之后的每次呼叫都會回傳相同的結果?或者只是一次只能執行一項作業?基于代碼,我假設后者,但想檢查清楚。
這里有一些關于鎖定比我聰明的人的好資訊:https ://stackoverflow.com/a/12316461/10243808
他的解釋和提供的鏈接中的一些示例將為您提供很多選項來處理回傳的內容和時間,這可能用于對最后一個場景進行單元測驗(我猜即使沒有重構,您也可以在內部回傳一些錯誤訊息if(@lock.isLocked()) 陳述句。)盡管在重構之后,我認為您正在測驗 C# 功能,我認為這在絕大多數情況下都是正確的。
uj5u.com熱心網友回復:
這是我想出的解決方案。
我創建了自己的“Job”和“Ilock”,而不是嘲笑它們。
internal sealed class TestableJob : Job
{
private readonly ILock @lock;
public TestableJob(ILock @lock)
: base(@lock)
{
this.@lock = @lock;
}
public bool IsLockedBeforeJobExecution
{
get; set;
}
public override Task ExecuteAsync(IExecutionContext executionContext)
{
this.IsLockedBeforeJobExecution = this[email protected]();
return Task.CompletedTask;
}
}
internal sealed class TestableLock : ILock
{
private bool isLockedFlag;
public bool IsLocked()
{
return this.isLockedFlag;
}
public void Lock()
{
this.isLockedFlag = true;
}
public void Unlock()
{
this.isLockedFlag = false;
}
}
對于某些測驗,我使用這些自定義實作,而在其他測驗中,我使用模擬。
[Theory(DisplayName = "The `Job` is NOT executed when the `ILock` is \"Locked\".")]
[AutoDomainData]
internal async Task TheJobIsNotExecutedWhenTheLockIsLocked([Frozen] Mock<ILock> lockMock,
[Frozen] Mock<Job> jobMock)
{
// VALIDATION.
ILock @lock = lockMock?.Object ?? throw new ArgumentNullException(nameof(lockMock));
Job job = jobMock?.Object ?? throw new ArgumentNullException(nameof(jobMock));
// MOCK SETUP.
_ = lockMock.Setup(x => x.IsLocked())
.Returns(true);
// ACT.
await job.StartAsync(new CancellationToken())
.ConfigureAwait(false);
// ASSERT.
jobMock.Verify(job => job.ExecuteAsync(It.IsAny<IExecutionContext>()), Times.Never);
}
[Theory(DisplayName = "The `Job` is executed when the `ILock` is \"NOT Locked\".")]
[AutoDomainData]
internal async Task TheJobIsExecutedWhenTheLockIsNotLocked([Frozen] Mock<ILock> lockMock,
[Frozen] Mock<Job> jobMock)
{
// VALIDATION.
ILock @lock = lockMock?.Object ?? throw new ArgumentNullException(nameof(lockMock));
Job job = jobMock?.Object ?? throw new ArgumentNullException(nameof(jobMock));
// MOCK SETUP.
_ = lockMock.Setup(x => x.IsLocked())
.Returns(false);
// ACT.
await job.StartAsync(new CancellationToken())
.ConfigureAwait(false);
// ASSERT.
jobMock.Verify(job => job.ExecuteAsync(It.IsAny<IExecutionContext>()), Times.Once);
}
[Fact(DisplayName = "The `ILock` is \"Locked\" before the `Job` is executed.")]
internal async Task TheLockIsLockedBeforeTheJobIsExecuted()
{
// ARRANGE.
var @lock = new TestableLock();
var job = new TestableJob(@lock);
// ACT.
await job.StartAsync(new CancellationToken())
.ConfigureAwait(false);
// ASSERT.
_ = job.IsLockedBeforeJobExecution
.Should()
.BeTrue();
}
[Fact(DisplayName = "The `ILock` is \"Unlocked\" once the `Job` is executed.")]
internal async Task TheLockIsUnlockedOnceTheJobIsExecuted()
{
// ARRANGE.
var @lock = new TestableLock();
var job = new TestableJob(@lock);
// ACT.
await job.StartAsync(new CancellationToken())
.ConfigureAwait(false);
// ASSERT.
_ = @lock.IsLocked()
.Should()
.BeFalse();
}
[Theory(DisplayName = "The `ILock` is \"Unlocked\" when the `Job` is stopped.")]
[AutoDomainData]
internal async Task TheLockIsUnlockedWhenTheJobIsStopped([Frozen] Mock<ILock> lockMock, [Frozen] Mock<Job> jobMock)
{
// VALIDATION.
ILock @lock = lockMock?.Object ?? throw new ArgumentNullException(nameof(lockMock));
Job job = jobMock?.Object ?? throw new ArgumentNullException(nameof(jobMock));
// ACT.
await job.StopAsync(new CancellationToken())
.ConfigureAwait(false);
// ASSERT.
lockMock.Verify(@lock => @lock.Unlock(), Times.Once);
}
[Theory(DisplayName = "The `ILock` is \"Unlocked\" when an exception is raised during the execution of the `Job`.")]
[AutoDomainData]
internal async Task TheLockIsUnlockedWhenAnExceptionIsRaisedDuringTheExecutionOfTheJob(
[Frozen] Mock<ILock> lockMock,
[Frozen] Mock<Job> jobMock)
{
// VALIDATION.
ILock @lock = lockMock?.Object ?? throw new ArgumentNullException(nameof(lockMock));
Job job = jobMock?.Object ?? throw new ArgumentNullException(nameof(jobMock));
// MOCK SETUP.
_ = jobMock.Setup(@mock => mock.ExecuteAsync(It.IsAny<IExecutionContext>()))
.Throws<ArgumentOutOfRangeException>();
// ACT.
await job.StartAsync(new CancellationToken())
.ConfigureAwait(false);
// ASSERT.
lockMock.Verify(@lock => @lock.Unlock(), Times.Once);
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/523254.html
上一篇:從“CustomerService”測驗“createCustomer”方法時出現NullPointerException
