Skip to content

Commit

Permalink
Merge pull request #16 from cgardner/more-configurable-chances
Browse files Browse the repository at this point in the history
Customizable Chances
  • Loading branch information
daylerees authored Jul 17, 2017
2 parents db2eab4 + 836ab19 commit e4d88a8
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 15 deletions.
6 changes: 3 additions & 3 deletions build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<!-- Execute code style check. -->
<target name="phpcs">
<echo>Running PHP PSR-2 code style checks.</echo>
<exec executable="vendor/bin/phpcs" passthru="true">
<exec executable="vendor/bin/phpcs" passthru="true" checkreturn="true">
<arg value="--colors" />
<arg value="--standard=PSR2" />
<arg value="src" />
Expand All @@ -23,15 +23,15 @@
<!-- Execute PHP linting. -->
<target name="phplint">
<echo>Running PHP parallel linter.</echo>
<exec executable="vendor/bin/parallel-lint" passthru="true">
<exec executable="vendor/bin/parallel-lint" passthru="true" checkreturn="true">
<arg value="src" />
</exec>
</target>

<!-- Execute PHPUnit tests. -->
<target name="phpunit">
<echo>Running PHP Unit tests.</echo>
<exec executable="vendor/bin/phpunit" passthru="true">
<exec executable="vendor/bin/phpunit" passthru="true" checkreturn="true">
<arg value="--color" />
</exec>
</target>
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
}
],
"require": {
"php": "^5.5 || ^7.0"
"php": "^5.5 || ^7.0",
"ircmaxell/random-lib": "^1.2"
},
"require-dev": {
"phpunit/phpunit": ">=4.8",
Expand Down
7 changes: 7 additions & 0 deletions src/Chances/Chance.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php
namespace Scientist\Chances;

interface Chance
{
public function shouldRun();
}
57 changes: 57 additions & 0 deletions src/Chances/StandardChance.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php
namespace Scientist\Chances;

use RandomLib\Factory;
use RandomLib\Generator;

class StandardChance implements Chance
{
private $generator;

private $percentage = 100;

/**
* StandardChance constructor.
* @param Generator|null $generator
*/
public function __construct(Generator $generator = null)
{
if ($generator === null) {
$factory = new Factory;
$generator = $factory->getLowStrengthGenerator();
}
$this->generator = $generator;
}

/**
* Determine whether or not the experiment should run
*/
public function shouldRun()
{
if ($this->percentage == 0) {
return false;
}

$random = $this->generator
->generateInt(0, 100);
return $random <= $this->percentage;
}

/**
* @return int
*/
public function getPercentage()
{
return $this->percentage;
}

/**
* @param int $percentage
* @return $this
*/
public function setPercentage($percentage)
{
$this->percentage = $percentage;
return $this;
}
}
18 changes: 11 additions & 7 deletions src/Experiment.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Scientist;

use Scientist\Chances\Chance;
use Scientist\Chances\StandardChance;
use Scientist\Matchers\Matcher;
use Scientist\Matchers\StandardMatcher;

Expand Down Expand Up @@ -60,9 +62,9 @@ class Experiment
/**
* Execution chance.
*
* @var integer
* @var \Scientist\Chances\Chance
*/
protected $chance = 100;
protected $chance;

/**
* Create a new experiment.
Expand All @@ -75,6 +77,7 @@ public function __construct($name, Laboratory $laboratory)
$this->name = $name;
$this->laboratory = $laboratory;
$this->matcher = new StandardMatcher;
$this->chance = new StandardChance;
}

/**
Expand Down Expand Up @@ -185,21 +188,21 @@ public function getMatcher()
/**
* Set the execution chance.
*
* @param integer $chance
* @param Chances\Chance $chance
*
* @return $this
*/
public function chance($chance)
public function chance(Chance $chance)
{
$this->chance = (int) $chance;
$this->chance = $chance;

return $this;
}

/**
* Get the execution chance.
*
* @return integer
* @return Chances\Chance
*/
public function getChance()
{
Expand All @@ -213,7 +216,8 @@ public function getChance()
*/
public function shouldRun()
{
return rand(0, 100) <= $this->chance;
return $this->chance
->shouldRun();
}

/**
Expand Down
1 change: 0 additions & 1 deletion src/Machine.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ public function __construct(callable $callback, array $params = [], $muted = fal
$this->params = $params;
$this->muted = $muted;
$this->result = new Result;

}

/**
Expand Down
156 changes: 156 additions & 0 deletions tests/Chances/StandardChanceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<?php
namespace Scientist\Chances;

use RandomLib\Generator;

class StandardChanceTest extends \PHPUnit_Framework_TestCase
{
/**
* @var StandardChance
*/
private $chance;

/**
* @var Generator
*/
private $generator;

public function setUp()
{
$this->generator = $this->getMockGenerator();
$this->chance = new StandardChance($this->generator);
}

public function test_that_standard_chance_is_an_instance_of_chance()
{
$chance = new StandardChance();
$this->assertInstanceOf('\Scientist\Chances\Chance', $chance);
}

public function test_that_a_random_number_generator_is_created_upon_instantiation()
{
$chance = new StandardChance();
$reflection = new \ReflectionClass($chance);
$property = $reflection->getProperty('generator');
$property->setAccessible(true);

$this->assertInstanceOf('\RandomLib\Generator', $property->getValue($chance));
}

public function test_that_it_takes_a_custom_random_number_generator_in_the_constructor()
{
$reflection = new \ReflectionClass($this->chance);
$property = $reflection->getProperty('generator');
$property->setAccessible(true);

$this->assertSame($this->generator, $property->getValue($this->chance));
}

/**
* @dataProvider percentageDataProvider
*/
public function test_that_should_run_returns_true_when_the_chance_is_100($random)
{
$this->generator
->expects($this->once())
->method('generateInt')
->with(0, 100)
->willReturn($random);

$this->assertTrue($this->chance->shouldRun());
}

public function test_that_the_default_percentage_is_100()
{
$this->assertEquals(100, $this->chance->getPercentage());
}

public function test_that_set_percentage_sets_the_percentage()
{
$percentage = rand(1, 100);
$this->chance
->setPercentage($percentage);
$this->assertEquals($percentage, $this->chance->getPercentage());
}

public function test_that_set_percentage_returns_the_chance_object_for_chaining()
{
$percentage = rand(1, 100);
$this->assertSame($this->chance, $this->chance->setPercentage($percentage));
}

public function test_that_should_run_always_returns_false_when_percentage_is_zero()
{
$this->generator
->expects($this->never())
->method('generateInt');
$this->chance
->setPercentage(0);
$this->assertFalse($this->chance->shouldRun());
}

/**
* @dataProvider nonZeroPercentageDataProvider
* @param integer $percentage Percentage of the time to run
*/
public function test_that_it_returns_true_when_percentage_is_greater_than_the_generated_number($percentage)
{
$this->generator
->expects($this->once())
->method('generateInt')
->with(0, 100)
->willReturn($percentage - 1);

$this->chance
->setPercentage($percentage);

$this->assertTrue($this->chance->shouldRun());
}

/**
* @dataProvider nonZeroPercentageDataProvider
* @param integer $percentage Percentage of the time to run
*/
public function test_that_it_returns_false_when_percentage_is_less_than_the_generated_number($percentage)
{
$this->generator
->expects($this->once())
->method('generateInt')
->with(0, 100)
->willReturn($percentage + 1);

$this->chance
->setPercentage($percentage);

$this->assertFalse($this->chance->shouldRun());
}

/**
* @return array
*/
public function nonZeroPercentageDataProvider()
{
$percentages = $this->percentageDataProvider();
array_shift($percentages);
return $percentages;
}

/**
* Data provider to cover all 100 percentage values
* @return array
*/
public function percentageDataProvider()
{
return array_map(function ($value) {
return [$value];
}, range(0, 100));
}

public function getMockGenerator()
{
return $this->getMockBuilder('\RandomLib\Generator')
->disableOriginalConstructor()
->disableProxyingToOriginalMethods()
->getMock();
}
}
20 changes: 17 additions & 3 deletions tests/ExperimentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,10 @@ public function test_that_multiple_trial_callbacks_can_be_defined()

public function test_that_a_chance_variable_can_be_set()
{
$chance = $this->getMock('\Scientist\Chances\Chance');
$e = new Experiment('test experiment', new Laboratory);
$e->chance(50);
$this->assertEquals(50, $e->getChance());
$e->chance($chance);
$this->assertEquals($chance, $e->getChance());
}

public function test_that_an_experiment_matcher_can_be_set()
Expand Down Expand Up @@ -93,12 +94,25 @@ public function test_that_running_experiment_with_no_laboratory_executes_control

public function test_that_running_experiment_with_zero_chance_executes_control()
{
$chance = $this->getMockChance();
$chance->expects($this->once())
->method('shouldRun')
->willReturn(false);

$l = new Laboratory;
$v = $l->experiment('test experiment')
->control(function () { return 'foo'; })
->chance(0)
->chance($chance)
->run();

$this->assertEquals('foo', $v);
}

public function getMockChance()
{
return $this->getMockBuilder('\Scientist\Chances\Chance')
->disableOriginalConstructor()
->disableProxyingToOriginalMethods()
->getMock();
}
}

0 comments on commit e4d88a8

Please sign in to comment.