在编码之前进行测试

时间:2010-12-04 15:23:17

标签: unit-testing testing

在谈到TDD时,我听到了这么多,“你应该在编码之前总是进行测试”实际上我从来没有做过完整的TDD或者可能没有利用它,但是怎么可能测试你甚至没做过的事情???

你能给我一个明确的例子吗?

9 个答案:

答案 0 :(得分:4)

最近我一直在想TDD的这个方面与我在Structure and Interpretation of Computer Programs中读过的“一厢情愿”的编程概念是一样的。例如,在作者所教授的麻省理工学院课程的lecture 2A中,以下示例用于计算平方根作为固定点的函数:

(define (sqrt x)
  (fixed-point
      (lambda (y) (average (/ x y) y))
      1))

在定义fixed-point过程之前显示。您实际上并不需要定义来理解您可以使用 fixed-point过程来计算平方根。一旦你看到你将如何使用它,你就可以开始定义它。

(define (fixed-point f start)
  (define tolerance 0.00001)
  (define (close-enuf? u v)
     (< (abs (- u v)) tolerance))
  (define (iter old new)
     (if (close-enuf? old new)
         new
         (iter new (f new))))
  (iter start (f start)))

这与TDD中使用的简单思路相同。通过在编写方法之前为您的方法编写测试,您将自己展示如何使用这些方法。

答案 1 :(得分:4)

我也怀疑那些以测试为导向的高速发展的互联网群众的方式。虽然这通常是一个好主意,但我有时认为这对我的工作流程是一种阻碍,因为从套接字模拟流数据有时可能不如信任我的串口实际工作有效。也就是说,当你想要试驾而不是“测试 - 确保”(之后编写单元测试以防止工作代码回归)时,这就是看起来的样子。

基本上,这是一个想法:

编写sqrt

首先,当您意识到需要软件的新功能或功能时,您会想出一个大纲。

我的,我相信我需要一个带数字的函数,找到平方根,然后返回

现在在TDD中,而不只是编写代码并检查数字是否正确...... TDD'er说:

我会编写一个调用我不存在的函数的函数,并将结果与​​我脑中的结果进行比较

所以他写道:

void testSqrt () {
    if (sqrt(9.0) == 3.0) {
        printf("ALL IS FINE AND DANDY HERE");
    }
    else {
        printf("THE SYSTEM IS DOWN! THE SYSTEM IS DOWN!"); /* untz untz untz utnz */
    }
}

现在,他不存在的sqrt在生活中有一个目的......确保在喂9时总是返回3!他的想法是他是否写作

float sqrt(float n) {
    long i; float x, y;
    const float f = 1.5;
    x = n/2.0;
    y = n;
    i = *(long *)&y;
    i = 0x5f3759df-(i >> 1);
    y = *(float *) &i;
    y = y*(f-(x*y*y));
    y = y*(f-(x*y*y));
    return n * y;
}

float sqrt(float n) {
    float n_t = n/2.0;
    int i = *(int*)&n;
    i = 0x5f375a86 - (i>>1);
    n = *(float*)&i;
    n = n*(1.5f-n_t*n*n);
    return n;
}

他总能确信3是3,他没有吃过棕色酸。我希望这是对所涉及的思想过程的一个很好的总结。

答案 2 :(得分:2)

在编写某些内容之前,请为其定义API。然后,您为这些API编写单元测试,并且所有情况都将失败(因为您没有实现它们)。在准备好大多数案例之后,你会遇到一堆失败案例。之后,您可以开始实现这些API。随着您取得一些进展,失败案例应该减少。一旦所有案件都通过,您的设计就完成了。那是TDD。

答案 3 :(得分:1)

阅读Test Driven Development: By Example以获得详尽的解释。

摘要是:在编写代码之前,不要编写所有测试;你编写一个测试,运行它以确保它失败(如果它在你编写代码之前通过,你有一个糟糕的测试),然后代码足以让它运行。现在您知道您已编写并测试了该功能。此时你重构(如果有任何重构要做),那么继续写下一个测试。

优势在于,通过在测试中添加一小部分功能,您最终会得到一整套测试和一个纤薄,组织良好的设计。声称这个设计会比你没有使用过测试优先设计时更好的设计(并且有一些证据支持这一点)。

答案 4 :(得分:0)

我认为你应该在编写实际代码之前编写测试。这样做会带来一些好处:您对要测试的代码外观没有先入为主的想法。你完全熟悉了即将解决的问题。

答案 5 :(得分:0)

def test_add_numbers
  assert_equal( 42, add(40,2) )
  assert_equal( 42, add(42,0) )
  assert_equal( 42, add(44,-2) )
  assert_equal( 42, add(40,1,1) )
  assert_equal( 42, add(42) )
  assert_raises(ArgumentError,"Add requires one argument"){
    add()
  }
end

您尚未编写add函数,但现在您知道它至少需要一个参数,可以接受两个以上的函数,并且您需要确保考虑负值和零值。< / p>

您测试您计划支持的接口,然后迭代编写代码并进行测试,直到所有测试都通过。

答案 6 :(得分:0)

这个想法是首先编写测试:

1)帮助您理清API 2)帮助您编写最少量的代码。

假设你需要一个方法来获取一个列表和一个String并返回一个数字。从测试开始,帮助您定义函数的行为方式:

public void testMyFunc() {
    List arg1 = new List()...
    String arg2 = 'test';
    int returned;
    int expected = 3;

     // ok what do i need to write
     returned = myFunc(arg1, arg2);

     assertEquals(expected, returned);
}

此时代码甚至不会编译 - 但是你有一个关于myFunc方法应该如何表现的完整模板。现在你编写了最少量的代码来使测试通过....

答案 7 :(得分:0)

这是一个应该做得很好的例子 - http://www.theserverside.net/tt/articles/content/TDD_Chapter/ch2.pdf

这个想法类似于“一厢情愿”或“基于场景的设计” - 您可以想象您想要的组件已经存在并想象您想拥有的最简单的API。这导致界面发现。您的测试指定了需求/行为,而不是实现。完成测试后,您可以选择最简单的实现来继续测试。这是一种简单而有效的方法......

答案 8 :(得分:0)

一位同事最近说他在任何测试集中写的第一个测试都是这样做的:

Assert.Fail("Bootstrapping the test");

只是为了摆脱它。