1. 程式人生 > >構建一個ASP.NET Wiki來解釋TDD

構建一個ASP.NET Wiki來解釋TDD

目錄

介紹

什麼是TDD

TDD的好處

TDD不是什麼

TDD生命週期

限制

什麼是BDD?

TDD先決條件

例子

第1步:實體到DTO對映

第2步:Markdown到HTML轉換

第3步:使用Markdown進行EnHance對映

第4步:設定資料庫遷移

第5步:實體CRUD

第6步:測試服務

第7步:繼續測試UI

編輯

檢視

列表

結論


TDDBDD用例子解釋

介紹

在本文中,我將嘗試解釋什麼是TDD以及它在開發過程中的幫助。有很多資源和書籍可以做到這一點,但我將嘗試用一個簡單的例項介紹。這比你在書中讀到的嚴格定義更像是一個

哲學概述。可能純粹的這種方法的支持者會發現這個解釋有點不完整(對不起......),但我認為這足以開始學習和理解基礎知識。我的主要目的不是寫另一本關於TDD的書,而只是用清晰簡單的單詞解釋它是什麼,這樣初學者也可以理解和接受它。

完整的原始碼可以在github上找到

什麼是TDD

從維基百科的定義開始:

引用:

測試驅動開發TDD)是一個軟體開發過程,它依賴於非常短的開發週期的重複:需求變成非常具體的測試用例,然後軟體被改進以僅通過新的測試。這與軟體開發相反,軟體開發允許新增未經證明符合要求的軟體。

清楚嗎?TDD的主要目的是建立一種策略,其中測試將推動開發過程,以使編碼更有效,更高效,減少迴歸的。

先決條件是以較小的步驟分解大任務並使用單元測試進行開發。這允許您處理較小的程式碼片段,使其工作,然後將許多工作部分整合在一起。

TDD的好處

TDD引入您的編碼體驗將達到一個轉折點。以下是最重要的好處的簡短列表:

  1. 專注於非常重要的一點:你將被要求分解問題,這將有助於關注最重要的事情。
  2. 處理更簡單的任務:每次使用單個、小型的任務可簡化故障排除並加快開發速度。你將不會陷入編寫所有程式碼的情況,然後某些東西不起作用,你不知道為什麼。
  3. 簡化的整合:當完成多個工作功能時,將所有功能放在一起將是一件愉快而輕鬆的任務。在迴歸的情況下,您將事先知道哪部分程式碼是壞的。
  4. 免費測試
    :完成完整任務後,許多單元測試仍然存在,可以用作整合\單元測試來驗證程式碼並避免迴歸。

TDD不是什麼

TDD是一種很好的方法,但不是:

  • 替代測試(單元測試,驗收測試,UI測試)
  • 你可以在一天內學會的東西
  • 為你編寫程式碼的東西
  • 一個神聖的人,可以驅除程式碼中的bug

TDD生命週期

TDD主要由三個步驟組成:

  1. 寫單元測試(RED)。
  2. 讓它工作(綠色)。
  3. 重構。

在該示例中,您可以編寫單元測試,使用其中的程式碼實現該功能,直到它工作,然後重構將這段程式碼放在需要的地方。

步驟1,2:使測試工作

public class StripTest
{
    [Fact]
    public static void StripHTml()
    {
        string test="<h1>test</h1>";
        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="<h1>test</h1>";
        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

BDDTDD的增強,它考慮了單元測試是限制性的情況。此擴充套件使用開發人員作為單元測試,保持BDD的理念。您仍然可以將複雜任務分解為較小的任務,使用使用者行為進行測試,和在純後端任務中使用TDD具有相同的優勢。

TDD先決條件

在團隊合作中,除了瞭解所有涉及的技術之外,所有隊友都必須瞭解並接受這一理念。

首先,您的程式碼必須由強大的單元測試系統授權:

  • .NET.NET Core:內建Visual Studioxunit(第二個是我個人的,首選的選擇)
  • Javajunit執行得很好,我不需要找到另一個解決方案
  • PHPPHP單元在所有情況下都適合我

然後,重要且必須:具有允許在測試期間模擬或重新建立正確行為的體系結構。我說的是在測試期間可以在記憶體或本地資料庫中工作的ORM,也可以使用服務或儲存庫模式。使用DI框架(內建在.NET Core中,autofac或其他任何......)也有幫助。

最後但並非最不重要:一個完善的構建過程,整合在一個持續的整合流程中,除了正確的配置之外,確定哪個單元測試在整合期間在其上執行是有意義的,以及在本地執行的內容。

例子

讓我們嘗試在現實世界的例子中實踐我們對TDD的瞭解。我想用這種方法建立一個wiki。我的意思是一個簡單的維基,使用者登入,寫下標記頁併發布。

首先,我會將任務分解為較小的後續活動。每個子部分將使用小型單元測試開發。我將專注於wiki 頁面CRUD

1步:實體到DTO對映

  1. 編寫實體。
  2. 編寫wiki 頁面DTO
  3. 編寫將實體對映到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<wikipagedto>(source);
    Assert.Equal("title", result.Title);
    Assert.Equal(1, result.Version);
}

// From Mapping configuration, code omitted for brevity
 public MappingProfile()
{
    CreateMap<wikipageentity, wikipagedto="">().ReverseMap();
}

2步:MarkdownHTML轉換

