虽然目标是 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];
}
}
为了测试这些条件,我尝试了:
// 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);
}
假设我想要 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."
);
}
您可以将其重写为 while 循环,然后进行 2 个单独的检查。我假设层数组已排序且健全 - 即层不重叠且之间没有漏洞。
对于循环终止,您检查最小值,对于增量,您检查最大值。
我不熟悉这种 Sol 语言,但它看起来像 C,所以我建议这样:
uint256 i = 0;
while(tiers[i].minVal() <= cumReceivedInvestments) {
if (cumReceivedInvestments < tiers[i].maxVal()) {
i++;
}
}
return tiers[i];
这样每个分支都将被测试,并且代码仍然实现相同的目标。