mirror of
https://github.com/emilybache/GildedRose-Refactoring-Kata.git
synced 2026-02-09 03:31:28 +00:00
Test with generated testcases which cover all paths
This commit is contained in:
parent
cf6f9f54fe
commit
4037feedba
@ -22,6 +22,8 @@ You should make sure the gradle commands shown above work when you execute them
|
|||||||
There are instructions in the [TextTest Readme](../texttests/README.md) for setting up TextTest. What's unusual for the Java version is there are two executables listed in [config.gr](../texttests/config.gr) for Java. One uses Gradle wrapped in a python script, the other relies on your CLASSPATH being set correctly in [environment.gr](../texttests/environment.gr).
|
There are instructions in the [TextTest Readme](../texttests/README.md) for setting up TextTest. What's unusual for the Java version is there are two executables listed in [config.gr](../texttests/config.gr) for Java. One uses Gradle wrapped in a python script, the other relies on your CLASSPATH being set correctly in [environment.gr](../texttests/environment.gr).
|
||||||
|
|
||||||
|
|
||||||
|
## Starting Point
|
||||||
|
|
||||||
### Project info
|
### Project info
|
||||||
1. Code is highly unreadable and impossible to extend
|
1. Code is highly unreadable and impossible to extend
|
||||||
2. Algorithm and test cases are unknown, I do not trust that `TexttestFixtures` covers all paths so I want to validate it with jacoco
|
2. Algorithm and test cases are unknown, I do not trust that `TexttestFixtures` covers all paths so I want to validate it with jacoco
|
||||||
@ -33,7 +35,8 @@ There are instructions in the [TextTest Readme](../texttests/README.md) for sett
|
|||||||
3. understand algorithm and propose better solution
|
3. understand algorithm and propose better solution
|
||||||
|
|
||||||
|
|
||||||
### Execution
|
## Execution
|
||||||
|
### Testing TexttestFixtures
|
||||||
TexttestFixtures indeed do not cover all paths, based on jacoco coverage:
|
TexttestFixtures indeed do not cover all paths, based on jacoco coverage:
|
||||||

|

