2015-02-11 112 views
0

我的問題很簡單(與更有經驗的開發者可能也知道):單元測試UI方法

我如何單元測試這個方法(我知道,沒有一種方法,因爲它不是在一個班,但讓假設它是)?

void drawRegularPolygon(int numberOfPoint, Point centerPoint, double radius) 
{ 
    double angleDelta = 2 * PI/numberOfPoint; 

    Point firstPoint = new Point(centerPoint.x + radius, centerPoint.y); 

    for (int i = 1; i < numberOfPoint; i++) { 
     double angle = angleDelta * i; 
     Point secondPoint = new Point(centerPoint.x + radius * cos(angle), centerPoint.y + radius * sin(angle)); 

     DrawLine(firstPoint, secondPoint); 

     firstPoint = secondPoint; 
    } 
} 

我接受的回答說:「你應該重構這個樣子this甚至考慮測試之前」。

編輯1: 我剛剛在我的代碼中發現了一個錯誤,但我留在那裏有一個單元測試應該如何捕捉它的想法。

編輯2: 我也想過另一種解決方案

Array<Line> regularPolygonLines(int numberOfPoint, Point centerPoint, double radius) 
{ 
    Array<Line> lines = new Array<Line>; 

    double angleDelta = 2 * PI/numberOfPoint; 

    Point firstPoint = new Point(centerPoint.x + radius, centerPoint.y); 

    for (int i = 1; i <= numberOfPoint; i++) { 
     double angle = angleDelta * i; 
     Point secondPoint = new Point(centerPoint.x + radius * cos(angle), centerPoint.y + radius * sin(angle)); 

     lines.add(new Line(firstPoint, secondPoint)); 

     firstPoint = secondPoint; 
    } 

    return lines; 
} 

void drawRegularPolygon(int numberOfPoint, Point centerPoint, double radius) 
{ 
    Array<Line> lines = regularPolygonLines(numberOfPoint, centerPoint, radius); 

    for (Line line in lines) { 
     DrawLine(line.firstPoint, line.secondPoint); 
    } 
} 

,並在這其中,regularPolygonLines方法可以檢測,但drawRegularPolygon不能。

+0

你在正確的軌道上你拉出*計算*並隔離*渲染*。事實上,計算可以被測試,您可以將其視爲用戶界面的「業務規則」。它也有助於SRP和清晰度。 – toddmo 2015-03-10 17:20:12

回答

1

有看這個了一些方法,並在實用性相反的順序,

你不測試的用戶界面

通常,當你正在開發一個應用程序,該位是重要的是應用程序功能,因此它通常要好得多才能測試它計算出的正確結果,而不是它以綠色文本顯示它。更糟糕的是,隨着應用程序的開發,以及從一個用戶界面移到另一個用戶界面,整個測試套件突然崩潰,只是因爲您將按鈕從右下移到左下,或將您的綠色文本更改爲黃色文本。我曾經在一些組織中進行過測試,而不是測試如果我點擊這個按鈕我們測試會發生什麼,如果我運行這個命令會發生什麼

但是,由於正在開發一段代碼實際上吸引了一些東西,那麼你可能想要測試一下。

您可以使用UI測試框架

已經有相當多的測試框架(擴展?),如NUnitFormsWhite以及最近WipFlash。這些可以以多種方式工作,提供相當於驅動程序,使您能夠查找元素,與它們交互或在較低級別上移動鼠標並直接單擊元素。其中一些甚至提供屏幕比較功能,使您能夠比較已經渲染的內容和之前存儲的良好渲染。

你嘲笑/僞造的依賴關係,看看他們是否產生您預期 什麼(這可能是你正在尋找的答案)

您也可以使用更BDD喜歡的方式,並使用一組示例來驗證已知的示例。從最簡單的情況開始,雙面形狀或。這個我們可以算出(在我們的頭上),如果我們在點100,100附近以50的半徑繪製它,那麼我們最終應該從100,150到100,50以及另一個從100,50到100,150的線(反之亦然) 。因此,我們現在需要的是獲得DrawLine(...)可以繪製的點的某種方式。

