PHPUnit测试隔离以及如何编写一个测试,其中期望值取决于方法中经常更改的某个值



所以我用PHP编码,并用PHPUnit编写测试。我有这个方法convertCashAmountToMainCurrency,我想测试:

public static function convertCashAmountToMainCurrency(string $currency, float $amount): float
{
$feeInMainCurrency = $amount / Constants::CURRENCIES[$currency][Constants::RATE];
return self::roundUp($feeInMainCurrency, Constants::CURRENCIES[Constants::MAIN_CURRENCY][Constants::PRECISION]);
}
public static function roundUp(float $value, int $precision): float
{
$pow = pow(10, $precision);
return (ceil($pow * $value) + ceil($pow * $value - ceil($pow * $value))) / $pow;
}

我有这里使用的常数

public const RATE = 'RATE';
public const PRECISION = 'PRECISION';
public const CURRENCY_EUR = 'EUR';
public const CURRENCY_USD = 'USD';
public const CURRENCY_JPY = 'JPY';
public const MAIN_CURRENCY = self::CURRENCY_EUR;
public const CURRENCIES = [
self::CURRENCY_EUR => [
self::RATE => 1,
self::PRECISION => 2,
],
self::CURRENCY_USD => [
self::RATE => 1.1497,
self::PRECISION => 2,
],
self::CURRENCY_JPY => [
self::RATE => 129.53,
self::PRECISION => 0,
],
];

我正在测试这样的方法:

/**
* @param string $currency
* @param float $amount
* @param float $expectation
*
* @runInSeparateProcess
* @preserveGlobalState disabled
* @dataProvider dataProviderForConvertCashAmountToMainCurrencyTesting
*/
public function testConvertCashAmountToMainCurrency(string $currency, float $amount, float $expectation)
{
$this->assertEquals(
$expectation,
Math::convertCashAmountToMainCurrency($currency, $amount)
);
}
public function dataProviderForConvertCashAmountToMainCurrencyTesting(): array
{
return [
'convert EUR to main currency' => [Constants::CURRENCY_EUR, 100.01, 100.01],
'convert USD to main currency' => [Constants::CURRENCY_USD, 100.01, 86.99],
'convert JPY to main currency' => [Constants::CURRENCY_JPY, 10001, 77.21],
];
}

当预期值和汇率以固定大小表示时,测试就可以通过。但问题是,无论货币兑换率值如何,我每次都需要通过测试,所以我不需要每次汇率变化时都重写测试因为现在,如果我更改RATE(例如,甚至PRECISION(值,测试将不会因为断言失败而通过。

既然有人告诉我需要隔离我的测试来解决这个问题,有人能证实这一点并让我走上解决问题的正确道路吗?提前感谢您的帮助!

问题是convertCashAmountToMainCurrency依赖于全局状态。有一些方法可以在测试中处理这个问题,但对我来说,这不是一个好的做法。我的建议是将转换提取到一个新的类中,该类可以传递所有所需的信息。这样它就不依赖于全局状态,并且可以很容易地进行隔离测试。

这可能看起来像这样:

class CurrencyConverter
{
private string $mainCurrency;
/**
* @var array<string, CurrencyConfig>
*/
private array $currencies;
/**
* @param array<string, CurrencyConfig> $currencies
*/
public function __construct(string $mainCurrency, array $currencies)
{
Assert::keyExists($currencies, $mainCurrency);
$this->mainCurrency = $mainCurrency;
$this->currencies   = $currencies;
}
public function toMainCurrency(string $currency, float $amount): float
{
Assert::keyExists($this->currencies, $currency);
$feeInMainCurrency = $amount / $this->currencies[$currency]->rate();
return self::roundUp($feeInMainCurrency, $this->currencies[$this->mainCurrency]->precision());
}
private static function roundUp(float $value, int $precision): float
{
$pow = pow(10, $precision);
return (ceil($pow * $value) + ceil($pow * $value - ceil($pow * $value))) / $pow;
}
}
class CurrencyConfig
{
private float $rate;
private int $precision;
public function __construct(float $rate, int $precision)
{
Assert::greaterThan($rate, 0);
Assert::greaterThanEq($precision, 0);
$this->rate      = $rate;
$this->precision = $precision;
}
public function rate(): float
{
return $this->rate;
}
public function precision(): int
{
return $this->precision;
}
}
/**
* @param string $currency
* @param float  $amount
* @param float  $expectation
*
* @runInSeparateProcess
* @preserveGlobalState disabled
* @dataProvider        dataProviderForConvertCashAmountToMainCurrencyTesting
*/
public function testConvertCashAmountToMainCurrency(string $currency, float $amount, float $expectation)
{
$converter = new CurrencyConverter(
Constants::CURRENCY_EUR,
[
Constants::CURRENCY_EUR => new CurrencyConfig(1, 2),
Constants::CURRENCY_USD => new CurrencyConfig(1.1497, 2),
Constants::CURRENCY_JPY => new CurrencyConfig(129.53, 0),
]
);
self::assertEquals($expectation, $converter->toMainCurrency($currency, $amount));
}

如果您无法摆脱现有的静态方法,只需使用全局状态中的配置创建一个转换器即可。

class Math
{
public static function convertCashAmountToMainCurrency(string $currency, float $amount): float
{
$converter = new CurrencyConverter(
Constants::MAIN_CURRENCY,
array_map(
fn(array $item) => new CurrencyConfig($item[Constants::RATE], $item[Constants::PRECISION]),
Constants::CURRENCIES
)
);
return $converter->toMainCurrency($currency, $amount);
}
}

最新更新