has been defined and “APPROVED” then as long as the test provides consistent output then the test will continue to pass. Compare your implementation/actual program against approved outputs Once the test provides output that is different to the approved output the test will fail.
can be difficult to use • Approval tests simplify this by taking a snapshot of the results confirming that they have not changed assertEquals(5, person.getAge()) ... As many asserts than you want to check fields Approvals.verify(person) Always only 1 single line
program output when creating a test case. • Design a Printer to display complex objects, instead of many assertions. • If actual program output is not yet available, the approved value may be a manual sketch of the expected output (useful when you do TDD). • Approved values are stored separately from the source code for the test case
a first test by using junit 5 (dependency already in your pom) @Test public void updateQuality() { var items = new Item[]{new Item("a common item", 0, 0)}; var gildedRose = new GildedRose(items); gildedRose.updateQuality(); assertEquals("a common item", gildedRose.items[0].name); assertEquals(-1, gildedRose.items[0].sellIn); assertEquals(0, gildedRose.items[0].quality); }
in your pom.xml <dependency> <groupId>com.approvaltests</groupId> <artifactId>approvaltests</artifactId> <version>9.1.0</version> <scope>test</scope> </dependency> Refactor the existing test using ApprovalTests @Test public void updateQuality() { var items = new Item[]{new Item("a common item", 0, 0)}; var gildedRose = new GildedRose(items); gildedRose.updateQuality(); Approvals.verify(gildedRose.items[0]); }
• GildedRoseTests.updateQuality.received.txt that has been generated based on what is inside the verify method call • GildedRoseTests.updateQuality.approved.txt a content that has already been approved The actual implementation is functionally good. So we must approve what is currently generated / calculated by the system.
updateQuality() { var name = "a common item"; var sellIn = 0; var quality = 0; CombinationApprovals.verifyAllCombinations( this::callUpdateQuality, new String[]{name}, new Integer[]{sellIn}, new Integer[]{quality} ); } private String callUpdateQuality(String name, int sellIn, int quality) { var items = new Item[]{new Item(name, sellIn, quality)}; var gildedRose = new GildedRose(items); gildedRose.updateQuality(); return gildedRose.items[0].toString(); } Run the test again when you use CombinationApprovals it adds a description of the combination for each input : [a common item, 0, 0] => a common item, -1, 0
red or orange line 2. Add an input in the CombinationApprovals 3. Run the test with coverage (check that you improved the coverage) 4. Approve the result (with your terminal) Repeat until you have 100% coverage and are very confident By discovering them with the Code Coverage tool
this::callUpdateQuality, new String[]{"a common item", "Aged Brie", "Backstage passes to a TAFKAL80ETC concert", "Sulfuras, Hand of Ragnaros"}, new Integer[]{-1, 0, 11}, new Integer[]{0, 1, 49, 50} ); } [a common item, -1, 0] => a common item, -2, 0 [a common item, -1, 1] => a common item, -2, 0 [a common item, -1, 49] => a common item, -2, 47 [a common item, -1, 50] => a common item, -2, 48 [a common item, 0, 0] => a common item, -1, 0 [a common item, 0, 1] => a common item, -1, 0 [a common item, 0, 49] => a common item, -1, 47 [a common item, 0, 50] => a common item, -1, 48 [a common item, 11, 0] => a common item, 10, 0 [a common item, 11, 1] => a common item, 10, 0 [a common item, 11, 49] => a common item, 10, 48 [a common item, 11, 50] => a common item, 10, 49 [Aged Brie, -1, 0] => Aged Brie, -2, 2 [Aged Brie, -1, 1] => Aged Brie, -2, 3 [Aged Brie, -1, 49] => Aged Brie, -2, 50 [Aged Brie, -1, 50] => Aged Brie, -2, 50 [Aged Brie, 0, 0] => Aged Brie, -1, 2 [Aged Brie, 0, 1] => Aged Brie, -1, 3 [Aged Brie, 0, 49] => Aged Brie, -1, 50 [Aged Brie, 0, 50] => Aged Brie, -1, 50 [Aged Brie, 11, 0] => Aged Brie, 10, 1 [Aged Brie, 11, 1] => Aged Brie, 10, 2 [Aged Brie, 11, 49] => Aged Brie, 10, 50 [Aged Brie, 11, 50] => Aged Brie, 10, 50 [Backstage passes to a TAFKAL80ETC concert, -1, 0] => Backstage passes to a TAFKAL80ETC concert, -2, 0 [Backstage passes to a TAFKAL80ETC concert, -1, 1] => Backstage passes to a TAFKAL80ETC concert, -2, 0 [Backstage passes to a TAFKAL80ETC concert, -1, 49] => Backstage passes to a TAFKAL80ETC concert, -2, 0 [Backstage passes to a TAFKAL80ETC concert, -1, 50] => Backstage passes to a TAFKAL80ETC concert, -2, 0 [Backstage passes to a TAFKAL80ETC concert, 0, 0] => Backstage passes to a TAFKAL80ETC concert, -1, 0 [Backstage passes to a TAFKAL80ETC concert, 0, 1] => Backstage passes to a TAFKAL80ETC concert, -1, 0 [Backstage passes to a TAFKAL80ETC concert, 0, 49] => Backstage passes to a TAFKAL80ETC concert, -1, 0 [Backstage passes to a TAFKAL80ETC concert, 0, 50] => Backstage passes to a TAFKAL80ETC concert, -1, 0 [Backstage passes to a TAFKAL80ETC concert, 11, 0] => Backstage passes to a TAFKAL80ETC concert, 10, 1 [Backstage passes to a TAFKAL80ETC concert, 11, 1] => Backstage passes to a TAFKAL80ETC concert, 10, 2 [Backstage passes to a TAFKAL80ETC concert, 11, 49] => Backstage passes to a TAFKAL80ETC concert, 10, 50 [Backstage passes to a TAFKAL80ETC concert, 11, 50] => Backstage passes to a TAFKAL80ETC concert, 10, 50 [Sulfuras, Hand of Ragnaros, -1, 0] => Sulfuras, Hand of Ragnaros, -1, 0 [Sulfuras, Hand of Ragnaros, -1, 1] => Sulfuras, Hand of Ragnaros, -1, 1 [Sulfuras, Hand of Ragnaros, -1, 49] => Sulfuras, Hand of Ragnaros, -1, 49 [Sulfuras, Hand of Ragnaros, -1, 50] => Sulfuras, Hand of Ragnaros, -1, 50 [Sulfuras, Hand of Ragnaros, 0, 0] => Sulfuras, Hand of Ragnaros, 0, 0 [Sulfuras, Hand of Ragnaros, 0, 1] => Sulfuras, Hand of Ragnaros, 0, 1 [Sulfuras, Hand of Ragnaros, 0, 49] => Sulfuras, Hand of Ragnaros, 0, 49 [Sulfuras, Hand of Ragnaros, 0, 50] => Sulfuras, Hand of Ragnaros, 0, 50 [Sulfuras, Hand of Ragnaros, 11, 0] => Sulfuras, Hand of Ragnaros, 11, 0 [Sulfuras, Hand of Ragnaros, 11, 1] => Sulfuras, Hand of Ragnaros, 11, 1 [Sulfuras, Hand of Ragnaros, 11, 49] => Sulfuras, Hand of Ragnaros, 11, 49 [Sulfuras, Hand of Ragnaros, 11, 50] => Sulfuras, Hand of Ragnaros, 11, 50
CombinationApprovals.verifyAllCombinations( this::callUpdateQuality, new String[]{"a common item", "Aged Brie", "Backstage passes to a TAFKAL80ETC concert", "Sulfuras, Hand of Ragnaros"}, new Integer[]{-100, -1, 0, 2, 8, 11}, new Integer[]{0, 1, 49, 50} ); }
Yoan THIRION (freelance) • design software since more than 15 years • fundamental to succeed in that area : agility and technical excellence • help teams deliver well crafted software • implementation of agile and technical practices (eXtreme programming, Refactoring, DDD, Mob programming, …) Let’s connect My services https://www.yoan-thirion.com/