寫測試可以縮短除錯、驗證時間,進而縮短整個開發時程,尤其是這種非同步的需求,如果沒有用測試技巧,真的會花費很多的時間,接下來就來分享使用 Hangfire 的測試技巧...
開發環境
- VS 2019
- .NET Framework 4.8
實作
開啟一個 UnitTest Project
安裝以下套件
Install-Package Microsoft.AspNet.WebApi.OwinSelfHost
Install-Package Microsoft.Owin.Diagnostics
Install-Package Microsoft.Owin.Host.SystemWeb
Install-Package Hangfire
Install-Package Hangfire.Console
Install-Package Hangfire.MemoryStorage
Install-Package Hangfire.Dashboard.Management
Install-Package NSubstitute
單元測試
隔離 IBackgroundJobClient
將任務加入排程內有兩種做法,一種是使用靜態方法﹑如下所示:
- BackgroundJob.Enqueue
- BackgroundJob.Schedule
- BackgroundJob.ContinueWith
另一種是使用 IBackgroundJobClient
,BackgroundJob
骨子裏面是用 IBackgroundJobClient
實作的,若是要讓撰寫單元測試,應該讓你的物件相依 IBackgroundJobClient
,下面這個例子,EnqueueAction
方法依賴了 JobClient.Enqueue
方法,JobClient.Enqueue
呼叫 Action
方法,Action
是上一篇介紹 Hangfire.Dashboard.Management 的範例程式,主要是用來給 Hangfire UI 操作介面觸發排程,EnqueueAction
則是用 JobClient.Enqueue
直接觸發排程。
[ManagementPage("演示", "default")]
public class DemoJob
{
public IBackgroundJobClient JobClient
{
get
{
if (this._jobClient == null)
{
this._jobClient = new BackgroundJobClient();
}
return this._jobClient;
}
set => this._jobClient = value;
}
private IBackgroundJobClient _jobClient;
public DemoJob()
{
}
public DemoJob(IBackgroundJobClient jobClient)
{
this.JobClient = jobClient;
}
[Queue("default")]
[Hangfire.Dashboard.Management.Support.Job]
[DisplayName("呼叫內部方法")]
[Description("呼叫內部方法")]
[AutomaticRetry(Attempts = 3)] //自動重試
[DisableConcurrentExecution(90)] //禁止使用並行
public void Action(PerformContext context = null, IJobCancellationToken cancellationToken = null)
{
if (cancellationToken.ShutdownToken.IsCancellationRequested)
{
return;
}
Console.WriteLine("Action 方法被調用");
context.WriteLine($"測試用,Now:{DateTime.Now}");
}
public void EnqueueAction()
{
this.JobClient.Enqueue(() => this.Action(null, JobCancellationToken.Null));
}
}
用 NSub 建立出假物件,呼叫被側方法,最後用 client.Received()
驗證 IBackgroundJobClient.Create
是否有調用 Action
方法
[TestMethod]
public void 驗證有呼叫Create方法()
{
//arrange
var client = Substitute.For<IBackgroundJobClient>();
var demoJob = new DemoJob(client);
//act
demoJob.EnqueueAction();
//assert
client.Received()
.Create(Arg.Is<Job>(p => p.Method.Name == "Action"),
Arg.Is<EnqueuedState>(p => p.Name == "Enqueued"));
}
集成測試
只有測試 IBackgroundJobClient
和 Action
的互動還不夠,因為 Action
並沒有真正的跑到,要怎麼讓測試案例直接測到 Action 方法?
-
直接測 Action 方法,由於
Action
方法還依賴了 Hangfire 的物件,所以必須要解除依賴才可以,這我就不演練了。 - 把
BackgroundJobServer
架起來,然後用IBackgroundJobClient
建立排程。
BackgroundJobServer
下面的例子
- 用
BackgroundJobServer
把 Hangfire Server 建立起來。 - 用
BackgroundJobClient
調用DemoJob.EnqueueAction
方法。 BackgroundJobServer
、BackgroundJobClient
這兩個都用到了相同的JobStorage
。- 由於這是非同步的服務,所以我讓 Server 的輪詢時間為 0 秒
SchedulePollingInterval = new TimeSpan(0, 0, 0)
,測試案例跑慢一點Thread.Sleep(5000)
[TestClass]
public class BackgroundJobServer_IntegrateTest
{
private static BackgroundJobServer s_jobServer;
private static JobStorage s_storage;
[ClassInitialize]
public static void ClassInitialize(TestContext context)
{
var options = new BackgroundJobServerOptions
{
SchedulePollingInterval = new TimeSpan(0, 0, 0),
};
s_storage = new MemoryStorage();
s_jobServer = new BackgroundJobServer(options, s_storage);
}
[ClassCleanup]
public static void ClassCleanup()
{
s_jobServer?.Dispose();
}
[TestMethod]
public void 集成測試()
{
var job = new DemoJob();
var client = new BackgroundJobClient(s_storage);
job.JobClient = client;
job.EnqueueAction();
Thread.Sleep(5000);
}
}
看測試報告驗證被測方法有被執行到。
BackgroundJobServer via OWIN
這裡我改用 OWIN WebApp.Start
把 BackgroundJobServer
建立起來,其他的就跟上面的例子差不多,就不浪費篇幅囉
[TestClass]
public class BackgroundJobServerOwin_IntegrateTest
{
private const string HOST_ADDRESS = "http://localhost:9527";
private static IDisposable s_webApp;
[ClassCleanup]
public static void ClassCleanup()
{
s_webApp?.Dispose();
}
[ClassInitialize]
public static void ClassInitialize(TestContext context)
{
s_webApp = WebApp.Start<Startup>(HOST_ADDRESS);
Console.WriteLine("Hangfire service start...");
}
[TestMethod]
public void 集成測試()
{
var job = new DemoJob();
var client = new BackgroundJobClient();
job.JobClient = client;
job.EnqueueAction();
Thread.Sleep(5000);
}
}
參考資源
https://docs.hangfire.io/en/latest/background-methods/writing-unit-tests.html
範例程式
https://github.com/yaochangyu/sample.dotblog/tree/master/Hangfire/Lab.HangfireUnitTes
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET