Code Katas - Notes
Make folder called Katas, inside make directory called src & tests then run
composer require phpunit/phpunit
vendor/bin/phpunit --generate-configuration
Troubleshoot
This test does not have a @covers annotation but is expected to have one
/**
*
* @test
* @covers \\src\\RomanNumerals -> find the one that it refers
*/
/**
* @dataProvider checks -> get the function checks
*/
Prime Numbers
<?php
namespace App;
class PrimeFactors {
public function generate($number) {
$factors = [];
for ($divisor = 2; $number > 1;$divisor++ ) {
for (;$number % $divisor === 0; $number /= $divisor) {
$factors[] = $divisor;
}
}
return $factors;
}
}
<?php
use PHPUnit\\Framework\\TestCase;
class PrimeFactorsTest extends TestCase
{
/**
*
* @test
* @dataProvider factors
*/
function it_generate_prime_factors_for_1($number, $expected)
{
$factors = new \\App\\PrimeFactors;
$this->assertEquals($expected, $factors->generate($number));
}
public function factors() {
return [
[1, []],
[2, [2]],
[3, [3]],
[4, [2,2]],
[5, [5]],
[6, [2,3]],
[8, [2,2,2]],
[100, [2,2,5,5]],
[999, [3,3,3,37]],
];
}
}
Roman Numerals
<?php
use App\\RomanNumerals;
use PHPUnit\\Framework\\TestCase;
class RomanNumeralsTest extends TestCase
{
/**
*
* @test
* @covers \\src\\RomanNumerals
* @dataProvider checks
*/
function it_generates_the_roman_numeral_for_1($number, $numeral)
{
$this->assertEquals($numeral, RomanNumerals::generate($number));
}
/**
*
* @test
* @covers \\src\\RomanNumerals
*/
function it_cannot_generate_a_roman_numeral()
{
$this->assertFalse(RomanNumerals::generate(0));
}
public function checks() {
return [
[1, 'I'],
[2, 'II'],
[3, 'III'],
[4, 'IV'],
[5, 'V'],
[6, 'VI'],
[7, 'VII'],
[8, 'VIII'],
[9, 'IX'],
[10, 'X'],
[40, 'XL'],
[50, 'L'],
[90, 'XC'],
[100, 'C'],
[500, 'D'],
[400, 'CD'],
[900, 'CM'],
[1000, 'M'],
];
}
}
<?php
namespace App;
class RomanNumerals {
const NUMERALS = [
'M' => 1000,
'CM' => 900,
'D' => 500,
'CD' => 400,
'C' => 100,
'XC' => 90,
'L' => 50,
'XL' => 40,
'X' => 10,
'IX' => 9,
'V' => 5,
'IV' => 4,
'I' => 1
];
public static function generate($number) {
if ($number <= 0) {
return false;
}
$result = '';
foreach (static::NUMERALS as $numeral => $arabic) {
for(;$number >= $arabic; $number -= $arabic) {
$result .= $numeral;
}
}
return $result;
}
}
BowlingGame
<?php
namespace App;
class GameBowling {
const FRAMES_PER_GAME = 10;
protected array $rolls = [];
public function roll(int $pins) {
$this->rolls[] = $pins;
}
protected function isStrike($roll) {
return $this->pinCount($roll)==10;
}
protected function isSpare($roll) {
return $this->defaultFrameScore($roll) == 10;
}
protected function defaultFrameScore(int $roll): int {
return $this->pinCount($roll) + $this->pinCount($roll+1);
}
protected function strikeBonus(int $roll): int {
return $this->pinCount($roll + 1) + $this->pinCount($roll + 2);
}
protected function spareBonus(int $roll): mixed {
return $this->pinCount($roll + 2);
}
protected function pinCount(int $roll): int {
return $this->rolls[$roll];
}
public function score() {
$score = 0;
$roll = 0;
foreach (range(1, self::FRAMES_PER_GAME) as $frame) {
if ($this->isStrike($roll)) {
$score += $this->pinCount($roll) + $this->strikeBonus($roll);
$roll += 1;
continue;
}
$score += $this->defaultFrameScore($roll);
if ($this->isSpare($roll)) {
$score += $this->spareBonus($roll);
$roll += 2;
continue;
}
$roll += 2;
}
return $score;
}
}
<?php
use App\\GameBowling;
use PHPUnit\\Framework\\TestCase;
class GameBowlingTest extends TestCase
{
/**
*
* @test
* @covers \\src\\GameBowling
*/
function it_scores_a_gutter_game_as_zero() {
$game = new GameBowling();
foreach (range(1,20) as $roll) {
$game->roll(0);
}
$this->assertSame(0, $game->score());
}
/**
*
* @test
* @covers \\src\\GameBowling
*/
function it_scores_all_ones()
{
$game = new GameBowling();
foreach (range(1, 20) as $roll) {
$game->roll(1);
}
$this->assertSame(20, $game->score());
}
/**
*
* @test
* @covers \\src\\GameBowling
*/
function it_awards_a_one_roll_bonus_for_every_spare() {
$game = new GameBowling();
$game->roll(5);
$game->roll(5);
$game->roll(8);
foreach (range(1,17) as $roll) {
$game->roll(0);
}
$this->assertSame(26, $game->score());
}
/**
*
* @test
* @covers \\src\\GameBowling
*/
function it_awards_a_two_roll_bonus_for_every_strike() {
$game = new GameBowling();
$game->roll(10);
$game->roll(5);
$game->roll(2);
foreach (range(1,16) as $roll) {
$game->roll(0);
}
$this->assertSame(24, $game->score());
}
/**
*
* @test
* @covers \\src\\GameBowling
*/
function a_spare_on_the_final_frame_grants_one_extra_balls() {
$game = new GameBowling();
foreach (range(1,18) as $roll) {
$game->roll(0);
}
$game->roll(5);
$game->roll(5);
$game->roll(5);
$this->assertSame(15, $game->score());
}
/**
*
* @test
* @covers \\src\\GameBowling
*/
function a_strike_on_the_final_frame_grants_two_extra_balls() {
$game = new GameBowling();
foreach (range(1,18) as $roll) {
$game->roll(0);
}
$game->roll(10);
$game->roll(10);
$game->roll(10);
$this->assertSame(30, $game->score());
}
/**
*
* @test
* @covers \\src\\GameBowling
*/
function it_scores_a_perfect_game() {
$game = new GameBowling();
foreach (range(1,12) as $roll) {
$game->roll(10);
}
$this->assertSame(300, $game->score());
}
}
String Calculator Kata
<?php
use App\\StringCalculator;
use PHPUnit\\Framework\\TestCase;
class StringCalculatorTest extends TestCase
{
/**
*
* @test
* @covers \\src\\StringCalculator
*/
function it_evaluates_an_empty_string_as_0() {
$calculator = new StringCalculator;
$this->assertEquals(0, $calculator->add(''));
}
/**
*
* @test
* @covers \\src\\StringCalculator
*/
function it_finds_the_sum_of_a_single_number() {
$calculator = new StringCalculator;
$this->assertEquals(5, $calculator->add('5'));
}
/**
*
* @test
* @covers \\src\\StringCalculator
*/
function it_finds_the_sum_of_two_numbers() {
$calculator = new StringCalculator;
$this->assertEquals(10, $calculator->add('5,5'));
}
/**
*
* @test
* @covers \\src\\StringCalculator
*/
function it_finds_the_sum_of_any_amount_of_numbers() {
$calculator = new StringCalculator;
$this->assertEquals(19, $calculator->add('5,5,5,4'));
}
/**
*
* @test
* @covers \\src\\StringCalculator
*/
function it_accept_a_new_line_character_as_a_delimiter_too() {
$calculator = new StringCalculator;
$this->assertEquals(10, $calculator->add("5\\n5"));
}
/**
*
* @test
* @covers \\src\\StringCalculator
*/
function negative_numbers_are_not_allowed() {
$calculator = new StringCalculator;
$this->expectException(\\Exception::class);
$this->assertEquals(10, $calculator->add("5,-4"));
}
/**
*
* @test
* @covers \\src\\StringCalculator
*/
function number_greater_than_1000_are_ignored() {
$calculator = new StringCalculator;
$this->assertEquals(5, $calculator->add("5,1001"));
}
/**
*
* @test
* @covers \\src\\StringCalculator
*/
function it_supports_custom_delimiters() {
$calculator = new StringCalculator;
$this->assertEquals(20, $calculator->add("//:\\n5:4:11"));
}
}
<?php
namespace App;
class StringCalculator {
const MAX_NUMBER_ALLOWED = 1000;
protected string $delimiter = ",|\\n";
public function add(string $numbers) {
$this->disallowNegatives($numbers = $this->parseString($numbers));
return array_sum($this
->ignoreGreaterThan1000($numbers)
);
}
protected function parseString(string $numbers) {
$customDelimiter = '\\/\\/(.)\\n';
if (preg_match("/{$customDelimiter}/",$numbers, $matches)) {
$this->delimiter = $matches[1];
$numbers = str_replace($matches[0], '', $numbers);
}
return preg_split("/{$this->delimiter}/", $numbers);
}
protected function disallowNegatives(array $numbers): void {
foreach ($numbers as $number) {
if (!empty($number) && $number < 0) {
throw new \\Exception('Negative numbers are disallowed');
}
}
}
protected function ignoreGreaterThan1000(array $numbers): array {
return array_filter(
$numbers, fn($number) => $number <= self::MAX_NUMBER_ALLOWED
);
}
}
Tennis Match
<?php
namespace App;
class TennisMatch {
protected Player $playerOne;
protected Player $playerTwo;
public function __construct(Player $playerOne, Player $playerTwo) {
$this->playerOne = $playerOne;
$this->playerTwo = $playerTwo;
}
public function score() {
if ($this->hasWinner()) {
return 'Winner: '.$this->leader()->name;
}
if ($this->hasAdvantage()) {
return 'Advantage: ' . $this->leader()->name;
}
if ($this->isDeuce()) {
return 'deuce';
}
return sprintf(
'%s-%s',
$this->playerOne->toTerm(),
$this->playerTwo->toTerm(),
);
}
protected function hasWinner(): bool {
if (max([$this->playerOne->points, $this->playerTwo->points]) < 4) {
return false;
}
return abs($this->playerOne->points - $this->playerTwo->points) >= 2 ;
}
protected function leader(): Player {
return $this->playerOne->points > $this->playerTwo->points
? $this->playerOne
: $this->playerTwo;
}
protected function isDeuce(): bool {
return $this->canBeWon() && $this->playerOne->points === $this->playerTwo->points;
}
protected function hasAdvantage(): bool {
if ($this->canBeWon()) {
return ! $this->isDeuce();
}
return false;
}
protected function canBeWon(): bool {
return $this->playerOne->points >= 3 && $this->playerTwo->points >= 3;
}
}
<?php
namespace App;
class Player
{
public string $name;
public int $points = 0;
public function __construct(string $name) {
$this->name = $name;
}
public function score() {
$this->points++;
}
public function toTerm() {
switch($this->points) {
case '0':
return 'love';
case '1':
return 'fifteen';
case '2':
return 'thirty';
case '3':
return 'forty';
}
}
}
<?php
namespace App;
class TennisMatch {
protected Player $playerOne;
protected Player $playerTwo;
public function __construct(Player $playerOne, Player $playerTwo) {
$this->playerOne = $playerOne;
$this->playerTwo = $playerTwo;
}
public function score() {
if ($this->hasWinner()) {
return 'Winner: '.$this->leader()->name;
}
if ($this->hasAdvantage()) {
return 'Advantage: ' . $this->leader()->name;
}
if ($this->isDeuce()) {
return 'deuce';
}
return sprintf(
'%s-%s',
$this->playerOne->toTerm(),
$this->playerTwo->toTerm(),
);
}
protected function hasWinner(): bool {
if (max([$this->playerOne->points, $this->playerTwo->points]) < 4) {
return false;
}
return abs($this->playerOne->points - $this->playerTwo->points) >= 2 ;
}
protected function leader(): Player {
return $this->playerOne->points > $this->playerTwo->points
? $this->playerOne
: $this->playerTwo;
}
protected function isDeuce(): bool {
return $this->canBeWon() && $this->playerOne->points === $this->playerTwo->points;
}
protected function hasAdvantage(): bool {
if ($this->canBeWon()) {
return ! $this->isDeuce();
}
return false;
}
protected function canBeWon(): bool {
return $this->playerOne->points >= 3 && $this->playerTwo->points >= 3;
}
}
Gilded rose Kata (best)
https://github.com/laracasts/gilded-rose-with-phpunit
We’ll be refactoring this.
<?php
namespace App;
class GildedRose
{
public $name;
public $quality;
public $sellIn;
public function __construct($name, $quality, $sellIn)
{
$this->name = $name;
$this->quality = $quality;
$this->sellIn = $sellIn;
}
public static function of($name, $quality, $sellIn)
{
return new static($name, $quality, $sellIn);
}
public function tick()
{
if ($this->name != 'Aged Brie' and $this->name != 'Backstage passes to a TAFKAL80ETC concert') {
if ($this->quality > 0) {
if ($this->name != 'Sulfuras, Hand of Ragnaros') {
$this->quality = $this->quality - 1;
}
}
} else {
if ($this->quality < 50) {
$this->quality = $this->quality + 1;
if ($this->name == 'Backstage passes to a TAFKAL80ETC concert') {
if ($this->sellIn < 11) {
if ($this->quality < 50) {
$this->quality = $this->quality + 1;
}
}
if ($this->sellIn < 6) {
if ($this->quality < 50) {
$this->quality = $this->quality + 1;
}
}
}
}
}
if ($this->name != 'Sulfuras, Hand of Ragnaros') {
$this->sellIn = $this->sellIn - 1;
}
if ($this->sellIn < 0) {
if ($this->name != 'Aged Brie') {
if ($this->name != 'Backstage passes to a TAFKAL80ETC concert') {
if ($this->quality > 0) {
if ($this->name != 'Sulfuras, Hand of Ragnaros') {
$this->quality = $this->quality - 1;
}
}
} else {
$this->quality = $this->quality - $this->quality;
}
} else {
if ($this->quality < 50) {
$this->quality = $this->quality + 1;
}
}
}
}
}
to
<?php
namespace App;
class GildedRose
{
private static $items = [
'normal' => Item::class,
'Aged Brie' => Brie::class,
'Sulfuras, Hand of Ragnaros' => Sulfuras::class,
'Backstage passes to a TAFKAL80ETC concert' => BackstagePasses::class,
'Conjured Mana Cake' => Conjured::class,
];
public static function of($name, $quality, $sellIn)
{
if (! array_key_exists($name, self::$items)) {
throw new \\InvalidArgumentException('Item type does not exist.');
}
$class = self::$items[$name];
return new $class($quality, $sellIn);
}
}
<?php
namespace App;
class Item {
public $sellIn;
public $quality;
public function __construct($quality, $sellIn) {
$this->sellIn = $sellIn;
$this->quality = $quality;
}
public function tick() {
$this->sellIn-= 1;
$this->quality-= 1;
if ($this->sellIn <= 0 && $this->quality > 0) {
$this->quality -= 1;
}
if ($this->quality <= 0) {
$this->quality = 0;
}
}
}
<?php
namespace App;
class Brie extends Item {
public function tick() {
$this->sellIn -= 1;
$this->quality += 1;
if ($this->sellIn <= 0) {
$this->quality += 1;
}
if ($this->quality > 50) {
$this->quality = 50;
}
}
}
<?php
namespace App;
class Sulfuras extends Item {
public function tick() {
}
}
<?php
namespace App;
class BackstagePasses extends Item {
public function tick() {
$this->quality += 1;
if ($this->sellIn <= 10) {
$this->quality += 1;
}
if ($this->sellIn <= 5) {
$this->quality += 1;
}
if ($this->sellIn <= 0) {
$this->quality = 0;
}
if ($this->quality > 50) {
$this->quality = 50;
}
$this->sellIn-= 1;
}
}
<?php
namespace App;
class Conjured extends Item {
public function tick() {
$this->sellIn-= 1;
$this->quality-= 2;
if ($this->sellIn <= 0 && $this->quality > 0) {
$this->quality -= 2;
}
if ($this->quality <= 0) {
$this->quality = 0;
}
}
}
99 Bottles Kata
<?php
namespace App;
class Song {
public function sing() {
return $this->verses(99,0);
}
public function verses($start, $end) {
return implode("\\n", array_map(
fn($number) => $this->verse($number),
range($start, $end)
));
}
public function verse($number) {
switch ($number) {
case 2:
return
"2 bottles of beer on the wall\\n".
"2 bottles of beer\\n".
"Take one down and pass it around\\n".
"1 bottle of beer on the wall\\n";
case 1:
return
"1 bottle of beer on the wall\\n".
"1 bottle of beer\\n".
"Take one down and pass it around\\n".
"No more bottles of beer on the wall\\n";
case 0:
return
"No more bottles of beer on the wall\\n".
"No more bottles of beer\\n".
"Go to the store and buy some more\\n".
"99 bottles of beer on the wall";
default:
return
$number . " bottles of beer on the wall\\n".
$number . " bottles of beer\\n".
"Take one down and pass it around\\n".
$number - 1 . " bottles of beer on the wall\\n";
}
}
}
<?php
use App\\Song;
use PHPUnit\\Framework\\TestCase;
class SongTest extends TestCase
{
/**
*
* @test
* @covers \\src\\Song
*/
function ninety_nine_bottle_first() {
$expected = <<<EOT
99 bottles of beer on the wall
99 bottles of beer
Take one down and pass it around
98 bottles of beer on the wall
EOT;
$result = (new Song)->verse(99);
$this->assertEquals($expected, $result);
}
/**
*
* @test
* @covers \\src\\Song
*/
function ninety_eight_bottle_verse() {
$expected = <<<EOT
98 bottles of beer on the wall
98 bottles of beer
Take one down and pass it around
97 bottles of beer on the wall
EOT;
$result = (new Song)->verse(98);
$this->assertEquals($expected, $result);
}
/**
*
* @test
* @covers \\src\\Song
*/
function two_bottle_verse() {
$expected = <<<EOT
2 bottles of beer on the wall
2 bottles of beer
Take one down and pass it around
1 bottle of beer on the wall
EOT;
$result = (new Song)->verse(2);
$this->assertEquals($expected, $result);
}
/**
*
* @test
* @covers \\src\\Song
*/
function one_bottle_verse() {
$expected = <<<EOT
1 bottle of beer on the wall
1 bottle of beer
Take one down and pass it around
No more bottles of beer on the wall
EOT;
$result = (new Song)->verse(1);
$this->assertEquals($expected, $result);
}
/**
*
* @test
* @covers \\src\\Song
*/
function no_more_bottle_verse() {
$expected = <<<EOT
No more bottles of beer on the wall
No more bottles of beer
Go to the store and buy some more
99 bottles of beer on the wall
EOT;
$result = (new Song)->verse(0);
$this->assertEquals($expected, $result);
}
/**
*
* @test
* @covers \\src\\Song
*/
function it_gets_the_lyrics() {
//$this->assertEquals(true, true);
$expected = file_get_contents(__DIR__.'/stubs/lyrics.stub');
$result = (new Song)->sing();
$this->assertEquals($expected, $result);
}
}