代码之家  ›  专栏  ›  技术社区  ›  Jean

如何通过单元测试产生高效的代码?

  •  3
  • Jean  · 技术社区  · 16 年前

    我参加了一个TDD编码Dojo,在那里我们尝试在简单的问题上练习纯TDD。然而,我突然想到,从单元测试中产生的代码并不是最有效的。现在这在大多数情况下都是好的,但是如果代码使用量增加了,那么效率就成了问题。

    下面是ruby中的一个小例子:素因子分解。我遵循纯TDD方法,使测试一个接一个地通过,从而验证了我最初的验收测试(在底部评论)。 generic prime factorization algorithms 出现?为了减少问题域,假设我想得到一个 quadratic sieve

    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 浮现 换句话说:如何将迁移从琐碎的代码分解为最优的代码? 有人提到这是一种特定于问题的方法:我提供了一个示例问题,我不知道如何继续。

    7 回复  |  直到 14 年前
        1
  •  4
  •   kriss    16 年前
    • TDD公司 是为 正确性 和 非回归 专注于 单元测试
    • 演出 这是一个 问题。

    在Dojo中使用TDD时,我们尝试遵循以下规则

    • 编写使测试通过的最简单代码
    • 在添加新测试之前还要重构测试

    根据这些规则,我们有更多的空间去试验,而不是一见钟情。我们可以调整simplest的定义,并添加代码气味来考虑效率(基本上:如果我们想到几种简单的方法来实现一些更喜欢最有效的算法,如果我们知道一些比我们代码中使用的算法更有效但仍然很简单的算法,那就是气味)。

    概括地说,结果是TDD本身并不适合于预测总体代码性能和从一开始就实现高效的代码,即使使用TDD和重构,我们成功地实现了对代码的更好的理解,并可以增强代码的可读性 和 避免一些明显的性能瓶颈。在那个测试级别尝试在代码中插入性能约束通常是灾难性的(我们得到的代码和测试太复杂了,经常是坏代码或者太复杂了以至于无法更改)。

    一个原因是TDD我们通常使用非常小的测试集(最简单的失败测试)。另一方面,实际数据集会出现更多的性能问题,并且与上述规则的匹配性非常差。性能测试,即使形式上仍然是单元测试,也更像是功能测试。常见的优化策略包括添加缓存,或者考虑真实数据分布的某些属性,或者在一些小的优势特性对性能有很大负面影响时协商用户情景的变化。所有这些都不能真正内置在TDD中,但更可能是在分析代码时发现的。

    我相信 目标基本上是 功能测试 问题。

        2
  •  3
  •   BlueRaja - Danny Pflughoeft    16 年前

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

        3
  •  3
  •   Gishu    16 年前

    -如果这是你的问题。这是一个没有TDD优势的利基领域(利基:与大量生产企业软件、调用无数框架/库相比)。

    e、 你可以用TDD方法来实现排序,但是你找到一个新的算法或者找到一个像快速排序这样的有效算法的机会是渺茫的。除非你知道算法设计的原则,并有意识地朝着这个方向努力。

    更新:支持性证据。 http://www.markhneedham.com/blog/2009/12/10/tdd-big-leaps-and-small-steps/ 还有一些——不过,它们就像是关于reddit的讨论。固执己见的,不受任何人支持的。不发布这些。

        4
  •  2
  •   Ira Baxter    16 年前

    您当然可以向单元测试添加时间约束,但是您可能很难用独立于技术的方式(例如,O(N ln N))表达它们。

    如果您这样做,我建议您隔离功能测试和性能测试。

        5
  •  1
  •   JasDev    16 年前

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

    一些单元文本框架可能已经有了您可以参考的运行时间。这使得测量时间的额外样板代码变得不必要。

    而且,运行时间只是“效率”指标的一个例子。要测试的其他指标包括cpu时间、吞吐量、传输的输入/输出字节等

        6
  •  0
  •   stonemetal    16 年前

    进行效率测试。您给出了这样一个要求:“对于给定的环境,该功能在不到“x”的时间内运行。”

    我同意BlueRaja的观点,性能测试不应该是单元测试的标准部分,不过,如果对性能有很大的重视,它可以帮助您将它放在桌面上。

        7
  •  0
  •   ndp    16 年前

    一开始我认为这是行不通的,你需要一个比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.
    

    我一直想这样做,但在一个项目上没有合适的机会。告诉我们进展如何。

    推荐文章