本文共 7741 字,大约阅读时间需要 25 分钟。
目录
TDD和BDD用例子解释
在本文中,我将尝试解释什么是TDD以及它在开发过程中的帮助。有很多资源和书籍可以做到这一点,但我将尝试用一个简单的实例介绍。这比你在书中读到的严格定义更像是一个“哲学”概述。可能纯粹的这种方法的支持者会发现这个解释有点不完整(对不起......),但我认为这足以开始学习和理解基础知识。我的主要目的不是写另一本关于TDD的书,而只是用清晰简单的单词解释它是什么,这样初学者也可以理解和接受它。
。
从维基百科的定义开始:
引用:
测试驱动开发(TDD)是一个,它依赖于非常短的开发周期的重复:需求变成非常具体的,然后软件被改进以仅通过新的测试。这与软件开发相反,软件开发允许添加未经证明符合要求的软件。
清楚吗?TDD的主要目的是创建一种策略,其中测试将推动开发过程,以使编码更有效,更高效,减少回归的。
先决条件是以较小的步骤分解大任务并使用单元测试进行开发。这允许您处理较小的代码片段,使其工作,然后将许多工作部分集成在一起。
将TDD引入您的编码体验将达到一个转折点。以下是最重要的好处的简短列表:
TDD是一种很好的方法,但不是:
TDD主要由三个步骤组成:
在该示例中,您可以编写单元测试,使用其中的代码实现该功能,直到它工作,然后重构将这段代码放在需要的地方。
步骤1,2:使测试工作
public class StripTest{ [Fact] public static void StripHTml() { string test="test
"; string expected="test"; string result=StripHTML(test); Assert.Equal(expected,result); } public static string StripHTML(string input) { return Regex.Replace(input, "<.*?>", String.Empty); } }
第3步:重构
public class StripTest{ [Fact] public static void StripHTml() { string test="test
"; string expected="test"; string result=HtmlHelper.StripHTML(test); Assert.Equal(expected,result); } }//somewhere elsepublic static class HtmlHelper{ public static string StripHTML(string input) { return Regex.Replace(input, "<.*?>", String.Empty); }}
在许多情况下,很难编写涵盖实际代码使用情况的单元测试。完全逻辑的过程很容易,但是当我们要涉及数据库或UI时,编写工作的量会增加,并且在许多情况下,可能会超过好处。有一些最佳实践和框架对此有所帮助,但一般来说,并非应用程序的所有部分都可以使用普通单元测试进行测试。
BDD是TDD的增强,它考虑了单元测试是限制性的情况。此扩展使用开发人员作为单元测试,保持BDD的理念。您仍然可以将复杂任务分解为较小的任务,使用用户行为进行测试,和在纯后端任务中使用TDD具有相同的优势。
在团队合作中,除了了解所有涉及的技术之外,所有队友都必须了解并接受这一理念。
首先,您的代码必须由强大的单元测试系统授权:
然后,重要且必须:具有允许在测试期间模拟或重新创建正确行为的体系结构。我说的是在测试期间可以在内存或本地数据库中工作的ORM,也可以使用服务或存储库模式。使用DI框架(内置在.NET Core中,autofac或其他任何......)也有帮助。
最后但并非最不重要:一个完善的构建过程,集成在一个持续的集成流程中,除了正确的配置之外,确定哪个单元测试在集成期间在其上运行是有意义的,以及在本地运行的内容。
让我们尝试在现实世界的例子中实践我们对TDD的了解。我想用这种方法创建一个wiki。我的意思是一个简单的维基,用户登录,写下标记页并发布。
首先,我会将“长”任务分解为较小的后续活动。每个子部分将使用小型单元测试开发。我将专注于wiki 页面CRUD。
// Database entity public class WikiPageEntity{ [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public Guid Id { get; set; } public int Version { get; set; } public string Slug { get; set; } public string Body { get; set; } public string Title { get; set; }}// DTO model in BLLnamespace WikiCore.Lib.DTO{ public class WikiPageDTO { public string Title { get; set; } public string BodyMarkDown { get; set; } public string BodyHtml { get; set; } public int Version { get; set; } public string Slug { get; set; } }}// From unit test, code omitted for brevitypublic void EntityToDTO(){ WikiPageEntity source = new WikiPageEntity() { Title = "title", Slug = "titleslug", Version =1 }; var result = Mapper.Map(source); Assert.Equal("title", result.Title); Assert.Equal(1, result.Version);}// From Mapping configuration, code omitted for brevity public MappingProfile(){ CreateMap ().ReverseMap();}
//Before refactoring public class MarkdownTest{[Fact]public void ConvertMarkDown(){ var options = new MarkdownOptions { AutoHyperlink = true, AutoNewLines = true, LinkEmails = true, QuoteSingleLine = true, StrictBoldItalic = true }; Markdown mark = new Markdown(options); var testo = mark.Transform("#testo"); Assert.Equal("testo
", testo);}// after refactoring ( method moved to helper[Fact]public void ConvertMarkDownHelper(){ Assert.Equal("testo
", MarkdownHelper.ConvertToHtml("#testo"));}// From markdown helperpublic static class MarkdownHelper{ static MarkdownOptions options; static Markdown converter; static MarkdownHelper() { options = new MarkdownOptions { AutoHyperlink = true, AutoNewLines = true, LinkEmails = true, QuoteSingleLine = true, StrictBoldItalic = true }; converter = new Markdown(options); } public static string ConvertToHtml(string input) { Markdown mark = new Markdown(options); return mark.Transform(input); }}
// mapped profile changedpublic class MappingProfile : Profile{ public MappingProfile() { SlugHelper helper = new SlugHelper(); CreateMap() .ForMember(dest => dest.BodyMarkDown, (expr) => expr.MapFrom (x => x.Body)) .ForMember(dest => dest.BodyHtml, (expr) => expr.MapFrom (x => MarkdownHelper.ConvertToHtml(x.Body))) .ReverseMap(); CreateMap () .ForMember(dest => dest.Body, (expr) => expr.MapFrom (x => x.BodyMarkDown)) .ForMember(dest => dest.Slug, (expr) => expr.MapFrom (x => helper.GenerateSlug(x.Title))); }}// From unit test, code omitted for brevitypublic void EntityToDTO(){ WikiPageEntity source = new WikiPageEntity() { Body = "# prova h1", Title = "title", Slug = "titleslug", Version =1 }; var result = Mapper.Map (source); Assert.Equal("title", result.Title); Assert.Equal(1, result.Version); Assert.Equal(" prova h1
", result.BodyHtml);}
[Fact]public void MigrateInMemory(){ var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseInMemoryDatabase(); using (var db = new DatabaseContext(optionsBuilder.Options)) { db.Database.Migrate(); } // No error assert migration was OK}
[Fact]public void CrudInMemory(){ var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseInMemoryDatabase(); using (var db = new DatabaseContext(optionsBuilder.Options)) { db.Database.Migrate(); db.WikiPages.Add(new Lib.DAL.Model.WikiPageEntity() { Title = "title", Body = "#h1", Slug = "slug" }); db.SaveChanges(); var count=db.WikiPages.Where(x => x.Slug == "slug").Count(); Assert.Equal(1, count); // update, delete steps omitted for brevity }}
[Fact]public void TestSave(){ var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseInMemoryDatabase(); using (var db = new DatabaseContext(optionsBuilder.Options)) { db.Database.Migrate(); db.SaveChanges(); //this recreate same behaviour of asp.net MVC usage DatabaseWikiPageService service = new DatabaseWikiPageService(db, Mapper.Instance); service.Save(new Lib.BLL.BO.WikiPageBO() { BodyMarkDown="#h1", Title="prova prova" }); var item = service.GetPage("prova-prova"); Assert.NotNull(item); }}
一旦使用单元测试测试UI变得复杂,我就切换到BDD并完成了多个步骤来完成UI测试。因此,我不是编写所有代码然后测试它,而是在多个子活动中分解问题并逐个测试:
TDD是一种推动测试支持的开发过程的方法。这有助于以多种方式编码,但要求所有队友都有一些基础知识。一旦完成此步骤,您将处理更简单的任务和许多可以重用的测试。如果开发时需要编写单元测试,这个过程将有助于避免回归并更快地达到目标。此外,如果您的应用程序由于复杂性而难以测试,那么您可以保持执行BDD的相同理念。
原文地址:
转载地址:http://yzzhj.baihongyu.com/