Add ItemBuilders hierarchy to enforce building valid items

Also updated unit tests:
- Added unit tests for the new ItemBuilders classes
- Refactored existing tests to use the new builders
This commit is contained in:
Sarah Ashri 2024-03-15 11:46:55 +10:00
parent eb741a95c5
commit d076fc9ba4
8 changed files with 192 additions and 72 deletions

View File

@ -0,0 +1,13 @@
namespace GildedRoseKata;
public abstract class AbstractItemBuilder(string name, int sellIn, int quality)
{
protected readonly string Name = name;
protected readonly int SellIn = sellIn;
protected readonly int Quality = quality;
public virtual Item Build()
{
return new() { Name = Name, SellIn = SellIn, Quality = Quality };
}
}

View File

@ -0,0 +1,23 @@
using System;
namespace GildedRoseKata;
public class ItemBuilder(string name, int sellIn, int quality) : AbstractItemBuilder(name, sellIn, quality)
{
public override Item Build()
{
if (Quality < ItemQuality.MinQuality || Quality > ItemQuality.MaxQuality)
{
throw new ArgumentException("An item's quality must be >=0 and <=50");
}
if (ItemType.IsLegendaryItem(Name))
{
throw new ArgumentException("Legendary Items cannot be constructed using this builder");
}
return base.Build();
}
}

View File

@ -0,0 +1,17 @@
using System;
namespace GildedRoseKata;
public class LegendaryItemBuilder(string name, int sellIn)
: AbstractItemBuilder(name, sellIn, ItemQuality.LegendaryItemQuality)
{
public override Item Build()
{
if (!ItemType.IsLegendaryItem(Name))
{
throw new ArgumentException("Only Legendary items can be built using this builder");
}
return base.Build();
}
}

View File

@ -25,6 +25,8 @@ However, I've chosen to use the factory itself to manually manage it using a sem
11. Moved ItemType and ItemQuality to their own classes to allow re-use
12. Seperated the derived DailyUpdater classes into their own files.<br>
Now, adding a new updater won't have to change the existing DailyUpdater file.
13. Added a new ItemBuilders hierarchy to enforce building valid items (quality limits and special rules for Legendary items). Also added unit tests and refactored the existing tests to use the new builders.<br>
note: if we could change Item, this could have been implemented in the Item's constructor itself and by using Quality TinyType. However, since we can't change Item, the builders gives us a nice decoupled way to validate the items are constructed consistently.

View File

@ -0,0 +1,40 @@
using System;
using FluentAssertions;
using GildedRoseKata;
using NUnit.Framework;
namespace GildedRoseTests;
public class ItemBuilderTestFixture
{
[TestCase("Aged Brie", 5, 0)]
[TestCase("Elixir of the Mongoose", 0, 5)]
[TestCase("Backstage passes to a TAFKAL80ETC concert", -5, 39)]
public void WhenGivenValidParameters_ShouldBuildExpectedItemSuccessfully(string name, int sellIn,
int quality)
{
var item = new ItemBuilder(name, sellIn, quality).Build();
item.Name.Should().Be(name);
item.SellIn.Should().Be(sellIn);
item.Quality.Should().Be(quality);
}
[TestCase("Aged Brie", 5, -1)]
[TestCase("Elixir of the Mongoose", 0, 51)]
public void WhenGivenInvalidQuality_ShouldThrow(string name, int sellIn, int quality)
{
var action = () => new ItemBuilder(name, sellIn, quality).Build();
action.Should().Throw<Exception>().WithMessage("An item's quality must be >=0 and <=50");
}
[TestCase("Sulfuras, Hand of Ragnaros", 3, 3)]
[TestCase("something sulfuras", 3, 3)]
public void WhenGivenLegendaryItem_ShouldThrow(string name, int sellIn, int quality)
{
var action = () => new ItemBuilder(name, sellIn, quality).Build();
action.Should().Throw<Exception>().WithMessage("Legendary Items cannot be constructed using this builder");
}
}

View File

