Adding parameterized specification (Issue#1055) (#1088)

* Resolution proposition to Issue#1055 (UML diagram left to do)

* Deciding not to modify the UML diagram for now
This commit is contained in:
Martin Vandenbussche
2019-11-16 13:24:46 +01:00
committed by Ilkka Seppälä
parent cc571f4149
commit df8a4e3b47
17 changed files with 328 additions and 30 deletions

View File

@ -31,8 +31,11 @@ import com.iluwatar.specification.creature.Octopus;
import com.iluwatar.specification.creature.Shark;
import com.iluwatar.specification.creature.Troll;
import com.iluwatar.specification.property.Color;
import com.iluwatar.specification.property.Mass;
import com.iluwatar.specification.property.Movement;
import com.iluwatar.specification.selector.ColorSelector;
import com.iluwatar.specification.selector.MassGreaterThanSelector;
import com.iluwatar.specification.selector.MassSmallerThanOrEqSelector;
import com.iluwatar.specification.selector.MovementSelector;
import java.util.List;
import java.util.stream.Collectors;
@ -51,7 +54,7 @@ import org.slf4j.LoggerFactory;
* <p>http://martinfowler.com/apsupp/spec.pdf</p>
*/
public class App {
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
/**
@ -61,6 +64,8 @@ public class App {
// initialize creatures list
List<Creature> creatures = List.of(new Goblin(), new Octopus(), new Dragon(), new Shark(),
new Troll(), new KillerBee());
// so-called "hard-coded" specification
LOGGER.info("Demonstrating hard-coded specification :");
// find all walking creatures
LOGGER.info("Find all walking creatures");
List<Creature> walkingCreatures =
@ -79,5 +84,19 @@ public class App {
.filter(new ColorSelector(Color.RED).and(new MovementSelector(Movement.FLYING)))
.collect(Collectors.toList());
redAndFlyingCreatures.forEach(c -> LOGGER.info(c.toString()));
// so-called "parameterized" specification
LOGGER.info("Demonstrating parameterized specification :");
// find all creatures heavier than 500kg
LOGGER.info("Find all creatures heavier than 600kg");
List<Creature> heavyCreatures =
creatures.stream().filter(new MassGreaterThanSelector(600.0))
.collect(Collectors.toList());
heavyCreatures.forEach(c -> LOGGER.info(c.toString()));
// find all creatures heavier than 500kg
LOGGER.info("Find all creatures lighter than or weighing exactly 500kg");
List<Creature> lightCreatures =
creatures.stream().filter(new MassSmallerThanOrEqSelector(500.0))
.collect(Collectors.toList());
lightCreatures.forEach(c -> LOGGER.info(c.toString()));
}
}

View File

@ -24,6 +24,7 @@
package com.iluwatar.specification.creature;
import com.iluwatar.specification.property.Color;
import com.iluwatar.specification.property.Mass;
import com.iluwatar.specification.property.Movement;
import com.iluwatar.specification.property.Size;
@ -36,20 +37,23 @@ public abstract class AbstractCreature implements Creature {
private Size size;
private Movement movement;
private Color color;
private Mass mass;
/**
* Constructor.
*/
public AbstractCreature(String name, Size size, Movement movement, Color color) {
public AbstractCreature(String name, Size size, Movement movement, Color color, Mass mass) {
this.name = name;
this.size = size;
this.movement = movement;
this.color = color;
this.mass = mass;
}
@Override
public String toString() {
return String.format("%s [size=%s, movement=%s, color=%s]", name, size, movement, color);
return String.format("%s [size=%s, movement=%s, color=%s, mass=%s]",
name, size, movement, color, mass);
}
@Override
@ -71,4 +75,9 @@ public abstract class AbstractCreature implements Creature {
public Color getColor() {
return color;
}
@Override
public Mass getMass() {
return mass;
}
}

View File

@ -24,6 +24,7 @@
package com.iluwatar.specification.creature;
import com.iluwatar.specification.property.Color;
import com.iluwatar.specification.property.Mass;
import com.iluwatar.specification.property.Movement;
import com.iluwatar.specification.property.Size;
@ -39,4 +40,6 @@ public interface Creature {
Movement getMovement();
Color getColor();
Mass getMass();
}

View File

@ -24,6 +24,7 @@
package com.iluwatar.specification.creature;
import com.iluwatar.specification.property.Color;
import com.iluwatar.specification.property.Mass;
import com.iluwatar.specification.property.Movement;
import com.iluwatar.specification.property.Size;
@ -33,6 +34,10 @@ import com.iluwatar.specification.property.Size;
public class Dragon extends AbstractCreature {
public Dragon() {
super("Dragon", Size.LARGE, Movement.FLYING, Color.RED);
this(new Mass(39300.0));
}
public Dragon(Mass mass) {
super("Dragon", Size.LARGE, Movement.FLYING, Color.RED, mass);
}
}

View File

@ -24,6 +24,7 @@
package com.iluwatar.specification.creature;
import com.iluwatar.specification.property.Color;
import com.iluwatar.specification.property.Mass;
import com.iluwatar.specification.property.Movement;
import com.iluwatar.specification.property.Size;
@ -33,6 +34,10 @@ import com.iluwatar.specification.property.Size;
public class Goblin extends AbstractCreature {
public Goblin() {
super("Goblin", Size.SMALL, Movement.WALKING, Color.GREEN);
this(new Mass(30.0));
}
public Goblin(Mass mass) {
super("Goblin", Size.SMALL, Movement.WALKING, Color.GREEN, mass);
}
}

View File

@ -24,6 +24,7 @@
package com.iluwatar.specification.creature;
import com.iluwatar.specification.property.Color;
import com.iluwatar.specification.property.Mass;
import com.iluwatar.specification.property.Movement;
import com.iluwatar.specification.property.Size;
@ -33,6 +34,10 @@ import com.iluwatar.specification.property.Size;
public class KillerBee extends AbstractCreature {
public KillerBee() {
super("KillerBee", Size.SMALL, Movement.FLYING, Color.LIGHT);
this(new Mass(6.7));
}
public KillerBee(Mass mass) {
super("KillerBee", Size.SMALL, Movement.FLYING, Color.LIGHT, mass);
}
}

View File

@ -24,6 +24,7 @@
package com.iluwatar.specification.creature;
import com.iluwatar.specification.property.Color;
import com.iluwatar.specification.property.Mass;
import com.iluwatar.specification.property.Movement;
import com.iluwatar.specification.property.Size;
@ -33,6 +34,10 @@ import com.iluwatar.specification.property.Size;
public class Octopus extends AbstractCreature {
public Octopus() {
super("Octopus", Size.NORMAL, Movement.SWIMMING, Color.DARK);
this(new Mass(12.0));
}
public Octopus(Mass mass) {
super("Octopus", Size.NORMAL, Movement.SWIMMING, Color.DARK, mass);
}
}

View File

@ -24,6 +24,7 @@
package com.iluwatar.specification.creature;
import com.iluwatar.specification.property.Color;
import com.iluwatar.specification.property.Mass;
import com.iluwatar.specification.property.Movement;
import com.iluwatar.specification.property.Size;
@ -33,6 +34,10 @@ import com.iluwatar.specification.property.Size;
public class Shark extends AbstractCreature {
public Shark() {
super("Shark", Size.NORMAL, Movement.SWIMMING, Color.LIGHT);
this(new Mass(500.0));
}
public Shark(Mass mass) {
super("Shark", Size.NORMAL, Movement.SWIMMING, Color.LIGHT, mass);
}
}

View File

@ -24,6 +24,7 @@
package com.iluwatar.specification.creature;
import com.iluwatar.specification.property.Color;
import com.iluwatar.specification.property.Mass;
import com.iluwatar.specification.property.Movement;
import com.iluwatar.specification.property.Size;
@ -33,6 +34,10 @@ import com.iluwatar.specification.property.Size;
public class Troll extends AbstractCreature {
public Troll() {
super("Troll", Size.LARGE, Movement.WALKING, Color.DARK);
this(new Mass(4000.0));
}
public Troll(Mass mass) {
super("Troll", Size.LARGE, Movement.WALKING, Color.DARK, mass);
}
}

View File

@ -24,7 +24,7 @@
package com.iluwatar.specification.property;
/**
* <p>Color property.</p>
* Color property.
*/
public enum Color {

View File

@ -0,0 +1,65 @@
/*
* The MIT License
* Copyright © 2014-2019 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.specification.property;
/** Mass property. */
public class Mass {
private double value;
private String title;
public Mass(double value) {
this.value = value;
this.title = value + "kg"; // Implicit call to Double.toString(value)
}
public final boolean greaterThan(Mass other) {
return this.value > other.value;
}
public final boolean smallerThan(Mass other) {
return this.value < other.value;
}
public final boolean greaterThanOrEq(Mass other) {
return this.value >= other.value;
}
public final boolean smallerThanOrEq(Mass other) {
return this.value <= other.value;
}
@Override
public String toString() {
return title;
}
@Override
public final boolean equals(Object obj) {
if (!(obj instanceof Mass)) {
return false;
}
return ((Mass) obj).value == this.value;
}
}

View File

@ -0,0 +1,44 @@
/*
* The MIT License
* Copyright © 2014-2019 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.specification.selector;
import com.iluwatar.specification.creature.Creature;
import com.iluwatar.specification.property.Mass;
import java.util.function.Predicate;
/** Mass selector for values greater than the parameter. */
public class MassGreaterThanSelector implements Predicate<Creature> {
private final Mass mass;
/** The use of a double as a parameter will spare some typing when instantiating this class. */
public MassGreaterThanSelector(double mass) {
this.mass = new Mass(mass);
}
@Override
public boolean test(Creature t) {
return t.getMass().greaterThan(mass);
}
}

View File

@ -0,0 +1,44 @@
/*
* The MIT License
* Copyright © 2014-2019 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.specification.selector;
import com.iluwatar.specification.creature.Creature;
import com.iluwatar.specification.property.Mass;
import java.util.function.Predicate;
/** Mass selector for values smaller or equal to the parameter. */
public class MassSmallerThanOrEqSelector implements Predicate<Creature> {
private final Mass mass;
/** The use of a double as a parameter will spare some typing when instantiating this class. */
public MassSmallerThanOrEqSelector(double mass) {
this.mass = new Mass(mass);
}
@Override
public boolean test(Creature t) {
return t.getMass().smallerThanOrEq(mass);
}
}

View File

@ -24,6 +24,7 @@
package com.iluwatar.specification.creature;
import com.iluwatar.specification.property.Color;
import com.iluwatar.specification.property.Mass;
import com.iluwatar.specification.property.Movement;
import com.iluwatar.specification.property.Size;
import org.junit.jupiter.params.ParameterizedTest;
@ -47,12 +48,12 @@ public class CreatureTest {
*/
public static Collection<Object[]> dataProvider() {
return List.of(
new Object[]{new Dragon(), "Dragon", Size.LARGE, Movement.FLYING, Color.RED},
new Object[]{new Goblin(), "Goblin", Size.SMALL, Movement.WALKING, Color.GREEN},
new Object[]{new KillerBee(), "KillerBee", Size.SMALL, Movement.FLYING, Color.LIGHT},
new Object[]{new Octopus(), "Octopus", Size.NORMAL, Movement.SWIMMING, Color.DARK},
new Object[]{new Shark(), "Shark", Size.NORMAL, Movement.SWIMMING, Color.LIGHT},
new Object[]{new Troll(), "Troll", Size.LARGE, Movement.WALKING, Color.DARK}
new Object[]{new Dragon(), "Dragon", Size.LARGE, Movement.FLYING, Color.RED, new Mass(39300.0)},
new Object[]{new Goblin(), "Goblin", Size.SMALL, Movement.WALKING, Color.GREEN, new Mass(30.0)},
new Object[]{new KillerBee(), "KillerBee", Size.SMALL, Movement.FLYING, Color.LIGHT, new Mass(6.7)},
new Object[]{new Octopus(), "Octopus", Size.NORMAL, Movement.SWIMMING, Color.DARK, new Mass(12.0)},
new Object[]{new Shark(), "Shark", Size.NORMAL, Movement.SWIMMING, Color.LIGHT, new Mass(500.0)},
new Object[]{new Troll(), "Troll", Size.LARGE, Movement.WALKING, Color.DARK, new Mass(4000.0)}
);
}
@ -82,11 +83,17 @@ public class CreatureTest {
@ParameterizedTest
@MethodSource("dataProvider")
public void testToString(Creature testedCreature, String name, Size size, Movement movement, Color color) {
public void testGetMass(Creature testedCreature, String name, Size size, Movement movement, Color color, Mass mass) {
assertEquals(mass, testedCreature.getMass());
}
@ParameterizedTest
@MethodSource("dataProvider")
public void testToString(Creature testedCreature, String name, Size size, Movement movement, Color color, Mass mass) {
final String toString = testedCreature.toString();
assertNotNull(toString);
assertEquals(
String.format("%s [size=%s, movement=%s, color=%s]", name, size, movement, color),
String.format("%s [size=%s, movement=%s, color=%s, mass=%s]", name, size, movement, color, mass),
toString
);
}

View File

@ -0,0 +1,50 @@
/*
* The MIT License
* Copyright © 2014-2019 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.specification.selector;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.iluwatar.specification.creature.Creature;
import com.iluwatar.specification.property.Mass;
import org.junit.jupiter.api.Test;
public class MassSelectorTest {
/** Verify if the mass selector gives the correct results */
@Test
public void testMass() {
final Creature lightCreature = mock(Creature.class);
when(lightCreature.getMass()).thenReturn(new Mass(50.0));
final Creature heavyCreature = mock(Creature.class);
when(heavyCreature.getMass()).thenReturn(new Mass(2500.0));
final MassSmallerThanOrEqSelector lightSelector = new MassSmallerThanOrEqSelector(500.0);
assertTrue(lightSelector.test(lightCreature));
assertFalse(lightSelector.test(heavyCreature));
}
}