单元测试一个大方法

时间:2009-10-25 13:01:21

标签: unit-testing tdd automated-tests

遵循测试驱动开发。

我最近实现了一个需要干净界面的算法(A *)。通过干净我想要的是一些属性和一个搜索方法。

我发现很难找到的是测试搜索方法。它包含大约五个步骤但我基本上被迫在一个大的进程中编写这个方法,这使得事情变得困难。

对此有什么建议吗?

修改

我正在使用C#。不,我目前没有手头的代码。我的问题依赖于一个测试只在实现整个搜索方法后才通过 - 而不是算法中的一个步骤。我自然重构了代码,但它实现了我发现很难。

5 个答案:

答案 0 :(得分:11)

如果您的步骤足够大(或者它们本身就有意义),您应该考虑将它们委托给其他较小的类并测试您的类与它们之间的交互。例如,如果你有一个解析步骤,然后是一个排序步骤,然后是一个搜索步骤,那么拥有一个解析器类,一个分类器类等是有意义的。然后你可以在每个上使用TDD。

不知道你正在使用哪种语言,但如果你在.net世界中,你可以将这些类放在内部,然后将它们暴露给你的测试类,其中“内部可见”,这将使它们隐藏。

如果步骤很小而且没有任何意义,那么tvanfosson的建议是可行的。

答案 1 :(得分:4)

将您的大方法重构为较小的私有方法。使用您的语言中提供的反射或其他机制来独立测试较小的方法。最糟糕的情况 - 如果您无法访问反射或朋友,请使方法受到保护,并使您的测试类继承主类,以便它可以访问它们。

更新:我还应该澄清,简单地重构私有方法并不一定意味着您需要创建特定于这些方法的测试。如果要彻底测试依赖私有方法的公共方法,则可能不需要直接测试私有方法。这可能是一般情况。有时候直接测试私有方法是有意义的(比如说它简化或减少了公共方法所需的测试用例的数量),但我不认为这只是因为你重构私有而创建测试的要求方法实施。

答案 2 :(得分:1)

我假设A *表示搜索算法(例如http://en.wikipedia.org/wiki/A*_search_algorithm)。如果是这样,我理解你的问题,因为我们有类似的要求。这是WP算法,我将在下面发表评论:

<小时/> 伪代码

 function A*(start,goal)
     closedset := the empty set                 % The set of nodes already evaluated.     
     openset := set containing the initial node % The set of tentative nodes to be evaluated.
     g_score[start] := 0                        % Distance from start along optimal path.
     h_score[start] := heuristic_estimate_of_distance(start, goal)
     f_score[start] := h_score[start]           % Estimated total distance from start to goal through y.
     while openset is not empty
         x := the node in openset having the lowest f_score[] value
         if x = goal
             return reconstruct_path(came_from,goal)
         remove x from openset
         add x to closedset
         foreach y in neighbor_nodes(x)
             if y in closedset
                 continue
             tentative_g_score := g_score[x] + dist_between(x,y)

             if y not in openset
                 add y to openset

                 tentative_is_better := true
             elseif tentative_g_score < g_score[y]
                 tentative_is_better := true
             else
                 tentative_is_better := false
             if tentative_is_better = true
                 came_from[y] := x
                 g_score[y] := tentative_g_score
                 h_score[y] := heuristic_estimate_of_distance(y, goal)
                 f_score[y] := g_score[y] + h_score[y]
     return failure

 function reconstruct_path(came_from,current_node)
     if came_from[current_node] is set
         p = reconstruct_path(came_from,came_from[current_node])
         return (p + current_node)
     else
         return the empty path

如果确保存在解决方案,或者如果调整算法以便仅在新的节点具有比f值低的f值时将新节点添加到开放集中,则可以省略闭合集(产生树搜索算法)。任何先前的迭代。


首先,我不是轻浮,这取决于你是否理解算法 - 听起来好像你这样做。也可以转录上面的算法 - 希望它有效)并给它一些测试。这就是我要做的事情,因为我怀疑WP的作者比我好!大规模的测试会运行边缘情况,例如没有节点,一个节点,两个节点+没有边缘等等......如果它们都通过了我会很开心。但如果他们失败了,别无选择,只能理解算法。

如果是这样,我认为你必须为数据结构构建测试。这些是(至少)设置,距离,分数等。您必须创建这些对象并测试它们。案例1,2,3 ...写测试的预期距离是多少。添加A来设置Z有什么影响?需要测试。对于此算法,您需要测试heuristic_estimate_of_distance,依此类推。这是很多工作。

一种方法可能是找到另一种语言的实现并询问它以找到数据结构中的值。当然,如果您正在修改算法,那么您就是自己的!

有一件事比这更糟 - 数值算法。对角化矩阵 - 我们实际上得到了正确的答案。我和一位科学家合作编写了第三衍生矩阵 - 这会吓到我......

答案 3 :(得分:1)

使用TDD时要记住的一件重要事情是测试不需要永远存在。

在您的示例中,您知道您将提供一个干净的界面,并且大部分操作将委派给私有方法。我认为这是假设这些方法必须创建为私有,并保持这种方式,这会导致最麻烦。还假设测试必须在开发后保持不变。

我对这种情况的建议是:

  • 根据新方法绘制大方法的骨架,因此您需要执行一系列有意义的步骤。
  • 默认情况下将这些新方法设为公开,并开始使用TDD开发它们。
  • 然后,一旦您测试了所有部件,请编写另一个测试整个方法的部件。确保如果任何较小的委托部件被破坏,这个新测试将失败。
  • 此时大方法有效,并且被测试覆盖,您现在可以开始重构。我建议,正如FinnNk所做的那样,尝试将较小的方法提取到自己的类中。如果这没有意义,或者它需要花费太多时间,我会推荐其他人可能不同意的东西:将小方法更改为私有方法并删除测试。< / LI>

结果是您已使用TDD创建整个方法,测试仍然涵盖了该方法,并且API正是您要呈现的那个。

很多混淆来自这样的想法:一旦你编写了一个测试,你就必须始终保持,而事实并非如此。但是,如果你是多愁善感的,那么私有方法can be achieved in Java就有单元测试的方法,也许在C#中也有类似的结构。

答案 4 :(得分:-1)

您可以将Texttest(http://texttest.carmen.se/)视为“在界面下”进行测试的方式。

它允许您通过检查记录的数据来检查行为以验证行为,而不是对参数和方法结果进行纯粹的黑盒式测试。

免责声明:我听过关于Texttest的演示文稿,并查看了文档,但还没有时间在严肃的应用程序中进行尝试。