mirror of
https://github.com/Karaka-Management/phpOMS.git
synced 2026-01-11 09:48:40 +00:00
test improvements
This commit is contained in:
parent
b084791809
commit
ace6e49380
|
|
@ -28,8 +28,8 @@ final class CustomerValue
|
|||
* Simple customer lifetime value
|
||||
*
|
||||
* @param float $margin Margin per period
|
||||
* @param float $retentionRate Rate of remaining customers per period
|
||||
* @param float $discountRate Cost of capital to discound future revenue
|
||||
* @param float $retentionRate Rate of remaining customers per period (= average lifetime / (1 + average lifetime))
|
||||
* @param float $discountRate Cost of capital to discount future revenue
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
|
|
@ -43,30 +43,30 @@ final class CustomerValue
|
|||
/**
|
||||
* Normalized measure of recurring revenue
|
||||
*
|
||||
* @param array $revenues Revenues
|
||||
* @param int $periods Amount of revenue periods
|
||||
* @param float $cutoff Normalization cutoff (which values should be ignored)
|
||||
* @param array $revenues Revenues
|
||||
* @param int $periods Amount of revenue periods
|
||||
* @param float $lowerCutoff Normalization cutoff (which lower values should be ignored)
|
||||
* @param float $upperCutoff Normalization cutoff (which upper values should be ignored)
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function getMRR(array $revenues, int $periods = 12, float $cutoff = 0.1) : float
|
||||
public static function getMRR(array $revenues, int $periods = 12, float $lowerCutoff = 0.1, float $upperCutoff = 0.0) : float
|
||||
{
|
||||
if ($cutoff === 0.0) {
|
||||
if ($lowerCutoff === 0.0 && $upperCutoff === 0.0) {
|
||||
return \array_sum($revenues) / $periods;
|
||||
}
|
||||
|
||||
$count = \count($revenues);
|
||||
$offset = (int) \round($count * $cutoff, 0, \PHP_ROUND_HALF_UP);
|
||||
\sort($revenues);
|
||||
|
||||
if ($offset * 2 >= $count) {
|
||||
return 0.0;
|
||||
$sum = 0.0;
|
||||
foreach ($revenues as $revenue) {
|
||||
if ($revenue >= $lowerCutoff && $revenue <= $upperCutoff) {
|
||||
$sum += $revenue;
|
||||
}
|
||||
}
|
||||
|
||||
\sort($revenues);
|
||||
$normalized = \array_splice($revenues, $offset, $count - $offset);
|
||||
|
||||
return \array_sum($normalized) / $periods;
|
||||
return $sum / $periods;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,46 +87,6 @@ final class Metrics
|
|||
- $customers * $acquistionCost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Life time value of a customer
|
||||
*
|
||||
* @param \Closure $customerProfit Profit of a customer in year t
|
||||
* @param float $discountRate Discount rate
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function lifeTimeValue(\Closure $customerProfit, float $discountRate) : float
|
||||
{
|
||||
$ltv = 0.0;
|
||||
for ($i = 1; $i < 1000000; ++$i) {
|
||||
$ltv += $customerProfit($i) / \pow(1 + $discountRate, $i - 1);
|
||||
}
|
||||
|
||||
return $ltv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Life time value of a customer
|
||||
*
|
||||
* @param \Closure $customerProfit Profit of a customer in year t
|
||||
* @param float $discountRate Discount rate
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function simpleRetentionLifeTimeValue(\Closure $customerProfit, float $discountRate, float $retentionRate) : float
|
||||
{
|
||||
$ltv = 0.0;
|
||||
for ($i = 1; $i < 1000000; ++$i) {
|
||||
$ltv += $customerProfit($i) * \pow($retentionRate, $i - 1) / \pow(1 + $discountRate, $i - 1);
|
||||
}
|
||||
|
||||
return $ltv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the profitability of customers based on their purchase behaviour
|
||||
*
|
||||
|
|
@ -140,7 +100,7 @@ final class Metrics
|
|||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function calclateMailingSuccess(float $discountRate, array $purchaseProbability, array $payoffs) : Matrix
|
||||
public static function calculateMailingSuccess(float $discountRate, array $purchaseProbability, array $payoffs) : Matrix
|
||||
{
|
||||
$count = \count($purchaseProbability);
|
||||
$profit = new Matrix($count, $count);
|
||||
|
|
|
|||
|
|
@ -1846,7 +1846,6 @@ class DataMapperAbstract implements DataMapperInterface
|
|||
}
|
||||
|
||||
self::deleteModel($obj, $objId, $refClass);
|
||||
|
||||
self::clear();
|
||||
|
||||
return $objId;
|
||||
|
|
@ -1872,7 +1871,9 @@ class DataMapperAbstract implements DataMapperInterface
|
|||
$obj = [];
|
||||
|
||||
foreach ($result as $element) {
|
||||
if (isset($element[static::$primaryField . '_' . $depth]) && self::isInitialized(static::class, $element[static::$primaryField . '_' . $depth], $depth)) {
|
||||
if (isset($element[static::$primaryField . '_' . $depth])
|
||||
&& self::isInitialized(static::class, $element[static::$primaryField . '_' . $depth], $depth)
|
||||
) {
|
||||
$obj[$element[static::$primaryField . '_' . $depth]] = self::$initObjects[static::class][$element[static::$primaryField . '_' . $depth]['obj']];
|
||||
|
||||
continue;
|
||||
|
|
@ -1880,12 +1881,12 @@ class DataMapperAbstract implements DataMapperInterface
|
|||
|
||||
$toFill = self::createBaseModel();
|
||||
|
||||
if (isset($element[static::$primaryField . '_' . $depth])) {
|
||||
$obj[$element[static::$primaryField . '_' . $depth]] = self::populateAbstract($element, $toFill, $depth);
|
||||
self::addInitialized(static::class, $element[static::$primaryField . '_' . $depth], $obj[$element[static::$primaryField . '_' . $depth]], $depth);
|
||||
} else {
|
||||
if (!isset($element[static::$primaryField . '_' . $depth])) {
|
||||
throw new \Exception();
|
||||
}
|
||||
|
||||
$obj[$element[static::$primaryField . '_' . $depth]] = self::populateAbstract($element, $toFill, $depth);
|
||||
self::addInitialized(static::class, $element[static::$primaryField . '_' . $depth], $obj[$element[static::$primaryField . '_' . $depth]], $depth);
|
||||
}
|
||||
|
||||
return $obj;
|
||||
|
|
@ -1906,18 +1907,20 @@ class DataMapperAbstract implements DataMapperInterface
|
|||
$obj = [];
|
||||
|
||||
foreach ($result as $element) {
|
||||
if (isset($element[static::$primaryField]) && self::isInitializedArray(static::class, $element[static::$primaryField], $depth)) {
|
||||
if (isset($element[static::$primaryField])
|
||||
&& self::isInitializedArray(static::class, $element[static::$primaryField], $depth)
|
||||
) {
|
||||
$obj[$element[static::$primaryField]] = self::$initArrays[static::class][$element[static::$primaryField]]['obj'];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($element[static::$primaryField])) {
|
||||
$obj[$element[static::$primaryField]] = self::populateAbstractArray($element, [], $depth);
|
||||
self::addInitializedArray(static::class, $element[static::$primaryField], $obj[$element[static::$primaryField]], $depth);
|
||||
} else {
|
||||
if (!isset($element[static::$primaryField])) {
|
||||
throw new \Exception();
|
||||
}
|
||||
|
||||
$obj[$element[static::$primaryField]] = self::populateAbstractArray($element, [], $depth);
|
||||
self::addInitializedArray(static::class, $element[static::$primaryField], $obj[$element[static::$primaryField]], $depth);
|
||||
}
|
||||
|
||||
return $obj;
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ final class FileLogger implements LoggerInterface
|
|||
$this->verbose = $verbose;
|
||||
|
||||
if (\is_dir($lpath) || \strpos($lpath, '.') === false) {
|
||||
$path = \rtrim($lpath, '/') . '/' . \date('Y-m-d') . '.log';
|
||||
$path = \rtrim($path !== false ? $path : $lpath, '/') . '/' . \date('Y-m-d') . '.log';
|
||||
} else {
|
||||
$path = $lpath;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace phpOMS\tests\Business\Marketing;
|
||||
|
||||
use phpOMS\Business\Marketing\CustomerValue;
|
||||
|
||||
/**
|
||||
* @testdox phpOMS\tests\Business\Marketing\CustomerValueTest: Customer value
|
||||
*
|
||||
|
|
@ -22,18 +24,28 @@ namespace phpOMS\tests\Business\Marketing;
|
|||
class CustomerValueTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @testdox The simple customer life time value is correctly calculated
|
||||
* @group framework
|
||||
*/
|
||||
public function testSimpleCLV() : void
|
||||
{
|
||||
self::markTestIncomplete();
|
||||
$margin = 3000;
|
||||
$years = 10;
|
||||
$retention = $years / (1 + $years);
|
||||
self::assertEqualsWithDelta(30000, CustomerValue::getSimpleCLV($margin, $retention, 0.0), 0.1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox The monthly recurring revenue (MRR) is correctly calculated
|
||||
* @group framework
|
||||
*/
|
||||
public function testMRR() : void
|
||||
{
|
||||
self::markTestIncomplete();
|
||||
$revenues = [
|
||||
1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096
|
||||
];
|
||||
|
||||
self::assertEqualsWithDelta(77.53846, CustomerValue::getMRR($revenues, 13, 10, 1000), 0.01);
|
||||
self::assertEqualsWithDelta(630.07692307, CustomerValue::getMRR($revenues, 13, 0.0, 0.0), 0.01);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ class ConnectionFactoryTest extends \PHPUnit\Framework\TestCase
|
|||
}
|
||||
|
||||
/**
|
||||
* @testdox An invalid cache type results in an exception
|
||||
* @testdox A invalid cache type results in an exception
|
||||
* @group framework
|
||||
*/
|
||||
public function testInvalidCacheType() : void
|
||||
|
|
|
|||
|
|
@ -37,9 +37,10 @@ class ModuleManagerTest extends \PHPUnit\Framework\TestCase
|
|||
|
||||
protected function setUp() : void
|
||||
{
|
||||
$this->app = new class() extends ApplicationAbstract {
|
||||
protected string $appName = 'Api';
|
||||
};
|
||||
$this->app = new class() extends ApplicationAbstract {
|
||||
protected string $appName = 'Api';
|
||||
};
|
||||
|
||||
$this->app->appName = 'Api';
|
||||
$this->app->dbPool = $GLOBALS['dbpool'];
|
||||
$this->app->router = new WebRouter();
|
||||
|
|
|
|||
|
|
@ -17,19 +17,46 @@ namespace phpOMS\tests\Module;
|
|||
require_once __DIR__ . '/../Autoloader.php';
|
||||
|
||||
use phpOMS\Application\ApplicationAbstract;
|
||||
use phpOMS\Log\FileLogger;
|
||||
use phpOMS\Module\NullModule;
|
||||
use phpOMS\Utils\TestUtils;
|
||||
|
||||
/**
|
||||
* @testdox phpOMS\tests\Module\NullModuleTest: Basic module functionality
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class NullModuleTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function testModule() : void
|
||||
protected NullModule $module;
|
||||
|
||||
protected function setUp() : void
|
||||
{
|
||||
$app = new class() extends ApplicationAbstract
|
||||
{
|
||||
};
|
||||
|
||||
self::assertInstanceOf('\phpOMS\Module\ModuleAbstract', new NullModule($app));
|
||||
$this->module = new NullModule($app);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group framework
|
||||
*/
|
||||
public function testModule() : void
|
||||
{
|
||||
self::assertInstanceOf('\phpOMS\Module\ModuleAbstract', $this->module);
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox A invalid module method call will create an error log
|
||||
* @covers phpOMS\Module\NullModule
|
||||
* @group framework
|
||||
*/
|
||||
public function testInvalidModuleMethodCalls() : void
|
||||
{
|
||||
$this->module->invalidMethodCall();
|
||||
|
||||
$path = TestUtils::getMember(FileLogger::getInstance(), 'path');
|
||||
self::assertStringContainsString('Expected module/controller but got NullModule.', \file_get_contents($path));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user