2010-04-12 92 views
3

我参加了一个TDD编码Dojo,我们尝试在简单问题上练习纯TDD。然而,我发现单元测试中出现的代码并不是最有效的。现在大多数情况下都没问题,但是如果代码使用量增加,那么效率就成了问题。如何通过单元测试产生高效的代码?

我喜欢代码从单元测试中出现的方式,但是有可能通过进一步的测试使效率属性出现吗?

这里是一个红宝石的例子:素因子分解。我遵循纯粹的TDD方法,使得测试一个接一个地验证我的原始验收测试(在底部注释)。 如果我想让generic prime factorization algorithms之一出现,我还能采取哪些步骤?为了减少问题领域,我们假设我想要实现一个quadratic sieve ...现在在这种精确的情况下,我知道“最优算法,但在大多数情况下,客户端只会添加一个要求, x”的时间,特定的环境。

require 'shoulda' 
require 'lib/prime' 

class MathTest < Test::Unit::TestCase 
    context "The math module" do 
    should "have a method to get primes" do 
     assert Math.respond_to? 'primes' 
    end 
    end 
    context "The primes method of Math" do 
    should "return [] for 0" do 
     assert_equal [], Math.primes(0) 
    end 
    should "return [1] for 1 " do 
     assert_equal [1], Math.primes(1) 
    end 
    should "return [1,2] for 2" do 
     assert_equal [1,2], Math.primes(2) 
    end 
    should "return [1,3] for 3" do 
     assert_equal [1,3], Math.primes(3) 
    end 
    should "return [1,2] for 4" do 
     assert_equal [1,2,2], Math.primes(4) 
    end 
    should "return [1,5] for 5" do 
     assert_equal [1,5], Math.primes(5) 
    end 
    should "return [1,2,3] for 6" do 
     assert_equal [1,2,3], Math.primes(6) 
    end  
    should "return [1,3] for 9" do 
     assert_equal [1,3,3], Math.primes(9) 
    end   
    should "return [1,2,5] for 10" do 
     assert_equal [1,2,5], Math.primes(10) 
    end     
    end 
# context "Functionnal Acceptance test 1" do 
# context "the prime factors of 14101980 are 1,2,2,3,5,61,3853"do  
#  should "return [1,2,3,5,61,3853] for ${14101980*14101980}" do 
#  assert_equal [1,2,2,3,5,61,3853], Math.primes(14101980*14101980) 
#  end 
# end 
# end 
end 

和天真的算法,我创造了这个方法

module Math 
    def self.primes(n) 
    if n==0 
     return [] 
    else 
     primes=[1] 
     for i in 2..n do 
     if n%i==0   
      while(n%i==0) 
      primes<<i 
      n=n/i 
      end 
     end 
     end  
     primes 
    end 
    end 
end 

编辑1从第一个答案来看,我想我并不清楚我的初始描述:性能测试是不是是我单元测试的一个标准部分,它是一个新验收测试编写回答特定要求从客户端。

编辑2我知道如何测试执行时间,但它似乎从简单的算法转移到优化的算法是一个巨大的步骤。我的问题是如何使最优代码emerge,换句话说:你如何分解从平凡代码到最优代码的迁移? 一些人提到这是一个问题的具体方法:我提供了一个示例问题,我不知道如何继续。

+2

会很好,但无论设计正确的代码还是有效的代码,单元测试都不会显示出现的属性。一个给定的单元测试可以通过许多非常**不同的实现来满足。 – Ray 2010-04-14 01:18:24

+0

如果你对Ruby感到满意,那么你根本不在意perf :-)如果你真的关心perf,那么你一定知道它必须通过为perf设计来实现,这意味着思考需要很长的时间并且很努力。这使得TDD成为了性能的致命的敌人 - 因为TDD旨在专注于“今天为我做了些什么”并缩短了交付时间。 – ZXX 2010-08-08 15:14:03

+0

是不是“为perf设计”最终形式过早优化? :) 此外,我在例子中使用红宝石,因为它很容易阅读。这只是为了例子,而不是我的问题的对象。 – Jean 2010-08-08 17:57:10

回答