@ -0,0 +1,32 @@
using System;
using FluentAssertions;
using GildedRoseKata;
using NUnit.Framework;
namespace GildedRoseTests;
public class LegendaryItemBuilderTestFixture
{
[TestCase("Aged Brie", 3)]
[TestCase("Elixir of the Mongoose", 0)]
[TestCase("Backstage passes to a TAFKAL80ETC concert", -3)]
public void WhenGivenOtherItemTypes_ShouldThrow(string name, int sellIn)
{
var action = () => new LegendaryItemBuilder(name, sellIn).Build();
action.Should().Throw<Exception>().WithMessage("Only Legendary items can be built using this builder");
}
[TestCase("Sulfuras, Hand of Ragnaros", 3)]
[TestCase("Sulfuras, Hand of Ragnaros", 0)]
[TestCase("something sulfuras", -3)]
public void WhenGivenValidParameters_ShouldBuildExpectedItemSuccessfully(string name, int sellIn)
{
var item = new LegendaryItemBuilder(name, sellIn).Build();
item.Name.Should().Be(name);
item.SellIn.Should().Be(sellIn);
item.Quality.Should().Be(ItemQuality.LegendaryItemQuality);
}
}

View File

@ -11,31 +11,25 @@ public static class TextTestFixture
Console.WriteLine("OMGHAI!");
var 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
},
new ItemBuilder("+5 Dexterity Vest", 10,20).Build(),
new ItemBuilder("Aged Brie", 2, 0).Build(),
new ItemBuilder("Elixir of the Mongoose", 5, 7).Build(),
new LegendaryItemBuilder("Sulfuras, Hand of Ragnaros", 0).Build(),
new LegendaryItemBuilder("Sulfuras, Hand of Ragnaros", -1).Build(),
new ItemBuilder("Backstage passes to a TAFKAL80ETC concert",
15,
20
).Build(),
new ItemBuilder("Backstage passes to a TAFKAL80ETC concert",
10,
49
).Build(),
new ItemBuilder("Backstage passes to a TAFKAL80ETC concert",
5,
49
).Build(),
// this conjured item does not work properly yet
new Item {Name = "Conjured Mana Cake", SellIn = 3, Quality = 6}
new ItemBuilder("Conjured Mana Cake", 3, 6).Build()
};
var app = new GildedRose(items);

View File

