test improvements

This commit is contained in:
Dennis Eichhorn 2020-09-02 20:39:14 +02:00
parent b084791809
commit ace6e49380
8 changed files with 79 additions and 76 deletions

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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;

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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();

View File

@ -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));
}
}