Unit testing 编码前测试

Unit testing 编码前测试,unit-testing,testing,Unit Testing,Testing,当谈到TDD时,我经常听到这样的说法,“您应该在编写代码之前始终进行测试”。实际上,我从来没有做过完整的TDD,或者可能没有利用它,但是如何测试您甚至没有做过的事情呢 你能给我一个明确的例子说明如何做到这一点吗?我认为这意味着你应该在编写实际代码之前编写测试。这样做会给您带来一些好处:您对要测试的代码外观没有先入为主的想法。你将彻底了解你要解决的问题 def test_add_numbers assert_equal( 42, add(40,2) ) assert_equal( 42,

当谈到TDD时,我经常听到这样的说法,“您应该在编写代码之前始终进行测试”。实际上,我从来没有做过完整的TDD,或者可能没有利用它,但是如何测试您甚至没有做过的事情呢


你能给我一个明确的例子说明如何做到这一点吗?

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

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
函数,但现在您知道它至少需要一个参数,可以接受两个以上的参数,并且需要确保考虑负值和零值


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

在编写代码之前,您需要为它定义API。然后您为这些API编写单元测试,所有情况都将失败(因为您没有实现它们)。在大多数案例准备就绪后,您会遇到一堆失败案例。之后,您可以开始实现这些API。随着您取得一些进展,失败案例应该会越来越少。一旦所有案例通过,您的设计就完成了。这就是TDD。

首先编写测试将:

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

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

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方法行为模板。现在您只需编写最少的代码即可通过测试……

最近我一直认为TDD的这一方面与我在中读到的“一厢情愿”编程的概念是一样的。例如,在作者教授的麻省理工学院课程中,给出了计算平方根作为不动点函数的以下示例:

(define (sqrt x)
  (fixed-point
      (lambda (y) (average (/ x y) y))
      1))
这在定义
定点过程之前显示。您并不需要定义来理解您可以使用
定点
过程来计算平方根。一旦你看到你将如何使用它,你就可以开始定义它了

(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)))
(定义(定点f启动)
(定义公差0.00001)
(定义(闭合enuf?u v)
(<(abs(-u v))公差)
(定义(iter旧-新)
(如果(关闭enuf?旧-新)
新的
(国际热核实验堆新(f新)))
(iter启动(f启动)))

这与TDD中使用的简单思想相同。在编写方法之前先为方法编写测试,这是在向自己展示如何使用这些方法。

我也对将测试驱动开发保持在高位的大量互联网络的方式表示怀疑。虽然这通常是一个好主意,但我有时认为这会妨碍我的工作流程,因为从套接字模拟流式数据有时比相信我的串行端口实际工作的效果要差。这就是说,当您想要测试驱动而不是“测试确保”(在之后编写单元测试以防止工作代码上的回归)时,下面是它的样子

基本上,这是一个想法: 书写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;
}


他总是可以确信3就是3,而且他没有吃过棕色的酸。我希望这是对所涉及的思维过程的一个很好的总结。

这里有一个很好的例子-

这个想法类似于“一厢情愿”或“基于场景的设计”——你想象你想要的组件已经存在,想象你想要的最简单的API。这将导致接口发现。您的测试指定了需求/行为,而不是实现。一旦您完成了测试,您就可以通过选择最简单的实现来继续使测试通过。这是一种简单而有效的方法…

请阅读以获得详细的解释

总结是:在编写代码之前,您没有编写所有的测试;您编写一个测试,运行它以确保它失败(如果在编写代码之前它通过了,那么您有一个糟糕的测试),然后编写足够的代码使其运行。现在您知道您已经编写并测试了该功能。此时进行重构(如果需要进行重构),然后继续编写下一个测试


其优点是,通过在测试中添加一些小的功能,您最终可以得到一整套测试和一个精简的、组织良好的设计。这种说法是,如果您没有使用“测试优先”设计,这种设计可能会比您编写的更好(并且有一些证据支持这种设计)。

一位同事最近说,他在任何测试集中编写的第一个测试只会:

Assert.Fail("Bootstrapping the test"); Assert.Fail(“引导测试”);
只需进入其中。

观看Roy Osherove的这段视频,改变你的生活:.+1用于回答1986年CLISP提出的单元测试问题。我投+1票只是因为你让我学会了很棒的sqrt算法。对于那些想了解它的人,这里有一个链接: Assert.Fail("Bootstrapping the test");