如何测试大型复杂代码?

时间:2015-08-21 21:56:39

标签: java unit-testing mocking

我的遗留代码只包含8700行,每种方法的平均长度约为200-300行。我想知道如何能够覆盖大部分代码甚至获得成功结果的输出。

例如:

    public class Aviation implements FlightStruct, FlightSystem extends Vehicles {
    ...var decoration...
    ...override method definitions...

    public controls aviation(Flight request, Flight destination, Flight target) {

    FlightResponse flightResponse = new FlightResponse();

    flightResponse.speed = target.speed;
    angularVel.calculate(greatCircle.flightResponse.speed);
    _readVelData();
    if (vel.circular.velocity <= 10000) {
    for(countryName : country) {
    calculateArrivalTime();
    _readFuel();
    if(fuel.limit < pwi.percent*100) {
           createShortPathLandingPoint(Path newDestination, Register checkPoint);
       }
    }
}
    private eqiptment company(String name, Vendor vendor) {
    ....calculate equiptment stuff that calls bunch of private methods
    }

 ...alot more method here
    }
}

我想知道我应该如何对这些代码进行单元测试? 我试图破坏数据,但内部方法太复杂,无法通过。通常一种方法调用多层子方法。 例如.... 燃料类调用_readfuel,在_readfuel中它调用destinationCheckPoint,flightWayPoint,flightSpeedCalc ......然后在每个子方法中调用它自己的子方法。 (例如flightWaypoint调用calculateWayPoint,angVelocity,speedLimit,....) 总的来说,我不认为这是可行的,因为查找数据很困难。 然后我的第二个选择是使用Mockito来模拟方法/类。这听起来更合理,但我再次遇到有多个私有子方法......并且mockito无法模拟私有方法。然后我尝试使用PowerMockito来模拟它为几千行工作的私有方法,并且有类似的东西:

while (!report.successful && !telCList.isEmpty()) {
...
report.wayPoint = update.assignWayPoint(wayPointList); //null point exception
...

其中更新为null并且声明了上面的几百行

 UpdateGateWay update = setDestinationWayPoint(distance, speed, altitude);

setDestinationWayPoint是一个私有方法,我使用powerMockito来模拟我已经返回值作为存根的一部分的行为。但是当它在下面被调用时它变为空。

此时我几乎放弃了。我想知道是否有更合理的方法来进行这种类型的测试......

2 个答案:

答案 0 :(得分:2)

为您不了解的遗留代码库编写有价值的 de novo 单元测试是一项艰巨的任务。特别是,如果代码是一团糟;例如&#34;每种方法平均长约200-300行&#34;

问题是如何弄清楚方法&gt;&gt;应该&lt;&lt;以足够的准确度来编写单元测试。不幸的是,这通常需要更深入的洞察,而不仅仅是了解代码如何编写的行为。

这不是一项不可能完成的任务,但它可能需要做很多工作......取决于代码的内在复杂性,原始设计的质量,要求,问题域等等。

一种可能的替代方法是将代码库视为黑盒子。不是编写单元测试,而是根据一些典型的&#34;编写系统测试。场景;例如如果从数据库状态X开始并提供输入I,则期望输出O和新数据库状态X&#39;。每次需要对旧代码库进行更改时,请添加新的系统测试。

  

我想知道如何能够覆盖大部分代码,甚至获得成功结果的输出。

我认为你在这里有一些误解。

  • 进行单元测试时,所涵盖的代码百分比不如人们期望的那么重要。目标应该是覆盖可能存在错误的代码。

  • &#34;成功的结果&#34; 是什么?个人方法电话?这假设您知道应该的结果。这可能很困难,特别是如果该方法具有您不了解或不了解的副作用。

最后一点是,某些代码本身很难编写单元测试。通常,是应该重写的代码。一个8,700行的代码库(实际上)并不那么大,并且可能是重写的候选者。 (您可以将现有代码视为&#34;工作原型&#34; ......在缺乏一系列功能要求的情况下。)

答案 1 :(得分:1)

这是一个艰难的。这个答案几乎肯定会过度简化您试图涵盖的问题的性质,但希望它能指出您正确的方向。

一般来说,如果我的任务是通过测试覆盖这些代码,我首先要尝试追踪一些彻底描述软件预期行为的软件需求。然后,我将编写一些高级集成样式测试,以涵盖预期的行为以及所有记录的边缘案例。我在此测试中使用的唯一存根将是对外部系统的调用。在这个阶段,我真的不会考虑单元测试术语。

这里的诀窍是考虑你试图用谨慎的功能组来测试的软件。一旦您确定了这些组,那么为每个组编写测试就成了确定功能边界的问题,然后通过集成测试来运行这些功能。基本上,在这个阶段,您应该关注的是系统(或函数,如果该级别的粒度是有意义的)的输入和输出,而不是太关注具体的实现细节。一个例子可能是:&#34;当我调用这个函数时,我希望在数据库中有一个具有这些确切属性的新记录。&#34;

在复杂的系统中,您肯定会期望在集成测试中看到一些重叠,但在此阶段,这没关系。您所要做的就是确保在将这些长函数重构为较小的单元时,系统的功能不会以非预期的方式(或中断)发生变化。换句话说,您希望确保在开始重构这些部分之后系统的行为与您开始之前完全相同。

一旦您的集成测试到位,希望您已经足够了解系统(或系统的一部分)的行为,以便您可以在您提到的大型复杂功能中识别出谨慎的行为单位在你的问题。这也将使您有机会提取可能存在的任何重复。集成测试越彻底地覆盖行为和边缘情况,您对重构的信心就越大。

一旦你进入这个阶段并开始识别各个功能单元,然后,开始考虑单元测试是有意义的。这也是模拟和存根对测试内部API更有意义的阶段(即,除了系统间API边界之外还模拟域间API边界)。

再次 - 这是一个巨大的过度简化的主题,我确信整本书都已经写好了,但我希望这可以帮助你在高层次上获得一些方向性的洞察力。