因此,您需要注入一個類來處理繪圖,並且有助於我們用其他東西替換它的功能,以模擬功能,以便我們捕捉到它的操作。現在

public interface IDrawStuff 
{ 
    void DrawLine(Point start, Point end); 
} 

public class RealDrawStuff : IDrawStuff 
{ 

    public void DrawLine(Point start, Point end) 
    { 
    // call the frameworks draw line functionality 
    } 
} 

我們可以簡單地注入我們的模擬功能,這可以用嘲諷的框架,如Moq做,但這樣你就明白這個更簡單,讓我們創造我們自己的現在。在這種情況下,我們只需記住,你的代碼計算

public class MockDrawStuff : IDrawStuff 
{ 
private List<Tuple<Point,Point>> drawnPoints = new List<Tuple<Point,Point>>(); 
public class DrawLine(Point start, Point end) 
{ 
    drawnPoints.Add(new Tuple<Point,Point>(start,end)); 
} 

public void Verify(Tuple<Point,Point>[] expectedPoints) 
{ 
    foreach(var i=0; i<expectedPoints.Count; i++) 
    { 
     var expected = expectedPoints[i]; 
     var actual = drawnPoints[i]; 
     if (actual.Item1.X != expected.Item1.X 
     || actual.Item1.Y != expected.Item1.Y 
     || actual.Item2.X != expected.Item2.X 
     || actual.Item2.Y != expected.Item2.Y) 
     { 
      throw new Exception("Fail: {0} != {1}",expected, actual); 
      //Probably wants more detail, but you get the idea 
     } 
    } 
} 

的點,你的代碼變得

public class MyPolygonRenderer 
{ 
private IDrawStuff renderer; 

    public MyPolygonRender(IDrawStuff renderer) 
    { 
    this.renderer = renderer; 
    } 

    void drawRegularPolygon(int numberOfPoint, Point centerPoint, double radius) 
{ 
double angleDelta = 2 * PI/numberOfPoint; 

Point firstPoint = new Point(centerPoint.x + radius, centerPoint.y); 

for (int i = 1; i < numberOfPoint; i++) { 
    double angle = angleDelta * i; 
    Point secondPoint = new Point(centerPoint.x + radius * cos(angle), centerPoint.y + radius * sin(angle)); 

    // Code change here 
    renderer.DrawLine(firstPoint, secondPoint); 

    firstPoint = secondPoint; 
    } 
} 
} 

這意味着我們現在終於可以寫我們的測試作爲

[TestFixure] //Assuming NUnit 
public class MyPolygonRendererTests 
{ 
    [Test] 
    public void ShouldDrawASimpleLine() 
    { 
     //Given 
     var mockDrawStuff = new MockDrawStuff();   
     var polygonRenderer = new MyPolygonRenderer(mockDrawStuff); 

    //When 
    polygonRenderer.drawRegularPolygon(2, new Point(100,100),50); 

    //Then 
    mockDrawStuff.Verify(new [] { 
     new Tuple<POint,Point>(new Point (100,150), new Point(100,50)), 
     new Tuple<POint,Point>(new Point (100,50), new Point(100,150)) 
    }); 
    } 
} 

然後你可以通過制定3點,4點等例子來建立其他測試。

+0

我想過做最後一個,但如果我不想讓誰使用我的drawRegularPolygon方法來了解實際的渲染?所以我不能將IDrawStuff渲染器作爲構造函數中的公有對象。 – 2015-02-11 21:56:10

+0

另外,你認爲用我的編輯2使用你的第一種方法(不測試UI)會更好嗎? – 2015-02-11 22:03:01

+0

然後使用不同的注射策略。你可以簡單地使用*工廠方法*,即使用參數'protected'構造函數,然後使用'public MyPologonRenderer():this(new RealDrawStuff()){}'和'public static MyPolygonRenderer ForTesting(IDrawStuff testDrawStuff){return new MyPolygonRenderer(testDrawStuff)}或*屬性注入*例如你公開一個內部屬性,可以讓你設置'IDrawStuff',然後用'[InternalsVisibleTo(「MyPolygonRendererTestsAssembly」)''標記你的代碼。 – AlSki 2015-02-12 09:53:08