【问题标题】:How do I testing large complex code?如何测试大型复杂代码?
【发布时间】:2015-08-22 07:19:03
【问题描述】:

我有一个遗留代码,其中仅包含 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
...

更新为空并且声明了它上面几百行的地方

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

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

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

【问题讨论】:

    标签: java unit-testing mocking


    【解决方案1】:

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

    问题是如何弄清楚方法>>应该编写的代码的行为方式。

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

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


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

    我认为您在这里有几个误解。

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

    • “成功的结果”是什么?单个方法调用?这假设您知道结果应该是什么。这可能很困难,特别是如果该方法具有您不知道或不了解的副作用。


    最后一点是,某些代码天生就很难为其编写单元测试。通常,应该重写的是代码。 8,700 行的代码库(实际上)并没有那么大,并且可能是重写的候选者。 (您可以将现有代码视为“工作原型”......在没有一套体面的功能要求的情况下。)

    【讨论】:

      【解决方案2】:

      这是一个艰难的过程。这个答案几乎肯定会过度简化您要解决的问题的性质,但希望它能为您指明正确的方向。

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

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

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

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

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

      再一次 - 这是对一个主题的过度简化,我确信整本书都在讨论这个主题,但我希望这可以帮助您从高层次获得一些方向性的洞察力。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多