Test with generated testcases which cover all paths

This commit is contained in:
Jacek Weremko 2024-06-15 14:36:06 +02:00
parent cf6f9f54fe
commit 4037feedba
8 changed files with 182 additions and 12 deletions

View File

@ -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).
## Starting Point
### Project info
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
@ -33,7 +35,8 @@ There are instructions in the [TextTest Readme](../texttests/README.md) for sett
3. understand algorithm and propose better solution
### Execution
## Execution
### Testing TexttestFixtures
TexttestFixtures indeed do not cover all paths, based on jacoco coverage:
![img.png](img.png)
@ -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
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:
![img_1.png](img_1.png)
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -1,6 +1,6 @@
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 {
return this.name + ", " + this.sellIn + ", " + this.quality
}

View 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
}
}
}
}
}
}

View 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")
}

View File

@ -6,15 +6,15 @@ fun main(args: Array<String>) {
val items = listOf(
Item("+5 Dexterity Vest", 10, 20), //
Item("Aged Brie", 2, 0), //
Item("Elixir of the Mongoose", 5, 7), //
Item("Sulfuras, Hand of Ragnaros", 0, 80), //
Item("Sulfuras, Hand of Ragnaros", -1, 80),
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", 5, 49),
// this conjured item does not work properly yet
Item("Conjured Mana Cake", 3, 6)
Item("Aged Brie", 2, 0), //
Item("Elixir of the Mongoose", 5, 7), //
Item("Sulfuras, Hand of Ragnaros", 0, 80), //
Item("Sulfuras, Hand of Ragnaros", -1, 80),
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", 5, 49),
// this conjured item does not work properly yet
Item("Conjured Mana Cake", 3, 6)
)
val app = GildedRose(items)

View File

@ -20,7 +20,16 @@ internal class GildedRoseTest {
val app = GildedRose(items)
app.updateQuality()
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
@ -53,7 +62,6 @@ internal class GildedRoseTest {
)
}
}
}

View 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
}
}