目录
介绍
什么是TDD
TDD的好处
TDD不是什么
TDD生命周期
限制
什么是BDD?
TDD先决条件
例子
第1步:实体到DTO映射
第2步:Markdown到HTML转换
第3步:使用Markdown进行EnHance映射
第4步:设置数据库迁移
第5步:实体CRUD
第6步:测试服务
第7步:继续测试UI
编辑
视图
列表
结论
TDD和BDD用例子解释
介绍在本文中,我将尝试解释什么是TDD以及它在开发过程中的帮助。有很多资源和书籍可以做到这一点,但我将尝试用一个简单的实例介绍。这比你在书中读到的严格定义更像是一个“哲学”概述。可能纯粹的这种方法的支持者会发现这个解释有点不完整(对不起......),但我认为这足以开始学习和理解基础知识。我的主要目的不是写另一本关于TDD的书,而只是用清晰简单的单词解释它是什么,这样初学者也可以理解和接受它。
完整的源代码可以在github上找到。
什么是TDD从维基百科的定义开始:
引用:
测试驱动开发(TDD)是一个软件开发过程,它依赖于非常短的开发周期的重复:需求变成非常具体的测试用例,然后软件被改进以仅通过新的测试。这与软件开发相反,软件开发允许添加未经证明符合要求的软件。
清楚吗?TDD的主要目的是创建一种策略,其中测试将推动开发过程,以使编码更有效,更高效,减少回归的。
先决条件是以较小的步骤分解大任务并使用单元测试进行开发。这允许您处理较小的代码片段,使其工作,然后将许多工作部分集成在一起。
TDD的好处将TDD引入您的编码体验将达到一个转折点。以下是最重要的好处的简短列表:
- 专注于非常重要的一点:你将被要求分解问题,这将有助于关注最重要的事情。
- 处理更简单的任务:每次使用单个、小型的任务可简化故障排除并加快开发速度。你将不会陷入编写所有代码的情况,然后某些东西不起作用,你不知道为什么。
- 简化的集成:当完成多个工作功能时,将所有功能放在一起将是一件愉快而轻松的任务。在回归的情况下,您将事先知道哪部分代码是坏的。
- 免费测试:完成完整任务后,许多单元测试仍然存在,可以用作集成\单元测试来验证代码并避免回归。
TDD是一种很好的方法,但不是:
- 替代测试(单元测试,验收测试,UI测试)
- 你可以在一天内学会的东西
- 为你编写代码的东西
- 一个神圣的人,可以驱除代码中的bug
TDD主要由三个步骤组成:
- 写单元测试(RED)。
- 让它工作(绿色)。
- 重构。
在该示例中,您可以编写单元测试,使用其中的代码实现该功能,直到它工作,然后重构将这段代码放在需要的地方。
步骤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 else
public static class HtmlHelper
{
public static string StripHTML(string input)
{
return Regex.Replace(input, "", String.Empty);
}
}
限制
在许多情况下,很难编写涵盖实际代码使用情况的单元测试。完全逻辑的过程很容易,但是当我们要涉及数据库或UI时,编写工作的量会增加,并且在许多情况下,可能会超过好处。有一些最佳实践和框架对此有所帮助,但一般来说,并非应用程序的所有部分都可以使用普通单元测试进行测试。
什么是BDD?BDD是TDD的增强,它考虑了单元测试是限制性的情况。此扩展使用开发人员作为单元测试,保持BDD的理念。您仍然可以将复杂任务分解为较小的任务,使用用户行为进行测试,和在纯后端任务中使用TDD具有相同的优势。
TDD先决条件在团队合作中,除了了解所有涉及的技术之外,所有队友都必须了解并接受这一理念。
首先,您的代码必须由强大的单元测试系统授权:
- .NET,.NET Core:内置Visual Studio或xunit(第二个是我个人的,首选的选择)
- Java:junit运行得很好,我不需要找到另一个解决方案
- PHP:PHP单元在所有情况下都适合我
然后,重要且必须:具有允许在测试期间模拟或重新创建正确行为的体系结构。我说的是在测试期间可以在内存或本地数据库中工作的ORM,也可以使用服务或存储库模式。使用DI框架(内置在.NET Core中,autofac或其他任何......)也有帮助。
最后但并非最不重要:一个完善的构建过程,集成在一个持续的集成流程中,除了正确的配置之外,确定哪个单元测试在集成期间在其上运行是有意义的,以及在本地运行的内容。
例子让我们尝试在现实世界的例子中实践我们对TDD的了解。我想用这种方法创建一个wiki。我的意思是一个简单的维基,用户登录,写下标记页并发布。
首先,我会将“长”任务分解为较小的后续活动。每个子部分将使用小型单元测试开发。我将专注于wiki 页面CRUD。
第1步:实体到DTO映射- 编写实体。
- 编写wiki 页面DTO。
- 编写将实体映射到DTO的代码。
// 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 BLL
namespace 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 brevity
public 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();
}
第2步:Markdown到HTML转换
- 创建一个转换markdown为HTML 的方法:
//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 helper
public 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);
}
}
第3步:使用Markdown进行EnHance映射
- 更改增加HTML字段计算的映射:
// mapped profile changed
public 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 brevity
public 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);
}
第4步:设置数据库迁移
- 运行Add-Migration脚本。
- 创建一个在内存中工作的单元测试来测试它。
[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
}
第5步:实体CRUD
- 写一个CRUD测试。
- 测试一下。
[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
}
}
第6步:测试服务
- 使用业务逻辑创建服务。
- 测试一下。
[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);
}
}
第7步:继续测试UI
一旦使用单元测试测试UI变得复杂,我就切换到BDD并完成了多个步骤来完成UI测试。因此,我不是编写所有代码然后测试它,而是在多个子活动中分解问题并逐个测试:
编辑- 准备表单,并进行测试。
- 准备模型,测试从表单提交的内容填充后端模型。
- 集成服务以保存数据,进行测试。
- 准备模型,传递到视图,测试它。
- 将模型与服务集成,以获得真实数据。测试一下。
- 准备视图模型,将假数据传递给UI,测试它。
- 整合服务,测试它。
TDD是一种推动测试支持的开发过程的方法。这有助于以多种方式编码,但要求所有队友都有一些基础知识。一旦完成此步骤,您将处理更简单的任务和许多可以重用的测试。如果开发时需要编写单元测试,这个过程将有助于避免回归并更快地达到目标。此外,如果您的应用程序由于复杂性而难以测试,那么您可以保持执行BDD的相同理念。
原文地址:https://www.codeproject.com/Articles/1267361/Build-an-ASP-NET-Wiki-to-Explain-TDD