Как протестировать все ветви теста диапазона?

Стремясь к 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);
  }
}

Я считаю, что это подтверждает ситуацию:

False true

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, поэтому, если первая строка истинна, по сути, cumReceivedInvestments < tiers[i].maxVal() также верна.

Дополнительная попытка

Я подумал, что если я выделю эту логику в отдельную функцию и брошу в нее все тестовые примеры, то смогу охватить все случаи. Это был не тот случай, и я думаю, что я неправильно предположил, что это тот случай, который я не могу осознать. Возможно, 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 условия условия True && False или, возможно, что-то еще?

Вся функция

Для полноты вот полная тестируемая функция:

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."
    );
  }
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
82
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы можете переписать это как цикл while и затем выполнить две отдельные проверки. Я предполагаю, что массив уровней отсортирован и исправен, т. е. уровни не перекрываются и между ними нет дыр.

Для завершения цикла вы проверяете минимум, а для приращения — максимум.

Я не знаком с этим языком Sol, но он похож на C, поэтому я бы предложил что-то вроде этого:

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

return tiers[i];

Таким образом, каждая ветвь будет протестирована, и код по-прежнему достигнет той же цели.

Другие вопросы по теме