2015-02-11 174 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