|
||||||
|
|
||||||
@ -43,3 +46,13 @@ There are two ways to advance:
|
|||||||
|
|
||||||
I prefer to go with (2) because it seems to me to be more resilient way. I need to check if execution
|
I prefer to go with (2) because it seems to me to be more resilient way. I need to check if execution
|
||||||
time is not bloated. Also minimize number of test cases.
|
time is not bloated. Also minimize number of test cases.
|
||||||
|
|
||||||
|
### Test cases generation
|
||||||
|
I copied GildedRose class to PlatinumRose and implemented a test which executes both app
|
||||||
|
and then compare results. Time of execution is under 500ms which is great. Also all paths are covered:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
One noticeable fact i that items are declared as `var` which makes it possible to update during/after execution.
|
||||||
|
In my opinion this is a design issue, code-smell and I'm going to change it to `val`.
|
||||||
|
So far I only changed Item to data class so that object comparison is easier, also it seems to be DTO/record so it suppose to be a data class
|
||||||
|
|||||||
BIN
Kotlin/img_1.png
Normal file
BIN
Kotlin/img_1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
@ -1,6 +1,6 @@
|
|||||||
package com.gildedrose
|
package com.gildedrose
|
||||||
|
|
||||||
open class Item(var name: String, var sellIn: Int, var quality: Int) {
|
data class Item(var name: String, var sellIn: Int, var quality: Int) {
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return this.name + ", " + this.sellIn + ", " + this.quality
|
return this.name + ", " + this.sellIn + ", " + this.quality
|
||||||
}
|
}
|
||||||
|
|||||||
58
Kotlin/src/main/kotlin/com/gildedrose/PlatinumRose.kt
Normal file
58
Kotlin/src/main/kotlin/com/gildedrose/PlatinumRose.kt
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package com.gildedrose
|
||||||
|
|
||||||
|
class PlatinumRose(var items: List<Item>) {
|
||||||
|
|
||||||
|
fun updateQuality() {
|
||||||
|
for (i in items.indices) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
43
Kotlin/src/main/kotlin/com/gildedrose/TestcasesGenerator.kt
Normal file
43
Kotlin/src/main/kotlin/com/gildedrose/TestcasesGenerator.kt
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package com.gildedrose
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
fun generateTestCasesInRanger(names: List<String>, sellInRange: IntRange, qualityRange: IntRange): List<Item> {
|
||||||
|
val items = LinkedList<Item>()
|
||||||
|
for (name in names) {
|
||||||
|
for (sellIn in sellInRange) {
|
||||||
|
for (quality in qualityRange) {
|
||||||
|
items.add(Item(name, sellIn, quality))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
val names = listOf(
|
||||||
|
"Aged Brie",
|
||||||
|
"Backstage passes to a TAFKAL80ETC concert",
|
||||||
|
"Sulfuras, Hand of Ragnaros",
|
||||||
|
"new none-existing on code name"
|
||||||
|
)
|
||||||
|
val sellInRange = -100..100
|
||||||
|
val qualityRange = -100..100
|
||||||
|
val allTestCases = generateTestCasesInRanger(names, sellInRange, qualityRange)
|
||||||
|
|
||||||
|
|
||||||
|
var testCasesCounter = 0
|
||||||
|
for (testCase in allTestCases) {
|
||||||
|
val app = GildedRose(listOf(Item(testCase.name, testCase.sellIn, testCase.quality)))
|
||||||
|
app.updateQuality()
|
||||||
|
|
||||||
|
|
||||||
|
if (testCase.sellIn != app.items[0].sellIn || testCase.quality != app.items[0].quality) {
|
||||||
|
//println("$testCasesCounter: $testCase vs ${app.items[0]}")
|
||||||
|
testCasesCounter += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println("Found $testCasesCounter testCasesCounter of out ${allTestCases.size} allTestCases")
|
||||||
|
|
||||||
|
}
|
||||||
@ -6,15 +6,15 @@ fun main(args: Array<String>) {
|
|||||||
|
|
||||||
val items = listOf(
|
val items = listOf(
|
||||||
Item("+5 Dexterity Vest", 10, 20), //
|
Item("+5 Dexterity Vest", 10, 20), //
|
||||||
Item("Aged Brie", 2, 0), //
|
Item("Aged Brie", 2, 0), //
|
||||||
Item("Elixir of the Mongoose", 5, 7), //
|
Item("Elixir of the Mongoose", 5, 7), //
|
||||||
Item("Sulfuras, Hand of Ragnaros", 0, 80), //
|
Item("Sulfuras, Hand of Ragnaros", 0, 80), //
|
||||||
Item("Sulfuras, Hand of Ragnaros", -1, 80),
|
Item("Sulfuras, Hand of Ragnaros", -1, 80),
|
||||||
Item("Backstage passes to a TAFKAL80ETC concert", 15, 20),
|
Item("Backstage passes to a TAFKAL80ETC concert", 15, 20),
|
||||||
Item("Backstage passes to a TAFKAL80ETC concert", 10, 49),
|
Item("Backstage passes to a TAFKAL80ETC concert", 10, 49),
|
||||||
Item("Backstage passes to a TAFKAL80ETC concert", 5, 49),
|
Item("Backstage passes to a TAFKAL80ETC concert", 5, 49),
|
||||||
// this conjured item does not work properly yet
|
// this conjured item does not work properly yet
|
||||||
Item("Conjured Mana Cake", 3, 6)
|
Item("Conjured Mana Cake", 3, 6)
|
||||||
)
|
)
|
||||||
|
|
||||||
val app = GildedRose(items)
|
val app = GildedRose(items)
|
||||||
|
|||||||
@ -20,7 +20,16 @@ internal class GildedRoseTest {
|
|||||||
val app = GildedRose(items)
|
val app = GildedRose(items)
|
||||||
app.updateQuality()
|
app.updateQuality()
|
||||||
assertEquals("foo", app.items[0].name)
|
assertEquals("foo", app.items[0].name)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should execute without exception for empty list`() {
|
||||||
|
val items = listOf<Item>()
|
||||||
|
val app = GildedRose(items)
|
||||||
|
app.updateQuality()
|
||||||
|
assertAll(
|
||||||
|
{ assertEquals(items, app.items) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@ -53,7 +62,6 @@ internal class GildedRoseTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
48
Kotlin/src/test/kotlin/com/gildedrose/PlatinumRoseTest.kt
Normal file
48
Kotlin/src/test/kotlin/com/gildedrose/PlatinumRoseTest.kt
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package com.gildedrose
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class PlatinumRoseTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should update quality for generated test cases and compare with GlidedRose`() {
|
||||||
|
val names = listOf(
|
||||||
|
"Aged Brie",
|
||||||
|
"Backstage passes to a TAFKAL80ETC concert",
|
||||||
|
"Sulfuras, Hand of Ragnaros",
|
||||||
|
"new none-existing on code name"
|
||||||
|
)
|
||||||
|
val sellInRange = -100..100
|
||||||
|
val qualityRange = -100..100
|
||||||
|
val allTestCases = generateTestCasesInRanger(names, sellInRange, qualityRange)
|
||||||
|
|
||||||
|
|
||||||
|
for (testCase in allTestCases) {
|
||||||
|
val platinumRose = PlatinumRose(listOf(Item(testCase.name, testCase.sellIn, testCase.quality)))
|
||||||
|
val gildedRose = GildedRose(listOf(Item(testCase.name, testCase.sellIn, testCase.quality)))
|
||||||
|
|
||||||
|
platinumRose.updateQuality()
|
||||||
|
gildedRose.updateQuality()
|
||||||
|
|
||||||
|
assertEquals(gildedRose.items, platinumRose.items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateTestCasesInRanger(
|
||||||
|
names: List<String>,
|
||||||
|
sellInRange: IntRange,
|
||||||
|
qualityRange: IntRange
|
||||||
|
): List<Item> {
|
||||||
|
val items = LinkedList<Item>()
|
||||||
|
for (name in names) {
|
||||||
|
for (sellIn in sellInRange) {
|
||||||
|
for (quality in qualityRange) {
|
||||||
|
items.add(Item(name, sellIn, quality))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user