如何测试范围测试的所有分支?

问题描述 投票:0回答:1

虽然目标是 100% 分支覆盖率,但我在测试范围检查的两个条件时遇到了一些困难:

// Find the matching tier
    for (uint256 i = 0; i < tiers.length; i++) {
      if (tiers[i].minVal() <= cumReceivedInvestments && cumReceivedInvestments < tiers[i].maxVal()) {
        return tiers[i];
      }
    }

如LCOV所示:

为了测试这些条件,我尝试了:

// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.23 <0.9.0;

import { PRBTest } from "@prb/test/src/PRBTest.sol";
import { StdCheats } from "forge-std/src/StdCheats.sol";
import { DecentralisedInvestmentHelper } from "../../src/Helper.sol";
import { TierInvestment } from "../../src/TierInvestment.sol";
import { Tier } from "../../src/Tier.sol";

contract HelperTest is PRBTest, StdCheats {
  TierInvestment internal validTierInvestment;

  uint256 private cumReceivedInvestments;

  Tier[] private _tiers;
  Tier[] private someTiers;
  DecentralisedInvestmentHelper private _helper;

  /// @dev A function invoked before each test case is run.
  function setUp() public virtual {
    cumReceivedInvestments = 5;

    // Specify the investment tiers in ether.
    uint256 firstTierCeiling = 4 ether;
    uint256 secondTierCeiling = 15 ether;
    uint256 thirdTierCeiling = 30 ether;

    // Start lowst tier at 2 wei, such that the tested cumulative investment
    //amount can go below that at 1 wei.
    Tier tier_0 = new Tier(2, firstTierCeiling, 10);
    _tiers.push(tier_0);
    Tier tier_1 = new Tier(firstTierCeiling, secondTierCeiling, 5);
    _tiers.push(tier_1);
    Tier tier_2 = new Tier(secondTierCeiling, thirdTierCeiling, 2);
    _tiers.push(tier_2);

    // Initialise contract helper.
    _helper = new DecentralisedInvestmentHelper();
  }

  function testExceedInvestmentCeiling() public {
    // vm.prank(address(validTierInvestment));
    vm.expectRevert(bytes("The investment ceiling is reached."));
    _helper.computeCurrentInvestmentTier(30 ether + 1 wei, _tiers);
  }

  function testNegativeInvestment() public {
    // vm.prank(address(validTierInvestment));
    vm.expectRevert(
      bytes(
        "Unexpected state: No matching tier found, the lowest investment tier starting point was larger than the cumulative received investments. All (Tier) arrays should start at 0."
      )
    );
    _helper.computeCurrentInvestmentTier(1 wei, _tiers);
  }

  function testCanInvestInNextTier() public {
    // True True for tier 0.
    assertEq(_helper.computeCurrentInvestmentTier(2 wei, _tiers).multiple(), 10);

    // False False for tier 0.
    assertEq(_helper.computeCurrentInvestmentTier(10 ether + 1 wei, _tiers).multiple(), 5);

    // Hits investment ceiling
    // assertEq(_helper.computeCurrentInvestmentTier(30 ether+1 wei, _tiers).multiple(), 2);

    // True True for tier 0, True True for tier 1 but tier 1 is not reached.,
    assertEq(_helper.computeCurrentInvestmentTier(2 wei, _tiers).multiple(), 10);

    // Hits investment ceiling before this can reach Tier 0.
    // False True for tier 0, True True for tier 1
    // assertEq(_helper.computeCurrentInvestmentTier(1 wei, _tiers).multiple(), 10);
  }

  function testGetRemainingAmountInCurrentTierBelow() public {
    vm.expectRevert(bytes("Error: Tier's minimum value exceeds received investments."));
    _helper.getRemainingAmountInCurrentTier(1 wei, _tiers[0]);
  }

  function testGetRemainingAmountInCurrentTierAbove() public {
    vm.expectRevert(bytes("Error: Tier's maximum value is not larger than received investments."));
    _helper.getRemainingAmountInCurrentTier(4 ether + 1 wei, _tiers[0]);
  }

  function testComputeRemainingInvestorPayoutNegativeFraction() public {
    vm.expectRevert(bytes("investorFracNumerator is smaller than investorFracDenominator."));
    _helper.computeRemainingInvestorPayout(0, 1, 0, 0);
  }
}

我相信这测试了这个案例:

假与真

tiers[i].minVal() <= cumReceivedInvestments = false
cumReceivedInvestments < tiers[i].maxVal() = true

对于:

tier 0
cumReceivedInvestments = 10 ether+1 wei

正确&&正确

tiers[i].minVal() <= cumReceivedInvestments = true
cumReceivedInvestments < tiers[i].maxVal() = true

对于:

tier 0
cumReceivedInvestments = 2 wei

假&&假

tiers[i].minVal() <= cumReceivedInvestments = false
cumReceivedInvestments < tiers[i].maxVal() = false

对于:

tier 0
cumReceivedInvestments = 10+1 wei

对与错

但是,我认为我无法意识到:

tiers[i].minVal() <= cumReceivedInvestments = true
cumReceivedInvestments < tiers[i].maxVal() = false

