这段日子的开发一直使用Asp.net Boilerplate ,称之为项目模板自然就有开发中常用的功能,测试框架也在其中,土牛的ABP源代码都有通过测试,很遗憾的是我之前没有写过测试,不会就要去找资料查找一下测试开发的概念。
这篇随笔就是要记录一下发生在自己身上的Getting Started with Testing。
软件测试 (software testing) 描绘一种用来促进鉴定软件的正确性、完整性、安全性和质量的过程,软件测试永远不可能完整的确立任意电脑软件的正确性。然而,在可计算理论(计算机科学的一个支派) 一个简单的数学证明推断出下列结果:不可能完全解决所谓“死机”,指任意计算机程序是否会进入死循环,或者罢工并产生输出问题。换句话说,软件测试是一种实际输出与预期输出间的审核或者比较过程。软件测试的经典定义是:在规定的条件下对程序进行操作,以发现程序错误,衡量软件质量,并对其是否能满足设计要求进行评估的过程。想想其实编程中测试无处不在,没有测试我们无法相信代码就一定可以正常执行。之前就遇到过这样的一些事如果代码写的多了,一个业务的执行操作要执行很多个方法,而且最后要去第三方接口拿回来数据在操作数据库,这样如果我要验证数据和仓储方法就要走完整个流程,有了单元测试这个问题就变得很好处理,单元测试 又称模块测试 是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。
既然测试如此的重要,.NET平台下也自然有针对测试开发的组件,XUNIT,NUNIT 这些都是用于.NET 平台下的测试工具,ABP项目模板中使用了XUNIT ,于是我也先看看XUNIT的介绍 ,中文的以后发出来...
XUNIT 的这篇介绍写的很简单,它有Fact,Theory的注解对方法测试,接下来的ABP项目中我便使用了它们。
在ABP中我将这部分的实现放到了单独的模块中
Application , Core ,EntityFramework ,Web ,WebApi , SACSLibrary
下边的图是具体改写的地方:
在SACSLibrary中我对SS(GDS)的最佳实践进行了改写,去除了一些对当前账号没有用处的服务,暂时性的把REST API 查询的这部分拿过来改写,它的REST 查询功能需要一个 SESSION LESS ,而且每次获取一个SESSION 它的生命周期长达一周,这就意味着我这一周的时间内只要是去做查询操作都可以用这个相同的SESSION。这就存在一个问题如何维护SESSION从而保证每次查询操作都能正常完成?自己写服务感觉有点麻烦,早早听说ABP有后台工作者这个便捷的功能,看来这次可以用上了
1 [UnitOfWork] 2 protected async override void DoWork() 3 { 4 Logger.Debug("------backgroundworker begin"); 5 6 using (CurrentUnitOfWork.DisableFilter(AbpDataFilters.MayHaveTenant)) 7 { 8 var validtokens = _tokenDetailsRepository.FirstOrDefault(t => t.IsValid); 9 10 if (validtokens == null || System.DateTime.Now >= validtokens.ExpirationDate) 11 { 12 if (validtokens != null) 13 { 14 validtokens.IsValid = false; 15 } 16 string clientid = _authorizationManager.CreateCredentialsString(_authorizationManager.CurrenConfig.UserId, _authorizationManager.CurrenConfig.Group, _authorizationManager.CurrenConfig.ClientSecret); 17 var response = await _authorizationManager.AuthorizeAsync(clientid); 18 TokenHolder tokenHolder; 19 if (response.IsSuccess) 20 { 21 var value = response.Value; 22 tokenHolder = TokenHolder.Valid(value.AccessToken, value.ExpiresIn); 23 TokenDetail _td = new TokenDetail() 24 { 25 IsValid = true, 26 Token = tokenHolder.Token, 27 ExpirationDate = tokenHolder.ExpirationDate 28 }; 29 _tokenDetailsRepository.Insert(_td); 30 Timer.Period =(int)(_td.ExpirationDate - System.DateTime.Now).TotalMilliseconds; 31 Logger.Debug(Timer.Period.ToString()+"之后执行"); 32 } 33 else 34 { 35 tokenHolder = TokenHolder.Invalid(response.StatusCode, response.Message); 36 _tokenDetailsRepository.Insert(new TokenDetail() 37 { 38 ErrorMessage = tokenHolder.ErrorMessage, 39 ErrorStatusCode = tokenHolder.ErrorStatusCode 40 }); 41 Timer.Period = 1000; 42 } 43 } 44 else 45 { 46 Timer.Period = (int)(validtokens.ExpirationDate - System.DateTime.Now).TotalMilliseconds; 47 Logger.Debug($"下次执行更新在 {Timer.Period} 毫秒之后"); 48 } 49 50 51 } 52 53 54 Logger.Debug("------backgroundworker end"); 55 }
这样目前测试我将执行时间调的小一些可以通过,不过不知道有没有使用不当的地方。
这里边是调用获取SESSION LESS的方法,这个方法我在写单元测试的项目里边测试通过。因为里边用到了仓储,但是数据库里边的数据我又没有办法用于测试,Effort(Entity Framework Fake ObjectContext Realization Tool) 这个工具我目前只是知道他可以MOCK,可以用来模拟操作数据库其他的并不了解了。虽然我不知道怎么使用原始的Effort,但是ABP封装了它这很方便我们去使用。
测试SESSION LESS
1 [Fact] 2 public async void BFM_Session_LESS_Test_Create_token() 3 { 4 var _config = new SACS.Library.Configuration.SampleConfigProvider(); 5 var obj = Resolve<IAuthorizationManager>(new 6 { 7 config = _config 8 }); 9 string clientid = obj.CreateCredentialsString(_config.UserId, _config.Group, _config.ClientSecret); 10 var response = await obj.AuthorizeAsync(clientid); 11 TokenHolder tokenHolder; 12 if (response.IsSuccess) 13 { 14 var value = response.Value; 15 tokenHolder = TokenHolder.Valid(value.AccessToken, value.ExpiresIn); 16 } 17 else 18 { 19 tokenHolder = TokenHolder.Invalid(response.StatusCode, response.Message); 20 } 21 response.IsSuccess.ShouldBe<bool>(true); 22 }
测试这个MOCK Repository
1 [Fact] 2 public async void BFM_Session_LESS_Test_InsertToken_Reporitory() 3 { 4 UsingDbContext((context) => 5 { 6 context.DisableAllFilters(); 7 context.TokenDetails.Add(new TokenDetail() 8 { 9 IsValid = true, 10 ExpirationDate = System.DateTime.Now.AddDays(5), 11 Token = "session_less" 12 }); 13 14 }); 15 var _repository = Resolve<IRepository<TokenDetail>>(); 16 _repository.ShouldNotBeNull(); 17 var backid = await _repository.InsertAndGetIdAsync(new TokenDetail() 18 { 19 Token = "sdsdsdsdsdsdsdsdsdsdsd", 20 IsValid = true, 21 ExpirationDate = System.DateTime.Now.AddDays(6) 22 }); 23 backid.ShouldBe(2); 24 }
测试改写之前的RestClient
1 [Fact] 2 public async void BFM_Query_Test_RestClient_Test() 3 { 4 UsingDbContext((context) => 5 { 6 context.DisableAllFilters(); 7 context.TokenDetails.Add(new TokenDetail() 8 { 9 IsValid = true, 10 ExpirationDate = System.DateTime.Now.AddDays(5), 11 Token = "T1RLAQIQMgZTCKXi+NCWBKaZ0S48I4QxAKw3ZVQyGyXazuIBNnAACQJq5rbfyr1AvNa0Y/7nf+YgPh8QKen+BTOJOwB6SFs9JsvjQpeXAfZlBvvXc3Qi4amQ8SqK7DATQCDiNWcXurfd77naZwwAczgjGc1LPF1XK3AXpV7N8Z2OtN3COZIyK4vAu+SK6IdHU3p/" 12 }); 13 14 }); 15 var configs = Resolve<SACS.Library.Configuration.IConfigProvider>(); 16 configs.ShouldNotBeNull(); 17 var _tokendetail = Resolve<IRepository<TokenDetail>>(); 18 _tokendetail.ShouldNotBeNull(); 19 _tokendetail.Count().ShouldBe(1); 20 var _tokenmangager = Resolve<IAuthorizationManager>(new 21 { 22 config = configs 23 24 }); 25 26 _tokenmangager.ShouldNotBeNull(); 27 28 var _restclient = Resolve<RestClient>(new 29 { 30 config = configs, 31 tokendetailrepository = _tokendetail, 32 restAuthorizationManager = _tokenmangager 33 34 }); 35 36 BargainFinderMaxPostRQ _bfmpostrq = new BargainFinderMaxPostRQ() 37 { 38 //......略 49 }; 50 IActivity activity = new BargainFinderMaxActivity(_restclient, _bfmpostrq); 51 Workflow workflow = new Workflow(activity); 52 SharedContext sharedContext = await workflow.RunAsync(); 53 BargainFinderMaxVM model = ViewModelFactory.CreateBargainFinderMaxVM(sharedContext); 54 model.ErrorMessage.ShouldBeEmpty(); 55 model.ResponseJson.ShouldNotBeNullOrEmpty(); 56 }
测试改写之后的RestClient ,用Theory 特性标注并指定了多组测试数据
1 public class BFM_Test_Data 2 { 3 public static IEnumerable<object[]> BargainFinderMaxPostRQCs { 4 get { 5 return new[] { 6 new object[] { 7 BFM_Query_Test_RestClient_LoadCpData1() // 略 8 },new object[] { 9 BFM_Query_Test_RestClient_LoadCpData2() //略 10 }, 11 new object[] { 12 BFM_Query_Test_RestClient_LoadCpData3() //略 13 } 14 }; 15 } } 16 }
测试方法:
[Theory] [MemberData("BargainFinderMaxPostRQCs",MemberType = typeof(BFM_Test_Data))]
public async void BFM_Query_Test_RestClient_TestCP(BargainFinderMaxPostRQCP cp) { /// ......略 }
好了,今天就记录到这里了。╰( ̄▽ ̄)╭
相关的文档 :
:Using Effort -Entity Framework Unit Testing Tool
:Getting Started With XUnit.net(Destop)
:Unit Testing in C# using XUnit,Entity Framework,Effort and ASP.NET Boilerplate