4
  • TDD正确性非回归并专注于单元测试
  • 仿形性能,这是一个功能测试问题。

我也用来参加每周的TDD编码Dojo,我们尝试了一些实验,看看它是否可以用于算法目的(找到更好的算法,找到没有明显的算法)或者内置的性能限制。

当道场使用TDD,我们尽量遵守规则之下

  • 写打破现有的代码最简单的测试(或支付的啤酒,如果它不破码)
  • 编写最简单代码,使测试添加新的测试
之前加入测试
  • 也重构测试之前通过
  • 重构代码(使用代码味道)

    根据这些规则,我们比第一眼就可以看到更多的实验空间。我们可以调整最简单的定义并添加代码异常来考虑效率(基本上来说:如果我们想到几种实现某些东西的简单方法,它们更喜欢效率最高的方法,并且如果我们知道一些更高效但仍然简单的方法 - 算法比我们的代码中使用的是一种气味)。

    总的来说,结果是TDD本身不适合预测整体代码的性能,并且从开始就实现高效的代码,即使使用TDD和重构,我们成功实现了对代码的更好洞察,并且可以增强它以获得更好的可读性避免了一些明显的性能瓶颈。试图在测试级别的代码中插入性能约束通常是灾难性的(我们得到的代码和测试太复杂,经常代码破坏或者更改太复杂)。

    其中一个原因是TDD我们通常使用非常小的测试集(最简单的测试失败)。另一方面,真正的数据集会出现更多的性能问题,并且与上述规则相吻合。性能测试,即使在正式的单元测试中,也是更相似的功能测试。通常的优化策略包括添加缓存,或者考虑到实际数据分布的某些属性,或者当某些小的优点特性对性能有很大的负面影响时,关联用户故事中的更改。所有这些都不能真正在TDD中内置,但更有可能在分析代码时发现。

    我相信表演目标基本上是一个功能测试的问题。

  • +0

    非常感谢您的贡献,这绝对是我正在寻找的答案对于。 – Jean 2010-04-18 20:15:01

    +0

    我决定选择你的答案作为我的问题的最佳答案。不过,我建议阅读这篇文章的读者也准备好Gishu的答案,因为它增加了这个答案,请参阅 – Jean 2010-04-18 20:36:05

    3

    不,你不应该尝试。单元测试测试的正确性,而不是效率 - 迫使他们测试效率是一种过早优化的一种形式。

    +0

    注意我提到**进一步**测试。 你如何定义正确性?我认为它是:“通过客户的要求”(验收测试)。 如果客户明确要求功能运行得更快(这是我最近在工作中遇到的情况)会怎么样? 我有关于如何编写一个检查代码在时间范围内运行的测试的想法,即使是自动化测试也是如此。 但是我将编写的代码使测试通过不会出现,我将不得不预期某种形式的代码实际上比另一种更快...... – Jean 2010-04-12 21:37:29

    1

    在您的单元测试代码中,您可以添加测量目标代码已用时间的代码。伪代码将如下所示:

    start_time = date_time_now(); 
    Math.primes(1000); 
    stop_time = date_time_now(); 
    assert stop_time-start_time < target_execution_time; 
    

    某些单位文本框架可能已经有可用的时间,您可以参考。这使得额外的样板代码无需测量时间。

    此外,elapsed_time只是要使用的“效率”指标的一个示例。其他要测试的指标包括cpu_time,吞吐量,传输的输入/输出字节等。

    +0

    虽然我可以测量执行时间,但这不会使高效的代码出现。它会创建一个失败的测试,但并不表明“快速”代码应该是什么样子。再次,你将如何使二次筛的代码出现?它不能简单地来自时间测量。 – Jean 2010-04-13 05:48:56

    0

    进行效率测试。您给出了这样的要求:“对于给定的环境,该功能的运行时间少于”x“时间。” 编写一个测试执行时间的测试。如果你通过了测试,那么如果失败了,就不需要进一步的代码增强,通过分析和微观选择或算法增强来加快速度。

    我不得不同意BlueRaja性能测试不应该是单元测试的标准部分,尽管如果重视性能,它可以帮助保持它在桌面上。

    2

    单元测试通常检查零散的功能正确性。

    你当然可以添加时序约束到一个单元测试,但你可能很难有时间与技术无关的方式表达出来(例如,O(N LN N)。

    而且,仅仅因为你可以写一个坚持要在一定时间内交付结果的单元测试并不意味着该单元的编码器必然能够找到一个达到这种效果的算法(当然,他可能无法想到如何正确实现功能,

    如果你这样做,我会建议隔离功能测试和性能测试 然后至少你可以告诉你代码在你deci之前是否工作表现确实很糟糕。

    +0

    查看包含的示例,我有功能测试正常。但是“验收测试”在我的笔记本电脑上运行了超过半个小时,使用100%的CPU。作为客户端,我希望该功能的运行速度至少提高10%。 我的问题是,当面对效率要求时,使高效算法出现。你如何将效率要求分解为单个步骤? 也许这是不可能的,我当然不知道如何去做,但我不知道这一切是什么,所以我问:) – Jean 2010-04-12 21:47:25

    +0

    你有两个问题:1)你可以单元测试期望的性能表现(答案基本上是肯定的)2)你能设计一个单元来通过你有代码的测试吗(答案是,取决于问题和你的技能水平,而与单元测试无关)。实现某种效果(功能或性能)的一个标准技巧是将其分解成更小的可能步骤,每个步骤都实现了所需效果的一部分(“分而治之”)。在完成基本建议之后,您需要具体问题的方法来实现它。 – 2010-04-13 02:33:03

    +0

    其实我只有一个问题,它指的是使用单元测试时出现的代码质量。代码结构应该显示为应用一系列测试的结果。从平凡的算法迁移到二次筛选我提到并不是微不足道的任务,它看起来像一个巨大的步骤,但我看不到中间步骤是什么。我非常确定分而治之对此无济于事。这就是为什么我给出了一个具体问题:从一个微不足道的素数分解器迁移到一个非平凡的分解器。 – Jean 2010-04-13 05:51:57

    0

    我起初虽然这不起作用,你需要一个比TDD更大的飞跃,而且我会说至少你的测试会在你重写你的代码时帮助你。

    但是,您应该尝试让我们知道。虽然我还没有做到,但我认为你的下一个测试应该是的一次性能测试。这显然是这个时候的要求,所以为什么不以同样的方式继续下去。

    我认为你可以编写一个能够在任何平台上可靠运行的基础。你需要一些基础设施,以帮助你,但它可以看起来像:

    TEST: should be faster than O(n^2) 
    setup: baseline_time_for_10 = time_of(f(10)) 
    100: assert time_of(f(100)) < baseline_time_for_10^2  
    etc. 
    

    我一直想做到这一点,但还没有一个项目有合适的机会。让我们知道怎么回事。

    +0

    就是这样的事情:我试过但我找不到“下一步”。我认为gishu和kriss几乎是钉了它(现在我在选择其中一个答案,因为他们都以有趣的方式提供... ...)之间撕裂...) – Jean 2010-04-18 20:31:02

    3

    TDD不能帮你推导算法 - 如果那是你的问题。这是其中一个不适合TDD优势的利基领域(利基:与成千上万框架/库中的企业软件相比)。

    没有阻止你继续使用TDD - 你可能会写一个性能测试,但是你的目标规格是什么?如果有可能减半你的规格呢?这些问题不能在不分析代码的情况下以正确的方式进行回答。去测试驱动不会回答这些问题;最多它会给你一个安全网,以检测你是否破坏了现有的代码。

    例如你可以用TDD来实现排序,但是你找到一个新算法或者到达一个像quicksort这样的现有高效算法的机会是黯淡的。除非你知道算法设计的原则,并有意识地通过它的方式。

    更新:支持证据。 http://www.markhneedham.com/blog/2009/12/10/tdd-big-leaps-and-small-steps/ 还有一些 - 但他们就像讨论reddit。自以为是的不受支持的免费为所有人。不发布这些。

    +0

    感谢您的回答。像往常一样,我的主要问题是沟通问题,毕竟我认识到存在一个问题,就是无法在上面写出正确的文字。你显然在那里帮了忙。 – Jean 2010-04-18 20:29:06