@ -11,8 +11,8 @@ public class UpdateQualityTestFixture
[Test]
public void PlainItems_WhenNotExpired_Should_DecreaseSellInAndQualityByOne()
{
var items = new List<Item> { new() { Name = "item 1", SellIn = 2, Quality = 3 } };
var expectedItemsAfterTest = new List<Item> { new() { Name = "item 1", SellIn = 1, Quality = 2 } };
var items = new List<Item> { new ItemBuilder("item 1", 2, 3).Build() };
var expectedItemsAfterTest = new List<Item> { new ItemBuilder("item 1", 1, 2).Build() };
var app = new GildedRose(items);
app.UpdateQuality();
@ -24,9 +24,9 @@ public class UpdateQualityTestFixture
[Test]
public void PlainItems_WhenExpired_Should_DecreaseSellInByOneAndQualityByTwo()
{
var items = new List<Item> { new() { Name = "item 1", SellIn = 0, Quality = 5 } };
var expectedItemsAfterDay1 = new List<Item> { new() { Name = "item 1", SellIn = -1, Quality = 3 } };
var expectedItemsAfterDay2 = new List<Item> { new() { Name = "item 1", SellIn = -2, Quality = 1 } };
var items = new List<Item> { new ItemBuilder("item 1", 0, 5).Build() };
var expectedItemsAfterDay1 = new List<Item> { new ItemBuilder("item 1", -1, 3).Build() };
var expectedItemsAfterDay2 = new List<Item> { new ItemBuilder("item 1", -2, 1).Build() };
var app = new GildedRose(items);
@ -43,13 +43,13 @@ public class UpdateQualityTestFixture
{
var items = new List<Item>
{
new() { Name = "item 1", SellIn = 0, Quality = 0 },
new() { Name = "item 1", SellIn = 0, Quality = 1 }
new ItemBuilder("item 1", 0, 0).Build(),
new ItemBuilder("item 1", 0, 1).Build()
};
var expectedItemsAfterTest = new List<Item>
{
new() { Name = "item 1", SellIn = -1, Quality = 0 },
new() { Name = "item 1", SellIn = -1, Quality = 0 }
new ItemBuilder("item 1", -1, 0).Build(),
new ItemBuilder("item 1", -1, 0).Build()
};
@ -63,8 +63,8 @@ public class UpdateQualityTestFixture
[Test]
public void BetterWithAgeItems_WhenNotExpired_Should_IncreaseQualityByOne()
{
var items = new List<Item> { new() { Name = "Aged Brie", SellIn = 1, Quality = 0 } };
var expectedItemsAfterTest = new List<Item> { new() { Name = "Aged Brie", SellIn = 0, Quality = 1 } };
var items = new List<Item> { new ItemBuilder("Aged Brie", 1, 0).Build() };
var expectedItemsAfterTest = new List<Item> { new ItemBuilder("Aged Brie", 0, 1).Build() };
var app = new GildedRose(items);
app.UpdateQuality();
@ -75,8 +75,8 @@ public class UpdateQualityTestFixture
[Test]
public void BetterWithAgeItems_WhenExpired_Should_IncreaseQualityByTwo()
{
var items = new List<Item> { new() { Name = "aged Brie", SellIn = 0, Quality = 1 } };
var expectedItemsAfterTest = new List<Item> { new() { Name = "aged Brie", SellIn = -1, Quality = 3 } };
var items = new List<Item> { new ItemBuilder("aged Brie", 0, 1).Build() };
var expectedItemsAfterTest = new List<Item> { new ItemBuilder("aged Brie", -1, 3).Build() };
var app = new GildedRose(items);
app.UpdateQuality();
@ -88,9 +88,9 @@ public class UpdateQualityTestFixture
[Test]
public void ItemsQuality_Should_NeverIncreaseAbove50()
{
var items = new List<Item> { new() { Name = "Aged Brie", SellIn = 1, Quality = 49 } };
var expectedItemsAfterDay1 = new List<Item> { new() { Name = "Aged Brie", SellIn = 0, Quality = 50 } };
var expectedItemsAfterDay2 = new List<Item> { new() { Name = "Aged Brie", SellIn = -1, Quality = 50 } };
var items = new List<Item> { new ItemBuilder("Aged Brie", 1, 49).Build() };
var expectedItemsAfterDay1 = new List<Item> { new ItemBuilder("Aged Brie", 0, 50).Build() };
var expectedItemsAfterDay2 = new List<Item> { new ItemBuilder("Aged Brie", -1, 50).Build() };
var app = new GildedRose(items);
@ -107,13 +107,13 @@ public class UpdateQualityTestFixture
{
var items = new List<Item>
{
new() { Name = "Sulfuras, Hand of Ragnaros", SellIn = 3, Quality = 3 },
new() { Name = "sulfuras", SellIn = 3, Quality = 31 }
new LegendaryItemBuilder("Sulfuras, Hand of Ragnaros", 3).Build(),
new LegendaryItemBuilder("sulfuras", 3).Build()
};
var expectedItemsAfterTest = new List<Item>
{
new() { Name = "Sulfuras, Hand of Ragnaros", SellIn = 3, Quality = 3 },
new() { Name = "sulfuras", SellIn = 3, Quality = 31 }
new LegendaryItemBuilder("Sulfuras, Hand of Ragnaros", 3).Build(),
new LegendaryItemBuilder("sulfuras", 3).Build()
};
var app = new GildedRose(items);
@ -123,11 +123,10 @@ public class UpdateQualityTestFixture
}
[Test]
[Ignore("not implemented yet - Legendary Items should not take quality (constant at 80)")]
public void LegendaryItems_Quality_IsConstant80()
{
var items = new List<Item> { new() { Name = "something sulfuras something", SellIn = 3 } };
var expectedItemsAfterTest = new List<Item> { new() { Name = "something sulfuras something", SellIn = 3, Quality = 80 } };
var items = new List<Item> { new LegendaryItemBuilder("something sulfuras something", 3).Build() };
var expectedItemsAfterTest = new List<Item> { new LegendaryItemBuilder("something sulfuras something", 3).Build() };
var app = new GildedRose(items);
@ -141,13 +140,13 @@ public class UpdateQualityTestFixture
{
var items = new List<Item>
{
new() { Name = "Backstage passes to a TAFKAL80ETC concert", SellIn = 11, Quality = 4 },
new() { Name = "backstage passes to some other show", SellIn = 20, Quality = 40 }
new ItemBuilder("Backstage passes to a TAFKAL80ETC concert", 11, 4).Build(),
new ItemBuilder("backstage passes to some other show", 20, 40).Build()
};
var expectedItemsAfterTest = new List<Item>
{
new() { Name = "Backstage passes to a TAFKAL80ETC concert", SellIn = 10, Quality = 5 },
new() { Name = "backstage passes to some other show", SellIn = 19, Quality = 41 }
new ItemBuilder("Backstage passes to a TAFKAL80ETC concert", 10, 5).Build(),
new ItemBuilder("backstage passes to some other show", 19, 41).Build()
};
var app = new GildedRose(items);
@ -162,13 +161,13 @@ public class UpdateQualityTestFixture
{
var items = new List<Item>
{
new() { Name = "backstage passes to a TAFKAL80ETC concert", SellIn = 10, Quality = 4 },
new() { Name = "Backstage passes to some other show", SellIn = 6, Quality = 41 }
new ItemBuilder("backstage passes to a TAFKAL80ETC concert", 10, 4).Build(),
new ItemBuilder("Backstage passes to some other show", 6, 41).Build()
};
var expectedItemsAfterTest = new List<Item>
{
new() { Name = "backstage passes to a TAFKAL80ETC concert", SellIn = 9, Quality = 6 },
new() { Name = "Backstage passes to some other show", SellIn = 5, Quality = 43 }
new ItemBuilder("backstage passes to a TAFKAL80ETC concert", 9, 6).Build(),
new ItemBuilder("Backstage passes to some other show", 5, 43).Build()
};
var app = new GildedRose(items);
@ -182,13 +181,13 @@ public class UpdateQualityTestFixture
{
var items = new List<Item>
{
new() { Name = "Backstage passes to some other show", SellIn = 5, Quality = 4 },
new() { Name = "Backstage passes to a TAFKAL80ETC concert", SellIn = 1, Quality = 41 }
new ItemBuilder("Backstage passes to some other show", 5, 4).Build(),
new ItemBuilder("Backstage passes to a TAFKAL80ETC concert", 1, 41).Build()
};
var expectedItemsAfterTest = new List<Item>
{
new() { Name = "Backstage passes to some other show", SellIn = 4, Quality = 7 },
new() { Name = "Backstage passes to a TAFKAL80ETC concert", SellIn = 0, Quality = 44 }
new ItemBuilder("Backstage passes to some other show", 4, 7).Build(),
new ItemBuilder("Backstage passes to a TAFKAL80ETC concert", 0, 44).Build()
};
var app = new GildedRose(items);
@ -202,21 +201,21 @@ public class UpdateQualityTestFixture
{
var items = new List<Item>
{
new() { Name = "Backstage passes", SellIn = 15, Quality = 49 },
new() { Name = "Backstage passes to some other show", SellIn = 10, Quality = 49 },
new() { Name = "Backstage passes to a TAFKAL80ETC concert", SellIn = 5, Quality = 48 }
new ItemBuilder("Backstage passes", 15, 49).Build(),
new ItemBuilder("Backstage passes to some other show", 10,49).Build(),
new ItemBuilder("Backstage passes to a TAFKAL80ETC concert", 5, 48).Build()
};
var expectedItemsAfterDay1 = new List<Item>
{
new() { Name = "Backstage passes", SellIn = 14, Quality = 50 },
new() { Name = "Backstage passes to some other show", SellIn = 9, Quality = 50 },
new() { Name = "Backstage passes to a TAFKAL80ETC concert", SellIn = 4, Quality = 50 }
new ItemBuilder("Backstage passes", 14, 50).Build(),
new ItemBuilder("Backstage passes to some other show", 9, 50).Build(),
new ItemBuilder("Backstage passes to a TAFKAL80ETC concert", 4, 50).Build()
};
var expectedItemsAfterDay2 = new List<Item>
{
new() { Name = "Backstage passes", SellIn = 13, Quality = 50 },
new() { Name = "Backstage passes to some other show", SellIn = 8, Quality = 50 },
new() { Name = "Backstage passes to a TAFKAL80ETC concert", SellIn = 3, Quality = 50 }
new ItemBuilder("Backstage passes", 13, 50).Build(),
new ItemBuilder("Backstage passes to some other show", 8, 50).Build(),
new ItemBuilder("Backstage passes to a TAFKAL80ETC concert", 3, 50).Build()
};
var app = new GildedRose(items);
@ -233,13 +232,13 @@ public class UpdateQualityTestFixture
{
var items = new List<Item>
{
new() { Name = "Backstage passes to a TAFKAL80ETC concert", SellIn = 0, Quality = 4 },
new() { Name = "Another type of backstage passes", SellIn = 0, Quality = 4 }
new ItemBuilder("Backstage passes to a TAFKAL80ETC concert", 0, 4).Build(),
new ItemBuilder("Another type of backstage passes", 0, 4).Build()
};
var expectedItemsAfterTest = new List<Item>
{
new() { Name = "Backstage passes to a TAFKAL80ETC concert", SellIn = -1, Quality = 0 },
new() { Name = "Another type of backstage passes", SellIn = -1, Quality = 0 }
new ItemBuilder("Backstage passes to a TAFKAL80ETC concert", -1, 0).Build(),
new ItemBuilder("Another type of backstage passes", -1, 0).Build()
};
var app = new GildedRose(items);