mirror of
https://github.com/emilybache/GildedRose-Refactoring-Kata.git
synced 2026-02-05 09:41:37 +00:00
Merge aa2d7dff24 into 58a991fe27
This commit is contained in:
commit
fc73c619c0
55
python/.github/workflows/lint-and-test.yml
vendored
Normal file
55
python/.github/workflows/lint-and-test.yml
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
name: Lint and Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- test/my_changes
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- test/my_changes
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r python/requirements-dev.txt
|
||||
|
||||
- name: Run Black (formatting)
|
||||
run: |
|
||||
black --check .
|
||||
|
||||
- name: Run isort (imports)
|
||||
run: |
|
||||
isort --check-only .
|
||||
|
||||
- name: Run flake8 (style)
|
||||
run: |
|
||||
flake8 .
|
||||
|
||||
- name: Run pylint (quality)
|
||||
run: |
|
||||
pylint python/gilded_rose.py
|
||||
|
||||
- name: Run mypy (type checking)
|
||||
run: |
|
||||
mypy python/gilded_rose.py
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
python -m unittest discover -s python/tests
|
||||
44
python/Documentation.md
Normal file
44
python/Documentation.md
Normal file
@ -0,0 +1,44 @@
|
||||
Gilded Rose Refactoring — Clean Code Edition (Python)
|
||||
Introduction
|
||||
This project is a clean-code refactor of the well-known Gilded Rose Kata. The goal of this refactoring effort was to apply python best practices, clean code, reduce cyclomatic complexity, and improve the maintainability and testability of the code. Key software design concepts such as the Open/Closed Principle (OCP), encapsulation, polymorphism, and modular architecture were used to restructure the core logic.
|
||||
1. Refactored `update_quality` Using the Open/Closed Principle
|
||||
Originally, the `update_quality` method in `GildedRose` contained a complex set of `if-elif` conditionals handling all item behaviors. This violated the Open/Closed Principle (OCP) by requiring changes to the method every time a new item type was added. We replaced this with a polymorphic structure, using item wrapper classes to handle item-specific behavior.
|
||||
Old style:
|
||||
if item.name == "Aged Brie":
|
||||
...
|
||||
New style:
|
||||
wrapper = item_factory(item)
|
||||
wrapper.update_quality()
|
||||
2. Introduced a Factory Function: `item_factory()`
|
||||
The factory function is responsible for returning an instance of the appropriate wrapper class based on the item name. This makes the core logic in `GildedRose` agnostic to item-specific rules and makes the system extensible without touching legacy code.
|
||||
3. Created Dedicated Classes per Item Type
|
||||
Each item behavior is now encapsulated in a dedicated class that extends a base wrapper. The classes include:
|
||||
• AgedBrieItem
|
||||
• BackstagePassItem
|
||||
• SulfurasItem
|
||||
• ConjuredItem
|
||||
• DefaultItem
|
||||
This use of polymorphism keeps the rules modular, isolated, and testable.
|
||||
4. Maintained Interface Compatibility
|
||||
The original `Item` class and `items` property were left untouched as required. This ensured backward compatibility with existing code and met the constraints defined in the kata.
|
||||
5. Restructured Codebase for Clarity
|
||||
Project structure:
|
||||
python/
|
||||
├── gilded_rose.py
|
||||
├── item_wrappers.py
|
||||
├── aged_brie.py
|
||||
├── backstage.py
|
||||
├── sulfuras.py
|
||||
├── conjured.py
|
||||
└── tests/
|
||||
6. GitHub Actions CI/CD Pipeline
|
||||
A GitHub Actions workflow was created to enforce code quality and automate tests. The workflow runs on pushes or PRs to the `main` and `test/my_changes` branches.
|
||||
Tasks include:
|
||||
• Code formatting (`black`)
|
||||
• Import sorting (`isort`)
|
||||
• Linting (`flake8`)
|
||||
• Static code analysis (`pylint`)
|
||||
• Type checking (`mypy`)
|
||||
• Unit + Approval tests (`pytest`, `approvaltests`)
|
||||
7. Tests Refactored
|
||||
Tests were restructured into logical classes for each item type, improving readability and focus. Approval testing was preserved for regression validation.
|
||||
12
python/aged_brie.py
Normal file
12
python/aged_brie.py
Normal file
@ -0,0 +1,12 @@
|
||||
from item_wrappers import ItemWrapper
|
||||
|
||||
class AgedBrieItem(ItemWrapper):
|
||||
def update(self):
|
||||
self.increase_quality()
|
||||
self.item.sell_in -= 1
|
||||
if self.item.sell_in < 0:
|
||||
self.increase_quality()
|
||||
|
||||
def increase_quality(self):
|
||||
if self.item.quality < 50:
|
||||
self.item.quality += 1
|
||||
19
python/backstage.py
Normal file
19
python/backstage.py
Normal file
@ -0,0 +1,19 @@
|
||||
from item_wrappers import ItemWrapper
|
||||
|
||||
class BackstagePassItem(ItemWrapper):
|
||||
def update(self):
|
||||
self.increase_quality()
|
||||
|
||||
if self.item.sell_in <= 10:
|
||||
self.increase_quality()
|
||||
if self.item.sell_in <= 5:
|
||||
self.increase_quality()
|
||||
|
||||
self.item.sell_in -= 1
|
||||
|
||||
if self.item.sell_in < 0:
|
||||
self.item.quality = 0
|
||||
|
||||
def increase_quality(self):
|
||||
if self.item.quality < 50:
|
||||
self.item.quality += 1
|
||||
14
python/conjured.py
Normal file
14
python/conjured.py
Normal file
@ -0,0 +1,14 @@
|
||||
from item_wrappers import ItemWrapper
|
||||
|
||||
class ConjuredItem(ItemWrapper):
|
||||
def update(self):
|
||||
self.decrease_quality()
|
||||
self.decrease_quality()
|
||||
self.item.sell_in -= 1
|
||||
if self.item.sell_in < 0:
|
||||
self.decrease_quality()
|
||||
self.decrease_quality()
|
||||
|
||||
def decrease_quality(self):
|
||||
if self.item.quality > 0:
|
||||
self.item.quality -= 1
|
||||
@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from item_wrappers import item_factory
|
||||
|
||||
class GildedRose(object):
|
||||
|
||||
@ -7,40 +8,8 @@ class GildedRose(object):
|
||||
|
||||
def update_quality(self):
|
||||
for item in self.items:
|
||||
if item.name != "Aged Brie" and item.name != "Backstage passes to a TAFKAL80ETC concert":
|
||||
if item.quality > 0:
|
||||
if item.name != "Sulfuras, Hand of Ragnaros":
|
||||
item.quality = item.quality - 1
|
||||
else:
|
||||
if item.quality < 50:
|
||||
item.quality = item.quality + 1
|
||||
if item.name == "Backstage passes to a TAFKAL80ETC concert":
|
||||
if item.sell_in < 11:
|
||||
if item.quality < 50:
|
||||
item.quality = item.quality + 1
|
||||
if item.sell_in < 6:
|
||||
if item.quality < 50:
|
||||
item.quality = item.quality + 1
|
||||
if item.name != "Sulfuras, Hand of Ragnaros":
|
||||
item.sell_in = item.sell_in - 1
|
||||
if item.sell_in < 0:
|
||||
if item.name != "Aged Brie":
|
||||
if item.name != "Backstage passes to a TAFKAL80ETC concert":
|
||||
if item.quality > 0:
|
||||
if item.name != "Sulfuras, Hand of Ragnaros":
|
||||
item.quality = item.quality - 1
|
||||
else:
|
||||
item.quality = item.quality - item.quality
|
||||
else:
|
||||
if item.quality < 50:
|
||||
item.quality = item.quality + 1
|
||||
wrapper = item_factory(item)
|
||||
wrapper.update()
|
||||
|
||||
|
||||
class Item:
|
||||
def __init__(self, name, sell_in, quality):
|
||||
self.name = name
|
||||
self.sell_in = sell_in
|
||||
self.quality = quality
|
||||
|
||||
def __repr__(self):
|
||||
return "%s, %s, %s" % (self.name, self.sell_in, self.quality)
|
||||
|
||||
43
python/item_wrappers.py
Normal file
43
python/item_wrappers.py
Normal file
@ -0,0 +1,43 @@
|
||||
from models import Item
|
||||
|
||||
class ItemWrapper:
|
||||
"""
|
||||
Classe abstraite représentant un item enrichi d'une logique métier propre.
|
||||
"""
|
||||
def __init__(self, item: Item):
|
||||
self.item = item
|
||||
|
||||
def update(self):
|
||||
raise NotImplementedError("Subclasses must implement update().")
|
||||
|
||||
|
||||
class NormalItem(ItemWrapper):
|
||||
def update(self):
|
||||
self.decrease_quality()
|
||||
self.item.sell_in -= 1
|
||||
if self.item.sell_in < 0:
|
||||
self.decrease_quality()
|
||||
|
||||
def decrease_quality(self):
|
||||
if self.item.quality > 0:
|
||||
self.item.quality -= 1
|
||||
|
||||
|
||||
def item_factory(item: Item) -> ItemWrapper:
|
||||
"""
|
||||
Retourne un wrapper métier adapté à l'item donné.
|
||||
"""
|
||||
if item.name == "Aged Brie":
|
||||
from aged_brie import AgedBrieItem
|
||||
return AgedBrieItem(item)
|
||||
elif item.name == "Sulfuras, Hand of Ragnaros":
|
||||
from sulfuras import SulfurasItem
|
||||
return SulfurasItem(item)
|
||||
elif item.name == "Backstage passes to a TAFKAL80ETC concert":
|
||||
from backstage import BackstagePassItem
|
||||
return BackstagePassItem(item)
|
||||
elif "Conjured" in item.name:
|
||||
from conjured import ConjuredItem
|
||||
return ConjuredItem(item)
|
||||
else:
|
||||
return NormalItem(item)
|
||||
8
python/models.py
Normal file
8
python/models.py
Normal file
@ -0,0 +1,8 @@
|
||||
class Item:
|
||||
def __init__(self, name, sell_in, quality):
|
||||
self.name = name
|
||||
self.sell_in = sell_in
|
||||
self.quality = quality
|
||||
|
||||
def __repr__(self):
|
||||
return "%s, %s, %s" % (self.name, self.sell_in, self.quality)
|
||||
@ -2,3 +2,9 @@ pytest
|
||||
approvaltests
|
||||
pytest-approvaltests
|
||||
coverage
|
||||
black
|
||||
flake8
|
||||
pylint
|
||||
isort
|
||||
mypy
|
||||
|
||||
|
||||
5
python/sulfuras.py
Normal file
5
python/sulfuras.py
Normal file
@ -0,0 +1,5 @@
|
||||
from item_wrappers import ItemWrapper
|
||||
|
||||
class SulfurasItem(ItemWrapper):
|
||||
def update(self):
|
||||
pass # Sulfuras does not change in quality or sell_in
|
||||
@ -0,0 +1,373 @@
|
||||
OMGHAI!
|
||||
-------- day 0 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, 10, 20
|
||||
Aged Brie, 2, 0
|
||||
Elixir of the Mongoose, 5, 7
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, 15, 20
|
||||
Backstage passes to a TAFKAL80ETC concert, 10, 49
|
||||
Backstage passes to a TAFKAL80ETC concert, 5, 49
|
||||
Conjured Mana Cake, 3, 6
|
||||
|
||||
-------- day 1 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, 9, 19
|
||||
Aged Brie, 1, 1
|
||||
Elixir of the Mongoose, 4, 6
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, 14, 21
|
||||
Backstage passes to a TAFKAL80ETC concert, 9, 50
|
||||
Backstage passes to a TAFKAL80ETC concert, 4, 50
|
||||
Conjured Mana Cake, 2, 4
|
||||
|
||||
-------- day 2 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, 8, 18
|
||||
Aged Brie, 0, 2
|
||||
Elixir of the Mongoose, 3, 5
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, 13, 22
|
||||
Backstage passes to a TAFKAL80ETC concert, 8, 50
|
||||
Backstage passes to a TAFKAL80ETC concert, 3, 50
|
||||
Conjured Mana Cake, 1, 2
|
||||
|
||||
-------- day 3 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, 7, 17
|
||||
Aged Brie, -1, 4
|
||||
Elixir of the Mongoose, 2, 4
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, 12, 23
|
||||
Backstage passes to a TAFKAL80ETC concert, 7, 50
|
||||
Backstage passes to a TAFKAL80ETC concert, 2, 50
|
||||
Conjured Mana Cake, 0, 0
|
||||
|
||||
-------- day 4 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, 6, 16
|
||||
Aged Brie, -2, 6
|
||||
Elixir of the Mongoose, 1, 3
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, 11, 24
|
||||
Backstage passes to a TAFKAL80ETC concert, 6, 50
|
||||
Backstage passes to a TAFKAL80ETC concert, 1, 50
|
||||
Conjured Mana Cake, -1, 0
|
||||
|
||||
-------- day 5 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, 5, 15
|
||||
Aged Brie, -3, 8
|
||||
Elixir of the Mongoose, 0, 2
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, 10, 25
|
||||
Backstage passes to a TAFKAL80ETC concert, 5, 50
|
||||
Backstage passes to a TAFKAL80ETC concert, 0, 50
|
||||
Conjured Mana Cake, -2, 0
|
||||
|
||||
-------- day 6 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, 4, 14
|
||||
Aged Brie, -4, 10
|
||||
Elixir of the Mongoose, -1, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, 9, 27
|
||||
Backstage passes to a TAFKAL80ETC concert, 4, 50
|
||||
Backstage passes to a TAFKAL80ETC concert, -1, 0
|
||||
Conjured Mana Cake, -3, 0
|
||||
|
||||
-------- day 7 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, 3, 13
|
||||
Aged Brie, -5, 12
|
||||
Elixir of the Mongoose, -2, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, 8, 29
|
||||
Backstage passes to a TAFKAL80ETC concert, 3, 50
|
||||
Backstage passes to a TAFKAL80ETC concert, -2, 0
|
||||
Conjured Mana Cake, -4, 0
|
||||
|
||||
-------- day 8 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, 2, 12
|
||||
Aged Brie, -6, 14
|
||||
Elixir of the Mongoose, -3, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, 7, 31
|
||||
Backstage passes to a TAFKAL80ETC concert, 2, 50
|
||||
Backstage passes to a TAFKAL80ETC concert, -3, 0
|
||||
Conjured Mana Cake, -5, 0
|
||||
|
||||
-------- day 9 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, 1, 11
|
||||
Aged Brie, -7, 16
|
||||
Elixir of the Mongoose, -4, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, 6, 33
|
||||
Backstage passes to a TAFKAL80ETC concert, 1, 50
|
||||
Backstage passes to a TAFKAL80ETC concert, -4, 0
|
||||
Conjured Mana Cake, -6, 0
|
||||
|
||||
-------- day 10 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, 0, 10
|
||||
Aged Brie, -8, 18
|
||||
Elixir of the Mongoose, -5, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, 5, 35
|
||||
Backstage passes to a TAFKAL80ETC concert, 0, 50
|
||||
Backstage passes to a TAFKAL80ETC concert, -5, 0
|
||||
Conjured Mana Cake, -7, 0
|
||||
|
||||
-------- day 11 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, -1, 8
|
||||
Aged Brie, -9, 20
|
||||
Elixir of the Mongoose, -6, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, 4, 38
|
||||
Backstage passes to a TAFKAL80ETC concert, -1, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -6, 0
|
||||
Conjured Mana Cake, -8, 0
|
||||
|
||||
-------- day 12 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, -2, 6
|
||||
Aged Brie, -10, 22
|
||||
Elixir of the Mongoose, -7, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, 3, 41
|
||||
Backstage passes to a TAFKAL80ETC concert, -2, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -7, 0
|
||||
Conjured Mana Cake, -9, 0
|
||||
|
||||
-------- day 13 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, -3, 4
|
||||
Aged Brie, -11, 24
|
||||
Elixir of the Mongoose, -8, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, 2, 44
|
||||
Backstage passes to a TAFKAL80ETC concert, -3, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -8, 0
|
||||
Conjured Mana Cake, -10, 0
|
||||
|
||||
-------- day 14 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, -4, 2
|
||||
Aged Brie, -12, 26
|
||||
Elixir of the Mongoose, -9, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, 1, 47
|
||||
Backstage passes to a TAFKAL80ETC concert, -4, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -9, 0
|
||||
Conjured Mana Cake, -11, 0
|
||||
|
||||
-------- day 15 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, -5, 0
|
||||
Aged Brie, -13, 28
|
||||
Elixir of the Mongoose, -10, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, 0, 50
|
||||
Backstage passes to a TAFKAL80ETC concert, -5, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -10, 0
|
||||
Conjured Mana Cake, -12, 0
|
||||
|
||||
-------- day 16 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, -6, 0
|
||||
Aged Brie, -14, 30
|
||||
Elixir of the Mongoose, -11, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, -1, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -6, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -11, 0
|
||||
Conjured Mana Cake, -13, 0
|
||||
|
||||
-------- day 17 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, -7, 0
|
||||
Aged Brie, -15, 32
|
||||
Elixir of the Mongoose, -12, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, -2, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -7, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -12, 0
|
||||
Conjured Mana Cake, -14, 0
|
||||
|
||||
-------- day 18 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, -8, 0
|
||||
Aged Brie, -16, 34
|
||||
Elixir of the Mongoose, -13, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, -3, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -8, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -13, 0
|
||||
Conjured Mana Cake, -15, 0
|
||||
|
||||
-------- day 19 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, -9, 0
|
||||
Aged Brie, -17, 36
|
||||
Elixir of the Mongoose, -14, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, -4, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -9, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -14, 0
|
||||
Conjured Mana Cake, -16, 0
|
||||
|
||||
-------- day 20 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, -10, 0
|
||||
Aged Brie, -18, 38
|
||||
Elixir of the Mongoose, -15, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, -5, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -10, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -15, 0
|
||||
Conjured Mana Cake, -17, 0
|
||||
|
||||
-------- day 21 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, -11, 0
|
||||
Aged Brie, -19, 40
|
||||
Elixir of the Mongoose, -16, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, -6, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -11, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -16, 0
|
||||
Conjured Mana Cake, -18, 0
|
||||
|
||||
-------- day 22 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, -12, 0
|
||||
Aged Brie, -20, 42
|
||||
Elixir of the Mongoose, -17, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, -7, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -12, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -17, 0
|
||||
Conjured Mana Cake, -19, 0
|
||||
|
||||
-------- day 23 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, -13, 0
|
||||
Aged Brie, -21, 44
|
||||
Elixir of the Mongoose, -18, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, -8, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -13, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -18, 0
|
||||
Conjured Mana Cake, -20, 0
|
||||
|
||||
-------- day 24 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, -14, 0
|
||||
Aged Brie, -22, 46
|
||||
Elixir of the Mongoose, -19, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, -9, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -14, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -19, 0
|
||||
Conjured Mana Cake, -21, 0
|
||||
|
||||
-------- day 25 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, -15, 0
|
||||
Aged Brie, -23, 48
|
||||
Elixir of the Mongoose, -20, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, -10, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -15, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -20, 0
|
||||
Conjured Mana Cake, -22, 0
|
||||
|
||||
-------- day 26 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, -16, 0
|
||||
Aged Brie, -24, 50
|
||||
Elixir of the Mongoose, -21, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, -11, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -16, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -21, 0
|
||||
Conjured Mana Cake, -23, 0
|
||||
|
||||
-------- day 27 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, -17, 0
|
||||
Aged Brie, -25, 50
|
||||
Elixir of the Mongoose, -22, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, -12, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -17, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -22, 0
|
||||
Conjured Mana Cake, -24, 0
|
||||
|
||||
-------- day 28 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, -18, 0
|
||||
Aged Brie, -26, 50
|
||||
Elixir of the Mongoose, -23, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, -13, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -18, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -23, 0
|
||||
Conjured Mana Cake, -25, 0
|
||||
|
||||
-------- day 29 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, -19, 0
|
||||
Aged Brie, -27, 50
|
||||
Elixir of the Mongoose, -24, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, -14, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -19, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -24, 0
|
||||
Conjured Mana Cake, -26, 0
|
||||
|
||||
-------- day 30 --------
|
||||
name, sellIn, quality
|
||||
+5 Dexterity Vest, -20, 0
|
||||
Aged Brie, -28, 50
|
||||
Elixir of the Mongoose, -25, 0
|
||||
Sulfuras, Hand of Ragnaros, 0, 80
|
||||
Sulfuras, Hand of Ragnaros, -1, 80
|
||||
Backstage passes to a TAFKAL80ETC concert, -15, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -20, 0
|
||||
Backstage passes to a TAFKAL80ETC concert, -25, 0
|
||||
Conjured Mana Cake, -27, 0
|
||||
|
||||
@ -1,15 +1,112 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import unittest
|
||||
import os
|
||||
import sys
|
||||
from models import Item
|
||||
|
||||
from gilded_rose import Item, GildedRose
|
||||
# insérer le dossier parent dans sys.path
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
from gilded_rose import GildedRose
|
||||
|
||||
|
||||
class GildedRoseTest(unittest.TestCase):
|
||||
def test_foo(self):
|
||||
items = [Item("foo", 0, 0)]
|
||||
gilded_rose = GildedRose(items)
|
||||
gilded_rose.update_quality()
|
||||
self.assertEqual("fixme", items[0].name)
|
||||
|
||||
|
||||
# class GildedRoseTest(unittest.TestCase):
|
||||
# def test_foo(self):
|
||||
# items = [Item("foo", 0, 0)]
|
||||
# gilded_rose = GildedRose(items)
|
||||
# gilded_rose.update_quality()
|
||||
# self.assertEqual("fixme", items[0].name)
|
||||
|
||||
class TestNormalItem(unittest.TestCase):
|
||||
def test_quality_decreases_by_one_before_expiration(self):
|
||||
items = [Item(name="+5 Dexterity Vest", sell_in=10, quality=20)]
|
||||
GildedRose(items).update_quality()
|
||||
self.assertEqual(items[0].quality, 19)
|
||||
self.assertEqual(items[0].sell_in, 9)
|
||||
|
||||
def test_quality_decreases_by_two_after_expiration(self):
|
||||
items = [Item(name="Elixir of the Mongoose", sell_in=0, quality=6)]
|
||||
GildedRose(items).update_quality()
|
||||
self.assertEqual(items[0].quality, 4)
|
||||
self.assertEqual(items[0].sell_in, -1)
|
||||
|
||||
def test_quality_never_negative(self):
|
||||
items = [Item(name="Elixir of the Mongoose", sell_in=0, quality=0)]
|
||||
GildedRose(items).update_quality()
|
||||
self.assertEqual(items[0].quality, 0)
|
||||
|
||||
|
||||
class TestAgedBrie(unittest.TestCase):
|
||||
def test_quality_increases_by_one_before_expiration(self):
|
||||
items = [Item(name="Aged Brie", sell_in=2, quality=0)]
|
||||
GildedRose(items).update_quality()
|
||||
self.assertEqual(items[0].quality, 1)
|
||||
self.assertEqual(items[0].sell_in, 1)
|
||||
|
||||
def test_quality_increases_by_two_after_expiration(self):
|
||||
items = [Item(name="Aged Brie", sell_in=0, quality=0)]
|
||||
GildedRose(items).update_quality()
|
||||
self.assertEqual(items[0].quality, 2)
|
||||
self.assertEqual(items[0].sell_in, -1)
|
||||
|
||||
def test_quality_never_exceeds_fifty(self):
|
||||
items = [Item(name="Aged Brie", sell_in=5, quality=50)]
|
||||
GildedRose(items).update_quality()
|
||||
self.assertEqual(items[0].quality, 50)
|
||||
|
||||
|
||||
class TestSulfuras(unittest.TestCase):
|
||||
def test_quality_and_sellin_never_change(self):
|
||||
items = [Item(name="Sulfuras, Hand of Ragnaros", sell_in=5, quality=80)]
|
||||
GildedRose(items).update_quality()
|
||||
self.assertEqual(items[0].quality, 80)
|
||||
self.assertEqual(items[0].sell_in, 5)
|
||||
|
||||
|
||||
class TestBackstagePasses(unittest.TestCase):
|
||||
def test_quality_increases_by_one_above_10_days(self):
|
||||
items = [Item(name="Backstage passes to a TAFKAL80ETC concert", sell_in=15, quality=20)]
|
||||
GildedRose(items).update_quality()
|
||||
self.assertEqual(items[0].quality, 21)
|
||||
|
||||
def test_quality_increases_by_two_between_10_and_6_days(self):
|
||||
items = [Item(name="Backstage passes to a TAFKAL80ETC concert", sell_in=10, quality=20)]
|
||||
GildedRose(items).update_quality()
|
||||
self.assertEqual(items[0].quality, 22)
|
||||
|
||||
def test_quality_increases_by_three_between_5_and_1_days(self):
|
||||
items = [Item(name="Backstage passes to a TAFKAL80ETC concert", sell_in=5, quality=20)]
|
||||
GildedRose(items).update_quality()
|
||||
self.assertEqual(items[0].quality, 23)
|
||||
|
||||
def test_quality_drops_to_zero_after_concert(self):
|
||||
items = [Item(name="Backstage passes to a TAFKAL80ETC concert", sell_in=0, quality=20)]
|
||||
GildedRose(items).update_quality()
|
||||
self.assertEqual(items[0].quality, 0)
|
||||
|
||||
def test_quality_never_exceeds_fifty(self):
|
||||
items = [Item(name="Backstage passes to a TAFKAL80ETC concert", sell_in=5, quality=49)]
|
||||
GildedRose(items).update_quality()
|
||||
self.assertEqual(items[0].quality, 50)
|
||||
|
||||
|
||||
class TestConjuredItems(unittest.TestCase):
|
||||
def test_quality_degrades_twice_as_fast_before_expiration(self):
|
||||
items = [Item(name="Conjured Mana Cake", sell_in=3, quality=6)]
|
||||
GildedRose(items).update_quality()
|
||||
self.assertEqual(items[0].quality, 4)
|
||||
|
||||
def test_quality_degrades_twice_as_fast_after_expiration(self):
|
||||
items = [Item(name="Conjured Mana Cake", sell_in=0, quality=6)]
|
||||
GildedRose(items).update_quality()
|
||||
self.assertEqual(items[0].quality, 2)
|
||||
|
||||
def test_quality_never_negative(self):
|
||||
items = [Item(name="Conjured Mana Cake", sell_in=0, quality=1)]
|
||||
GildedRose(items).update_quality()
|
||||
self.assertEqual(items[0].quality, 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
import io
|
||||
import sys
|
||||
import os
|
||||
|
||||
# insérer le dossier parent dans sys.path
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
from approvaltests.reporters import PythonNativeReporter
|
||||
|
||||
from approvaltests import verify
|
||||
from texttest_fixture import main
|
||||
|
||||
reporter = PythonNativeReporter()
|
||||
def test_gilded_rose_approvals():
|
||||
orig_sysout = sys.stdout
|
||||
try:
|
||||
@ -15,7 +20,7 @@ def test_gilded_rose_approvals():
|
||||
finally:
|
||||
sys.stdout = orig_sysout
|
||||
|
||||
verify(answer)
|
||||
verify(answer, reporter=reporter)
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_gilded_rose_approvals()
|
||||
@ -2,6 +2,7 @@
|
||||
from __future__ import print_function
|
||||
|
||||
from gilded_rose import *
|
||||
from models import Item
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
Loading…
Reference in New Issue
Block a user