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 11. Moved ItemType and ItemQuality to their own classes to allow re-use
12. Seperated the derived DailyUpdater classes into their own files.<br> 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. 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!"); Console.WriteLine("OMGHAI!");
var items = new List<Item>{ var items = new List<Item>{
new Item {Name = "+5 Dexterity Vest", SellIn = 10, Quality = 20}, new ItemBuilder("+5 Dexterity Vest", 10,20).Build(),
new Item {Name = "Aged Brie", SellIn = 2, Quality = 0}, new ItemBuilder("Aged Brie", 2, 0).Build(),
new Item {Name = "Elixir of the Mongoose", SellIn = 5, Quality = 7}, new ItemBuilder("Elixir of the Mongoose", 5, 7).Build(),
new Item {Name = "Sulfuras, Hand of Ragnaros", SellIn = 0, Quality = 80}, new LegendaryItemBuilder("Sulfuras, Hand of Ragnaros", 0).Build(),
new Item {Name = "Sulfuras, Hand of Ragnaros", SellIn = -1, Quality = 80}, new LegendaryItemBuilder("Sulfuras, Hand of Ragnaros", -1).Build(),
new Item new ItemBuilder("Backstage passes to a TAFKAL80ETC concert",
{ 15,
Name = "Backstage passes to a TAFKAL80ETC concert", 20
SellIn = 15, ).Build(),
Quality = 20 new ItemBuilder("Backstage passes to a TAFKAL80ETC concert",
}, 10,
new Item 49
{ ).Build(),
Name = "Backstage passes to a TAFKAL80ETC concert", new ItemBuilder("Backstage passes to a TAFKAL80ETC concert",
SellIn = 10, 5,
Quality = 49 49
}, ).Build(),
new Item
{
Name = "Backstage passes to a TAFKAL80ETC concert",
SellIn = 5,
Quality = 49
},
// this conjured item does not work properly yet // 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); var app = new GildedRose(items);

View File

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