因为 Tier 对象要求

maxVal
大于
minVal
,所以如果第一行为 true,则
cumReceivedInvestments < tiers[i].maxVal()
本身也为 true。

额外的尝试

我想,如果我将该逻辑分离到一个单独的函数中,并将所有测试用例放入该函数中,那么我将能够覆盖所有用例。事实并非如此,我想我错误地认为这是我无法意识到的

True && False
情况。也许 LCOV 说由于某种原因,由于 for 循环,它没有被覆盖。为了完整起见,这里是分离的函数:

function isInRange(uint256 minVal, uint256 maxVal, uint256 someVal) public view override returns (bool inRange) {
    if (minVal <= someVal && someVal < maxVal) {
      inRange = true;
    } else {
      inRange = false;
    }
    return inRange;
  }

  function computeCurrentInvestmentTier(
    uint256 cumReceivedInvestments,
    Tier[] memory tiers
  ) public view override returns (Tier currentTier) {
    // Check for exceeding investment ceiling.
    if (hasReachedInvestmentCeiling(cumReceivedInvestments, tiers)) {
      revert ReachedInvestmentCeiling(cumReceivedInvestments, "Investment ceiling is reached.");
    }

    // Find the matching tier
    uint256 nrOfTiers = tiers.length;
    for (uint256 i = 0; i < nrOfTiers; ++i) {
      if (isInRange(tiers[i].getMinVal(), tiers[i].getMaxVal(), cumReceivedInvestments)) {
        currentTier = tiers[i];
        return currentTier;
      }
    }
    // Should not reach here with valid tiers
    revert(
      string(
        abi.encodePacked(
          "Unexpected state: No matching tier found, the lowest ",
          "investment tier starting point was larger than the ",
          "cumulative received investments. All (Tier) arrays should start at 0."
        )
      )
    );
  }

并测试:

function testCanInvestInNextTier() public override {
    // True True for tier 0.
    assertEq(_helper.computeCurrentInvestmentTier(2 wei, _tiers).getMultiple(), 10);
    assertTrue(_helper.isInRange(1, 3, 2));

    // False False for tier 0.
    assertEq(_helper.computeCurrentInvestmentTier(10 ether + 1 wei, _tiers).getMultiple(), 5);
    assertFalse(_helper.isInRange(1, 2, 4));

    // Hits investment ceiling
    // assertEq(_helper.computeCurrentInvestmentTier(30 ether+1 wei, _tiers).getMultiple(), 2);

    // True True for tier 0, True True for tier 1 but tier 1 is not reached.,
    assertEq(_helper.computeCurrentInvestmentTier(2 wei, _tiers).getMultiple(), 10);
    assertFalse(_helper.isInRange(1, 2, 0));
    assertFalse(_helper.isInRange(3, 2, 1));

    // Hits investment ceiling before this can reach Tier 0.
    // False True for tier 0, True True for tier 1
    // assertEq(_helper.computeCurrentInvestmentTier(1 wei, _tiers).getMultiple(), 10);
  }

以及随附的 LCOV 报告:

问题

假设我想要 100% 的测试覆盖率,我该如何解决这个问题?例如,我是否应该更改 if 条件,是否应该移动或更改 require 语句,或者我的分析是否不正确,是否可以测试

&&
条件的所有 4 个条件,或者其他什么?

功能齐全

为了完整性,这里是正在测试的完整功能:

function hasReachedInvestmentCeiling(uint256 cumReceivedInvestments, Tier[] memory tiers) public view returns (bool) {
    return cumReceivedInvestments >= getInvestmentCeiling(tiers);
  }

  function computeCurrentInvestmentTier(
    uint256 cumReceivedInvestments,
    Tier[] memory tiers
  ) public view returns (Tier) {
    // Check for exceeding investment ceiling.

    require(!hasReachedInvestmentCeiling(cumReceivedInvestments, tiers), "The investment ceiling is reached.");

    // Find the matching tier
    for (uint256 i = 0; i < tiers.length; i++) {
      if (tiers[i].minVal() <= cumReceivedInvestments && cumReceivedInvestments < tiers[i].maxVal()) {
        return tiers[i];
      }
    }
    // Should not reach here with valid tiers
    revert(
      "Unexpected state: No matching tier found, the lowest investment tier starting point was larger than the cumulative received investments. All (Tier) arrays should start at 0."
    );
  }
if-statement solidity code-coverage test-coverage
1个回答
0
投票

您可以将其重写为 while 循环,然后进行 2 个单独的检查。我假设层数组已排序且健全 - 即层不重叠且之间没有漏洞。

对于循环终止,您检查最小值,对于增量,您检查最大值。

我不熟悉这种 Sol 语言,但它看起来像 C,所以我建议这样:

uint256 i = 0;
while(tiers[i].minVal() <= cumReceivedInvestments) { 
  if (cumReceivedInvestments < tiers[i].maxVal()) {
    i++;
  }
}

return tiers[i];

这样每个分支都将被测试,并且代码仍然实现相同的目标。

© www.soinside.com 2019 - 2024. All rights reserved.