  1. 建立一個轉換markdownHTML 的方法:
//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("<h1>testo</h1>", testo);
}
// after refactoring ( method moved to helper
[Fact]
public void ConvertMarkDownHelper()
{
    Assert.Equal("<h1>testo</h1>", 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對映

  1. 更改增加HTML欄位計算的對映:
// mapped profile changed
public class MappingProfile : Profile
{
  

    public MappingProfile()
    {
        SlugHelper helper = new SlugHelper();
        CreateMap<wikipageentity, wikipagedto="">()
            .ForMember(dest => dest.BodyMarkDown, (expr) => expr.MapFrom<string>(x => x.Body))
            .ForMember(dest => dest.BodyHtml, 
            (expr) => expr.MapFrom<string>(x => MarkdownHelper.ConvertToHtml(x.Body)))
            .ReverseMap();



        CreateMap<wikipagebo,wikipageentity>()
            .ForMember(dest => dest.Body, (expr) => expr.MapFrom<string>(x => x.BodyMarkDown))
            .ForMember(dest => dest.Slug, 
                      (expr) => expr.MapFrom<string>(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<wikipagedto>(source);
    Assert.Equal("title", result.Title);
    Assert.Equal(1, result.Version);
    Assert.Equal("<h1>prova h1</h1>", result.BodyHtml);
}

4步:設定資料庫遷移

  1. 執行Add-Migration指令碼。
  2. 建立一個在記憶體中工作的單元測試來測試它。
[Fact]
public void MigrateInMemory()
{
    
    var optionsBuilder = new DbContextOptionsBuilder<DatabaseContext>();
    optionsBuilder.UseInMemoryDatabase();

    using (var db = new DatabaseContext(optionsBuilder.Options))
    {
        db.Database.Migrate();
    }
    // No error assert migration was OK
}

5步:實體CRUD

  1. 寫一個CRUD測試。
  2. 測試一下。
[Fact]
public void CrudInMemory()
{
    var optionsBuilder = new DbContextOptionsBuilder<DatabaseContext>();
    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步:測試服務

  1. 使用業務邏輯建立服務。
  2. 測試一下。
[Fact]
public void TestSave()
{
    var optionsBuilder = new DbContextOptionsBuilder<DatabaseContext>();
    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測試。因此,我不是編寫所有程式碼然後測試它,而是在多個子活動中分解問題並逐個測試:

編輯

  1. 準備表單,並進行測試。
  2. 準備模型,測試從表單提交的內容填充後端模型。
  3. 整合服務以儲存資料,進行測試。

檢視

  1. 準備模型,傳遞到檢視,測試它。
  2. 將模型與服務整合,以獲得真實資料。測試一下。

列表

  1. 準備檢視模型,將假資料傳遞給UI,測試它。
  2. 整合服務,測試它。

結論

TDD是一種推動測試支援的開發過程的方法。這有助於以多種方式編碼,但要求所有隊友都有一些基礎知識。一旦完成此步驟,您將處理更簡單的任務和許多可以重用的測試。如果開發時需要編寫單元測試,這個過程將有助於避免迴歸並更快地達到目標。此外,如果您的應用程式由於複雜性而難以測試,那麼您可以保持執行BDD的相同理念。

 

原文地址:https://www.codeproject.com/Articles/1267361/Build-an-ASP-NET-Wiki-to-Explain-TDD