1、介紹數據庫
在本文中,我將介紹如何爲基於ASP.NET Boilerplate的項目建立單元測試。 我將使用本文開發的相同的應用程序(使用AngularJs,ASP.NET MVC,Web API和EntityFramework來構建NLayered單頁面Web應用程序)而不是建立要測試的新應用程序。 解決方案結構就是這樣:架構
咱們將測試項目的應用服務。 它包括SimpleTaskSystem.Core,SimpleTaskSystem.Application和SimpleTaskSystem.EntityFramework項目。 您能夠閱讀本文,瞭解如何構建此應用程序。 在這裏,我將專一於測試。框架
參照項目:http://pan.baidu.com/s/1gf9xEU3異步
2、建立一個項目async
我建立了一個名爲SimpleTaskSystem.Test的新類庫項目,並添加了如下nuget包:ide
當咱們添加這些包時,它們的依賴關係也將被自動添加。 最後,咱們應該添加對SimpleTaskSystem.Application,SimpleTaskSystem.Core和SimpleTaskSystem.EntityFramework程序集的引用,由於咱們將測試這些項目。函數
2、準備一個基礎測試類單元測試
1,爲了更容易地建立測試類,我將建立一個準備假數據庫鏈接的基類:測試
/// <summary> /// 這是全部測試類的基礎類。 /// 它準備了ABP系統,模塊和一個僞造的內存數據庫。 /// 具備初始數據的種子數據庫(<see cref =「SimpleTaskSystemInitialDataBuilder」/>)。 /// 提供使用DbContext輕鬆使用的方法。 /// </summary> public abstract class SimpleTaskSystemTestBase : AbpIntegratedTestBase<SimpleTaskSystemTestModule> { protected SimpleTaskSystemTestBase() { //種子初始數據 UsingDbContext(context => new SimpleTaskSystemInitialDataBuilder().Build(context)); } protected override void PreInitialize() { //假DbConnection使用Effort! LocalIocManager.IocContainer.Register( Component.For<DbConnection>() .UsingFactoryMethod(Effort.DbConnectionFactory.CreateTransient) .LifestyleSingleton() ); base.PreInitialize(); } public void UsingDbContext(Action<SimpleTaskSystemDbContext> action) { using (var context = LocalIocManager.Resolve<SimpleTaskSystemDbContext>()) { context.DisableAllFilters(); action(context); context.SaveChanges(); } } public T UsingDbContext<T>(Func<SimpleTaskSystemDbContext, T> func) { T result; using (var context = LocalIocManager.Resolve<SimpleTaskSystemDbContext>()) { context.DisableAllFilters(); result = func(context); context.SaveChanges(); } return result; } }
該基類繼承了AbpIntegratedTestBase,它是一個初始化了ABP系統的基類,定義了 protected IIocManager LocalIocManager { get; } 。每一個測試都會使用這個專用的IIocManager。所以,測試之間是相互隔離的。ui
在SimpleTaskSystemTestBase的PreInitialize方法中,咱們正在使用Effort註冊DbConnection到依賴注入系統(PreInitialize方法用於運行一些代碼,僅用於ABP初始化)。 咱們將其註冊爲Singleton(用於LocalIocConainer)。 所以,即便咱們在同一測試中建立了多個DbContext,測試中也將使用相同的數據庫(和鏈接)。
SimpleTaskSystemTestBase的UsingDbContext方法使得當咱們須要直接使用DbContect來處理數據庫時,能夠更容易地建立DbContextes。 在構造函數中,咱們使用它。 另外,咱們將在測試中看到如何使用它。
SimpleTaskSystemDbContext必須具備獲取DbConnection的構造函數才能使用該內存數據庫。 因此,我添加下面的構造函數接受一個DbConnection:
public class SimpleTaskSystemDbContext : AbpDbContext { public virtual IDbSet<Task> Tasks { get; set; } public virtual IDbSet<Person> People { get; set; } public SimpleTaskSystemDbContext() : base("Default") { } public SimpleTaskSystemDbContext(string nameOrConnectionString) : base(nameOrConnectionString) { } //這個構造函數用於測試 public SimpleTaskSystemDbContext(DbConnection connection) : base(connection, true) { } }
在SimpleTaskSystemTestBase的構造函數中,咱們還在數據庫中建立一個初始數據。 這很重要,由於一些測試須要數據庫中存在的數據。 SimpleTaskSystemInitialDataBuilder類填充數據庫,以下所示:
public class SimpleTaskSystemInitialDataBuilder { public void Build(SimpleTaskSystemDbContext context) { //添加一些人 context.People.AddOrUpdate( p => p.Name, new Person {Name = "Isaac Asimov"}, new Person {Name = "Thomas More"}, new Person {Name = "George Orwell"}, new Person {Name = "Douglas Adams"} ); context.SaveChanges(); //添加一些任務 context.Tasks.AddOrUpdate( t => t.Description, new Task { Description = "my initial task 1" }, new Task { Description = "my initial task 2", State = TaskState.Completed }, new Task { Description = "my initial task 3", AssignedPerson = context.People.Single(p => p.Name == "Douglas Adams") }, new Task { Description = "my initial task 4", AssignedPerson = context.People.Single(p => p.Name == "Isaac Asimov"), State = TaskState.Completed }); context.SaveChanges(); } }
咱們全部的測試類都將從SimpleTaskSystemTestBase繼承。 所以,全部測試都將經過使用具備初始數據的假數據庫初始化ABP來啓動。 咱們還能夠爲此基類添加經常使用的幫助方法,以便使測試更容易。
2,咱們應該建立一個專門用於測試的模塊。 這是SimpleTaskSystemTestModule在這裏:
[DependsOn( typeof(SimpleTaskSystemDataModule), typeof(SimpleTaskSystemApplicationModule) )] public class SimpleTaskSystemTestModule : AbpModule { }
此模塊僅定義依賴模塊,將進行測試。
3、建立第一個測試
咱們將建立第一個單元測試來測試TaskAppService類的CreateTask方法。
TaskAppService類和CreateTask方法定義以下:
public class TaskAppService : ApplicationService, ITaskAppService { private readonly ITaskRepository _taskRepository; private readonly IRepository<Person> _personRepository; public TaskAppService(ITaskRepository taskRepository, IRepository<Person> personRepository) { _taskRepository = taskRepository; _personRepository = personRepository; } public void CreateTask(CreateTaskInput input) { Logger.Info("Creating a task for input: " + input); var task = new Task { Description = input.Description }; if (input.AssignedPersonId.HasValue) { task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value); } _taskRepository.Insert(task); } //...other methods }
咱們先建立一個測試來測試CreateTask方法。
public class TaskAppService_Tests : SimpleTaskSystemTestBase { private readonly ITaskAppService _taskAppService; public TaskAppService_Tests() { //建立被測試的類(SUT(Software Under Test) - 被測系統) _taskAppService = LocalIocManager.Resolve<ITaskAppService>(); } [Fact] public void Should_Create_New_Tasks() { //準備測試 var initialTaskCount = UsingDbContext(context => context.Tasks.Count()); var thomasMore = GetPerson("Thomas More"); //運行SUT _taskAppService.CreateTask( new CreateTaskInput { Description = "my test task 1" }); _taskAppService.CreateTask( new CreateTaskInput { Description = "my test task 2", AssignedPersonId = thomasMore.Id }); //檢查結果 UsingDbContext(context => { context.Tasks.Count().ShouldBe(initialTaskCount + 2); context.Tasks.FirstOrDefault(t => t.AssignedPersonId == null && t.Description == "my test task 1").ShouldNotBe(null); var task2 = context.Tasks.FirstOrDefault(t => t.Description == "my test task 2"); task2.ShouldNotBe(null); task2.AssignedPersonId.ShouldBe(thomasMore.Id); }); } private Person GetPerson(string name) { return UsingDbContext(context => context.People.Single(p => p.Name == name)); } }
如前所述,咱們從SimpleTaskSystemTestBase繼承。 在單元測試中,咱們應該建立要測試的對象。 在構造函數中,我使用LocalIocManager(依賴注入管理器)來建立一個ITaskAppService(它建立了TaskAppService,由於它實現了ITaskAppService)。 以這種方式,我擺脫了建立依賴關係的模擬實現。
Should_Create_New_Tasks是測試方法。 它使用xUnit的Fact屬性進行裝飾。 所以,xUnit瞭解這是一種測試方法,它運行該方法。
在測試方法中,咱們一般遵循AAA模式,包括三個步驟:
在Should_Create_New_Tasks方法中,咱們將建立兩個任務,一個將被分配給Thomas More。 因此,咱們的三個步驟是:
在這裏,UsingDbContext方法能夠幫助咱們直接使用DbContext。 若是此測試成功,咱們瞭解若是咱們提供有效的輸入,CreateTask方法能夠建立任務。 此外,存儲庫正在工做,由於它將Tasks插入數據庫。
要運行測試,咱們經過選擇TEST \ Windows \ Test Explorer打開Visual Studio測試資源管理器:
而後咱們點擊測試資源管理器中的「所有運行」連接。 它在解決方案中找到並運行全部測試:
如上所示,咱們的第一個單元測試經過。恭喜! 若是測試或測試代碼不正確,測試將失敗。 假設咱們已經忘記將賦值賦給給某人(要測試它,註釋掉TaskAppService.CreateTask方法中的相關行)。 當咱們運行測試時,它將失敗:
Shouldly庫使得失敗的消息更清晰。 它也使寫入斷言變得容易。 比較xUnit的Assert.Equal與Shouldly的ShouldBe擴展方法:
Assert.Equal(thomasMore.Id, task2.AssignedPersonId); //Using xunit's Assert task2.AssignedPersonId.ShouldBe(thomasMore.Id); //Using Shouldly
我認爲第二個更容易和天然地寫和閱讀。 應該有不少其餘的擴展方法,使咱們的生活更輕鬆。 看到它的文檔。
4、測試異常
我想爲CreateTask方法建立第二個測試。 可是,此次輸入無效:
[Fact] public void Should_Not_Create_Task_Without_Description() { //說明未設置 Assert.Throws<AbpValidationException>(() => _taskAppService.CreateTask(new CreateTaskInput())); }
我但願CreateTask方法拋出AbpValidationException,若是我沒有設置描述建立任務。 由於在CreateTaskInput DTO類中將描述屬性標記爲必需(請參閱源代碼)。 若是CreateTask引起異常,則此測試成功,不然失敗。 注意; 驗證輸入和拋出異常是由ASP.NET Boilerplate基礎架構完成的。
5、在測試中使用存儲庫
我將測試從一我的到另外一我的分配一個任務:
//試圖將Isaac Asimov的任務分配給Thomas More [Fact] public void Should_Change_Assigned_People() { //咱們可使用存儲庫而不是DbContext var taskRepository = LocalIocManager.Resolve<ITaskRepository>(); //獲取測試數據 var isaacAsimov = GetPerson("Isaac Asimov"); var thomasMore = GetPerson("Thomas More"); var targetTask = taskRepository.FirstOrDefault(t => t.AssignedPersonId == isaacAsimov.Id); targetTask.ShouldNotBe(null); //運行 SUT _taskAppService.UpdateTask( new UpdateTaskInput { TaskId = targetTask.Id, AssignedPersonId = thomasMore.Id }); //檢查結果 taskRepository.Get(targetTask.Id).AssignedPersonId.ShouldBe(thomasMore.Id); }
在這個測試中,我使用ITaskRepository執行數據庫操做,而不是直接使用DbContext。 您可使用這些方法之一或混合。
6、測試異步方法
咱們也可使用xUnit來測試異步方法。 請參閱寫入以測試PersonAppService的GetAllPeople方法的方法。 GetAllPeople方法是異步的,因此測試方法也應該是異步的:
[Fact] public async Task Should_Get_All_People() { var output = await _personAppService.GetAllPeople(); output.People.Count.ShouldBe(4); }
7、概要
在本文中,我想顯示簡單的測試項目開發ASP.NET Boilerplate應用程序框架。 ASP.NET Boilerplate爲實現測試驅動開發提供了良好的基礎設施,或者簡單地爲您的應用程序建立了一些單元/集成測試。
Effort庫提供了一個與EntityFramework工做良好的假數據庫。 只要您使用EntityFramework和LINQ執行數據庫操做,它就能夠工做。 若是你有一個存儲過程而且要測試它,Effort不工做。 對於這種狀況,我建議使用LocalDB。