Merge branch 'master' of github.com:emilybache/Refactoring-Katas

This commit is contained in:
Emily Bache 2013-04-08 10:39:31 +02:00
commit d3c89db5c9
18 changed files with 587 additions and 166 deletions

View File

@ -22,7 +22,7 @@ CPPFLAGS += -I$(CPPUTEST_HOME)/include
# Flags passed to the C++ compiler.
CFLAGS += -g -Wall -Wextra
LD_LIBRARIES = -L$(CPPUTEST_HOME)/lib -lCppUTest -lCppUTestExt
LD_LIBRARIES = -L$(CPPUTEST_HOME)/lib -lCppUTest
# All tests produced by this Makefile. Remember to add new tests you
# created to the list.

View File

@ -1,3 +1,4 @@
package com.gildedrose;
class GildedRose {
Item[] items;

View File

@ -1,3 +1,4 @@
package com.gildedrose;
import static org.junit.Assert.*;
import org.junit.Test;

View File

@ -1,3 +1,4 @@
package com.gildedrose;
public class Item {
public String name;

View File

@ -1,3 +1,4 @@
package com.gildedrose;
public class TexttestFixture {
public static void main(String[] args) {

View File

@ -1,6 +1,12 @@
This Kata was originally created by Terry Hughes (http://twitter.com/#!/TerryHughes). It is already on GitHub [here](https://github.com/NotMyself/GildedRose). I could have forked it again, but I thought other language users might not want to download a whole C# project environment. In this repository are starting code samples for Java, Python, Ruby, Smalltalk, C#, C and C++.
This Kata was originally created by Terry Hughes (http://twitter.com/#!/TerryHughes). It is already on GitHub [here](https://github.com/NotMyself/GildedRose). See also [Bobby Johnson's description of the kata](http://iamnotmyself.com/2011/02/13/refactor-this-the-gilded-rose-kata/).
See also http://iamnotmyself.com/2011/02/13/refactor-this-the-gilded-rose-kata/
I translated the original C# into a few other languages, (with a little help from my friends!), and slightly changed the starting position. This means I've actually done a small amount of refactoring already compared with the original form of the kata, and made it easier to get going with writing tests by giving you one failing unit test to start with. I also added test fixtures for Text-Based approval testing with TextTest (see [the TextTests](https://github.com/emilybache/Refactoring-Katas/tree/master/GildedRose/texttests))
As Bobby Johnson points out in his article ["Why Most Solutions to Gilded Rose Miss The Bigger Picture"](http://iamnotmyself.com/2012/12/07/why-most-solutions-to-gilded-rose-miss-the-bigger-picture/), it'll actually give you
better practice at handling a legacy code situation if you do this Kata in the original C#. However, I think this kata
is also really useful for practicing writing good tests using different frameworks and approaches, and the small changes I've made help with that. I think it's also interesting to compare what the refactored code and tests look like in different programming languages.
I wrote this article ["Writing Good Tests for the Gilded Rose Kata"](http://emilybache.blogspot.se/2013/03/writing-good-tests-for-gilded-rose-kata.html) about how you could use this kata in a [coding dojo](https://leanpub.com/codingdojohandbook).
## How to use this Kata
@ -10,17 +16,6 @@ You could write some unit tests yourself, using the requirements to identify sui
Alternatively, use the "Text-Based" tests provided in this repository. (Read more about that in the next section)
I've also set this kata up on [cyber-dojo](http://cyber-dojo.com) for several languages, so you can get going really quickly:
- [Cucumber, Java](http://cyber-dojo.com/forker/fork/0F82D4BA89?avatar=gorilla&tag=45) - for this one I've also written some step definitions for you
- [JUnit, Java](http://cyber-dojo.com/forker/fork/751DD02C4C?avatar=snake&tag=4)
- [C#](http://cyber-dojo.com/forker/fork/107907AD1E?avatar=alligator&tag=13)
- [Ruby](http://cyber-dojo.com/forker/fork/A8943EAF92?avatar=hippo&tag=9)
- [RSpec, Ruby](http://cyber-dojo.com/forker/fork/8E58B0AD16?avatar=raccoon&tag=3)
- [Python](http://cyber-dojo.com/forker/fork/297041AA7A?avatar=lion&tag=4)
Whichever testing approach you choose, the idea of the exercise is to do some deliberate practice, and improve your skills at designing test cases and refactoring. The idea is not to re-write the code from scratch, but rather to practice designing tests, taking small steps, running the tests often, and incrementally improving the design.
## Text-Based Approval Testing
@ -33,3 +28,13 @@ Typically a piece of legacy code may not produce suitable textual output from th
The Text-Based tests in this repository are designed to be used with the tool "TextTest" (http://texttest.org). This tool helps you to organize and run text-based tests. There is more information in the README file in the "texttests" subdirectory.
## Get going quickly using Cyber-Dojo
I've also set this kata up on [cyber-dojo](http://cyber-dojo.com) for several languages, so you can get going really quickly:
- [JUnit, Java](http://cyber-dojo.com/forker/fork/751DD02C4C?avatar=snake&tag=4)
- [C#](http://cyber-dojo.com/forker/fork/107907AD1E?avatar=alligator&tag=13)
- [Ruby](http://cyber-dojo.com/forker/fork/A8943EAF92?avatar=hippo&tag=9)
- [RSpec, Ruby](http://cyber-dojo.com/forker/fork/8E58B0AD16?avatar=raccoon&tag=3)
- [Python](http://cyber-dojo.com/forker/fork/297041AA7A?avatar=lion&tag=4)
- [Cucumber, Java](http://cyber-dojo.com/forker/fork/0F82D4BA89?avatar=gorilla&tag=45) - for this one I've also written some step definitions for you

View File

@ -0,0 +1,28 @@
using System;
using System.IO;
using System.Text;
using GildedRose;
using NUnit.Framework;
using ApprovalTests;
using ApprovalTests.Reporters;
namespace GildedRoseTests
{
[TestFixture]
[UseReporter(typeof(NUnitReporter))]
public class ApprovalTest
{
[Test]
public void ThirtyDays()
{
StringBuilder fakeoutput = new StringBuilder();
Console.SetOut(new StringWriter(fakeoutput));
Console.SetIn(new StringReader("a\n"));
Program.Main(new string[] { });
String output = fakeoutput.ToString();
Approvals.Verify(output);
}
}
}

View File

@ -0,0 +1,100 @@
using System.Collections.Generic;
namespace GildedRose
{
class GildedRose
{
IList<Item> Items;
public GildedRose(IList<Item> Items)
{
this.Items = Items;
}
public void UpdateQuality()
{
for (var i = 0; i < Items.Count; i++)
{
if (Items[i].Name != "Aged Brie" && Items[i].Name != "Backstage passes to a TAFKAL80ETC concert")
{
if (Items[i].Quality > 0)
{
if (Items[i].Name != "Sulfuras, Hand of Ragnaros")
{
Items[i].Quality = Items[i].Quality - 1;
}
}
}
else
{
if (Items[i].Quality < 50)
{
Items[i].Quality = Items[i].Quality + 1;
if (Items[i].Name == "Backstage passes to a TAFKAL80ETC concert")
{
if (Items[i].SellIn < 11)
{
if (Items[i].Quality < 50)
{
Items[i].Quality = Items[i].Quality + 1;
}
}
if (Items[i].SellIn < 6)
{
if (Items[i].Quality < 50)
{
Items[i].Quality = Items[i].Quality + 1;
}
}
}
}
}
if (Items[i].Name != "Sulfuras, Hand of Ragnaros")
{
Items[i].SellIn = Items[i].SellIn - 1;
}
if (Items[i].SellIn < 0)
{
if (Items[i].Name != "Aged Brie")
{
if (Items[i].Name != "Backstage passes to a TAFKAL80ETC concert")
{
if (Items[i].Quality > 0)
{
if (Items[i].Name != "Sulfuras, Hand of Ragnaros")
{
Items[i].Quality = Items[i].Quality - 1;
}
}
}
else
{
Items[i].Quality = Items[i].Quality - Items[i].Quality;
}
}
else
{
if (Items[i].Quality < 50)
{
Items[i].Quality = Items[i].Quality + 1;
}
}
}
}
}
}
public class Item
{
public string Name { get; set; }
public int SellIn { get; set; }
public int Quality { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System;
using NUnit.Framework;
using System.Collections.Generic;
namespace GildedRose
{
[TestFixture()]
public class GildedRoseTest
{
[Test()]
public void foo() {
IList<Item> Items = new List<Item> { new Item{Name = "foo", SellIn = 0, Quality = 0} };
GildedRose app = new GildedRose(Items);
app.UpdateQuality();
Assert.AreEqual("fixme", Items[0].Name);
}
}
}

View File

@ -1,147 +0,0 @@
using System.Collections.Generic;
namespace GildedRose.Console
{
class Program
{
IList<Item> Items;
static void Main(string[] args)
{
System.Console.WriteLine("OMGHAI!");
var app = new Program()
{
Items = new List<Item>
{
new Item {Name = "+5 Dexterity Vest", SellIn = 10, Quality = 20},
new Item {Name = "Aged Brie", SellIn = 2, Quality = 0},
new Item {Name = "Elixir of the Mongoose", SellIn = 5, Quality = 7},
new Item {Name = "Sulfuras, Hand of Ragnaros", SellIn = 0, Quality = 80},
new Item {Name = "Sulfuras, Hand of Ragnaros", SellIn = -1, Quality = 80},
new Item
{
Name = "Backstage passes to a TAFKAL80ETC concert",
SellIn = 15,
Quality = 20
},
new Item
{
Name = "Backstage passes to a TAFKAL80ETC concert",
SellIn = 10,
Quality = 49
},
new Item
{
Name = "Backstage passes to a TAFKAL80ETC concert",
SellIn = 5,
Quality = 49
},
// this conjured item does not work properly yet
new Item {Name = "Conjured Mana Cake", SellIn = 3, Quality = 6}
}
};
for (var i = 0; i < 31; i++)
{
System.Console.WriteLine("-------- day " + i + " --------");
System.Console.WriteLine("name, sellIn, quality");
for (var j = 0; j < app.Items.Count; j++)
{
System.Console.WriteLine(app.Items[j].Name + ", " + app.Items[j].SellIn + ", " + app.Items[j].Quality);
}
System.Console.WriteLine("");
app.UpdateQuality();
}
System.Console.ReadKey();
}
public void UpdateQuality()
{
for (var i = 0; i < Items.Count; i++)
{
if (Items[i].Name != "Aged Brie" && Items[i].Name != "Backstage passes to a TAFKAL80ETC concert")
{
if (Items[i].Quality > 0)
{
if (Items[i].Name != "Sulfuras, Hand of Ragnaros")
{
Items[i].Quality = Items[i].Quality - 1;
}
}
}
else
{
if (Items[i].Quality < 50)
{
Items[i].Quality = Items[i].Quality + 1;
if (Items[i].Name == "Backstage passes to a TAFKAL80ETC concert")
{
if (Items[i].SellIn < 11)
{
if (Items[i].Quality < 50)
{
Items[i].Quality = Items[i].Quality + 1;
}
}
if (Items[i].SellIn < 6)
{
if (Items[i].Quality < 50)
{
Items[i].Quality = Items[i].Quality + 1;
}
}
}
}
}
if (Items[i].Name != "Sulfuras, Hand of Ragnaros")
{
Items[i].SellIn = Items[i].SellIn - 1;
}
if (Items[i].SellIn < 0)
{
if (Items[i].Name != "Aged Brie")
{
if (Items[i].Name != "Backstage passes to a TAFKAL80ETC concert")
{
if (Items[i].Quality > 0)
{
if (Items[i].Name != "Sulfuras, Hand of Ragnaros")
{
Items[i].Quality = Items[i].Quality - 1;
}
}
}
else
{
Items[i].Quality = Items[i].Quality - Items[i].Quality;
}
}
else
{
if (Items[i].Quality < 50)
{
Items[i].Quality = Items[i].Quality + 1;
}
}
}
}
}
}
public class Item
{
public string Name { get; set; }
public int SellIn { get; set; }
public int Quality { get; set; }
}
}

View File

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
namespace GildedRose
{
class Program
{
public static void Main(string[] args)
{
System.Console.WriteLine("OMGHAI!");
IList<Item> Items = new List<Item>{
new Item {Name = "+5 Dexterity Vest", SellIn = 10, Quality = 20},
new Item {Name = "Aged Brie", SellIn = 2, Quality = 0},
new Item {Name = "Elixir of the Mongoose", SellIn = 5, Quality = 7},
new Item {Name = "Sulfuras, Hand of Ragnaros", SellIn = 0, Quality = 80},
new Item {Name = "Sulfuras, Hand of Ragnaros", SellIn = -1, Quality = 80},
new Item
{
Name = "Backstage passes to a TAFKAL80ETC concert",
SellIn = 15,
Quality = 20
},
new Item
{
Name = "Backstage passes to a TAFKAL80ETC concert",
SellIn = 10,
Quality = 49
},
new Item
{
Name = "Backstage passes to a TAFKAL80ETC concert",
SellIn = 5,
Quality = 49
},
// this conjured item does not work properly yet
new Item {Name = "Conjured Mana Cake", SellIn = 3, Quality = 6}
};
var app = new GildedRose(Items);
for (var i = 0; i < 31; i++)
{
System.Console.WriteLine("-------- day " + i + " --------");
System.Console.WriteLine("name, sellIn, quality");
for (var j = 0; j < Items.Count; j++)
{
System.Console.WriteLine(Items[j].Name + ", " + Items[j].SellIn + ", " + Items[j].Quality);
}
System.Console.WriteLine("");
app.UpdateQuality();
}
}
}
}

View File

@ -26,4 +26,4 @@ if __name__ == "__main__":
for item in items:
print(item)
print("")
update_quality(items)
GildedRose(items).update_quality()

2
GildedRose/ruby/.rspec Normal file
View File

@ -0,0 +1,2 @@
--colour
--format nested

View File

@ -21,7 +21,7 @@ if ARGV.size > 0
days = ARGV[0].to_i + 1
end
gilded_rose = GildedRose.new
gilded_rose = GildedRose.new items
(0...days).each do |day|
puts "-------- day #{day} --------"
puts "name, sellIn, quality"
@ -29,5 +29,5 @@ gilded_rose = GildedRose.new
puts item
end
puts ""
gilded_rose.update_quality(items)
gilded_rose.update_quality
end

View File

@ -4,7 +4,7 @@ full_name:Gilded Rose Refactoring Kata
default_checkout:/Users/emily/training_materials/Refactoring-Katas/GildedRose
# Settings for the Java version
executable:TexttestFixture
executable:com.gildedrose.TexttestFixture
interpreter:java
# note you'll also need to update the file environment.gr with your classpath if you keep your classfiles somewhere unusual
@ -20,7 +20,7 @@ interpreter:java
#interpreter:ruby
# Settings for the C# version
#executable:${TEXTTEST_CHECKOUT}/csharp/Program.exe
#executable:${TEXTTEST_CHECKOUT}/GildedRose.exe
# turn on one of these if you prefer them to notepad or emacs.
[view_program]

View File

@ -6,6 +6,8 @@ The other language translations have been contributed by:
Python: Emily Bache
Ruby: Kim Persson and Lennart Fridén
## Kata: Yahtzee rules
The game of yahtzee is a simple dice game. Each player

View File

@ -0,0 +1,94 @@
require_relative 'yahtzee'
require 'test/unit'
class YahtzeeTest < Test::Unit::TestCase
def test_chance_scores_sum_of_all_dice
expected = 15
actual = Yahtzee.chance(2,3,4,5,1)
assert expected == actual
assert 16 == Yahtzee.chance(3,3,4,5,1)
end
def test_yahtzee_scores_50
expected = 50
actual = Yahtzee.yahtzee([4,4,4,4,4])
assert expected == actual
assert 50 == Yahtzee.yahtzee([6,6,6,6,6])
assert 0 == Yahtzee.yahtzee([6,6,6,6,3])
end
def test_1s
assert Yahtzee.ones(1,2,3,4,5) == 1
assert 2 == Yahtzee.ones(1,2,1,4,5)
assert 0 == Yahtzee.ones(6,2,2,4,5)
assert 4 == Yahtzee.ones(1,2,1,1,1)
end
def test_2s
assert Yahtzee.twos(1,2,3,2,6) == 4
assert Yahtzee.twos(2,2,2,2,2) == 10
end
def test_threes
assert 6 == Yahtzee.threes(1,2,3,2,3)
assert 12 == Yahtzee.threes(2,3,3,3,3)
end
def test_fours_test
assert 12 == Yahtzee.new(4,4,4,5,5).fours
assert 8 == Yahtzee.new(4,4,5,5,5).fours
assert 4 == Yahtzee.new(4,5,5,5,5).fours
end
def test_fives()
assert 10 == Yahtzee.new(4,4,4,5,5).fives()
assert 15 == Yahtzee.new(4,4,5,5,5).fives()
assert 20 == Yahtzee.new(4,5,5,5,5).fives()
end
def test_sixes_test
assert 0 == Yahtzee.new(4,4,4,5,5).sixes()
assert 6 == Yahtzee.new(4,4,6,5,5).sixes()
assert 18 == Yahtzee.new(6,5,6,6,5).sixes()
end
def test_one_pair
assert 6 == Yahtzee.score_pair(3,4,3,5,6)
assert 10 == Yahtzee.score_pair(5,3,3,3,5)
assert 12 == Yahtzee.score_pair(5,3,6,6,5)
end
def test_two_Pair
assert_equal 16, Yahtzee.two_pair(3,3,5,4,5)
assert_equal 0, Yahtzee.two_pair(3,3,5,5,5)
end
def test_three_of_a_kind()
assert 9 == Yahtzee.three_of_a_kind(3,3,3,4,5)
assert 15 == Yahtzee.three_of_a_kind(5,3,5,4,5)
assert 0 == Yahtzee.three_of_a_kind(3,3,3,3,5)
end
def test_four_of_a_knd
assert 12 == Yahtzee.four_of_a_kind(3,3,3,3,5)
assert 20 == Yahtzee.four_of_a_kind(5,5,5,4,5)
assert 0 == Yahtzee.three_of_a_kind(3,3,3,3,3)
end
def test_smallStraight()
assert 15 == Yahtzee.smallStraight(1,2,3,4,5)
assert 15 == Yahtzee.smallStraight(2,3,4,5,1)
assert 0 == Yahtzee.smallStraight(1,2,2,4,5)
end
def test_largeStraight
assert 20 == Yahtzee.largeStraight(6,2,3,4,5)
assert 20 == Yahtzee.largeStraight(2,3,4,5,6)
assert 0 == Yahtzee.largeStraight(1,2,2,4,5)
end
def test_fullHouse()
assert 18 == Yahtzee.fullHouse(6,2,2,2,6)
assert 0 == Yahtzee.fullHouse(2,3,4,5,6)
end
end

256
Yahtzee/ruby/yahtzee.rb Normal file
View File

@ -0,0 +1,256 @@
class Yahtzee
def self.chance(d1, d2, d3, d4, d5)
total = 0
total += d1
total += d2
total += d3
total += d4
total += d5
return total
end
def self.yahtzee(dice)
counts = [0]*(dice.length+1)
for die in dice do
counts[die-1] += 1
end
for i in 0..counts.size do
if counts[i] == 5
return 50
end
end
return 0
end
def self.ones( d1, d2, d3, d4, d5)
sum = 0
if (d1 == 1)
sum += 1
end
if (d2 == 1)
sum += 1
end
if (d3 == 1)
sum += 1
end
if (d4 == 1)
sum += 1
end
if (d5 == 1)
sum += 1
end
sum
end
def self.twos( d1, d2, d3, d4, d5)
sum = 0
if (d1 == 2)
sum += 2
end
if (d2 == 2)
sum += 2
end
if (d3 == 2)
sum += 2
end
if (d4 == 2)
sum += 2
end
if (d5 == 2)
sum += 2
end
return sum
end
def self.threes( d1, d2, d3, d4, d5)
s = 0
if (d1 == 3)
s += 3
end
if (d2 == 3)
s += 3
end
if (d3 == 3)
s += 3
end
if (d4 == 3)
s += 3
end
if (d5 == 3)
s += 3
end
return s
end
def initialize(d1, d2, d3, d4, _5)
@dice = [0]*5
@dice[0] = d1
@dice[1] = d2
@dice[2] = d3
@dice[3] = d4
@dice[4] = _5
end
def fours
sum = 0
for at in Array 0..4
if (@dice[at] == 4)
sum += 4
end
end
return sum
end
def fives()
s = 0
i = 0
for i in (Range.new(0, @dice.size))
if (@dice[i] == 5)
s = s + 5
end
end
s
end
def sixes
sum = 0
for at in 0..@dice.length
if (@dice[at] == 6)
sum = sum + 6
end
end
return sum
end
def self.score_pair( d1, d2, d3, d4, d5)
counts = [0]*6
counts[d1-1] += 1
counts[d2-1] += 1
counts[d3-1] += 1
counts[d4-1] += 1
counts[d5-1] += 1
at = 0
(0...6).each do |at|
if (counts[6-at-1] == 2)
return (6-at)*2
end
end
return 0
end
def self.two_pair( d1, d2, d3, d4, d5)
counts = [0]*6
counts[d1-1] += 1
counts[d2-1] += 1
counts[d3-1] += 1
counts[d4-1] += 1
counts[d5-1] += 1
n = 0
score = 0
for i in Array 0..5
if (counts[6-i-1] == 2)
n = n+1
score += (6-i)
end
end
if (n == 2)
return score * 2
else
return 0
end
end
def self.four_of_a_kind( _1, _2, d3, d4, d5)
tallies = [0]*6
tallies[_1-1] += 1
tallies[_2-1] += 1
tallies[d3-1] += 1
tallies[d4-1] += 1
tallies[d5-1] += 1
for i in (0..6)
if (tallies[i] == 4)
return (i+1) * 4
end
end
return 0
end
def self.three_of_a_kind( d1, d2, d3, d4, d5)
t = [0]*6
t[d1-1] += 1
t[d2-1] += 1
t[d3-1] += 1
t[d4-1] += 1
t[d5-1] += 1
for i in [0,1,2,3,4,5]
if (t[i] == 3)
return (i+1) * 3
end
end
0
end
def self.smallStraight( d1, d2, d3, d4, d5)
tallies = [0]*6
tallies[d1-1] += 1
tallies[d2-1] += 1
tallies[d3-1] += 1
tallies[d4-1] += 1
tallies[d5-1] += 1
(tallies[0] == 1 and
tallies[1] == 1 and
tallies[2] == 1 and
tallies[3] == 1 and
tallies[4] == 1) ? 15 : 0
end
def self.largeStraight( d1, d2, d3, d4, d5)
tallies = [0]*6
tallies[d1-1] += 1
tallies[d2-1] += 1
tallies[d3-1] += 1
tallies[d4-1] += 1
tallies[d5-1] += 1
if (tallies[1] == 1 and tallies[2] == 1 and tallies[3] == 1 and tallies[4] == 1 and tallies[5] == 1)
return 20
end
return 0
end
def self.fullHouse( d1, d2, d3, d4, d5)
tallies = []
_2 = false
i = 0
_2_at = 0
_3 = false
_3_at = 0
tallies = [0]*6
tallies[d1-1] += 1
tallies[d2-1] += 1
tallies[d3-1] += 1
tallies[d4-1] += 1
tallies[d5-1] += 1
for i in Array 0..5
if (tallies[i] == 2)
_2 = true
_2_at = i+1
end
end
for i in Array 0..5
if (tallies[i] == 3)
_3 = true
_3_at = i+1
end
end
if (_2 and _3)
return _2_at * 2 + _3_at * 3
else
return 0
end
end
end