SlideShare a Scribd company logo
http://tinyurl.com/sf-rnt
http://spryfox.com/our-games/road-not-taken/
shut down wifi, skype.
RECORD!
Unit
Tests
Practical
Andrew Fray, Spry Fox
Thank you for coming. This is Practical Unit Tests, and I’m Andrew Fray. 

My talk today is about how to structure your unit tests to aid iteration. We’re going to be looking in detail at my unit tests from a shipped AAA console
project, the mistakes I made in writing them, and the concrete costs they caused. Then we’ll look at simple ways to avoid those costs in your own unit
tests.

!
definitions
Post mortem
Anti-patterns
#pracunittests
Test Driven
Development
move on!
#pracunittests
Backwards Is Forward: Making Better
Games with Test-Driven Development
http://gdcvault.com/play/1013416/Backwards-Is-Forward-Making-Better
Sean Houghton, Noel Llopis
http://tinyurl.com/gddtdd
#pracunittests
2004
@tenpn
#pracunittests
2004
@tenpn
Definitions
#pracunittests
Unit TestSingle explicit assumption
IMPORTANT
functional/integration interchangable
#pracunittests
Unit TestSingle explicit assumption
Integration Test
Many implicit assumptions
IMPORTANT
functional/integration interchangable
#pracunittests
Qualities of Good Unit Tests
Readable - why. up to speed.
Maintainable - how. modify for iterating.
Trustworthy - quick, deterministic. no external resources.
#pracunittests
Qualities of Good Unit Tests
Readable
Readable - why. up to speed.
Maintainable - how. modify for iterating.
Trustworthy - quick, deterministic. no external resources.
#pracunittests
Qualities of Good Unit Tests
Readable
Maintainable
Readable - why. up to speed.
Maintainable - how. modify for iterating.
Trustworthy - quick, deterministic. no external resources.
#pracunittests
Qualities of Good Unit Tests
Readable
Maintainable
Trustworthy
Readable - why. up to speed.
Maintainable - how. modify for iterating.
Trustworthy - quick, deterministic. no external resources.
Post
Mortem
#pracunittests
F1 2011 X360/PS3/PC
PRODUCTION CODE => SUBSYSTEM CODE

HIGHLIGHT loc
shipped 4+ previously
#pracunittests
F1 2011 X360/PS3/PC
• Isolated new subsystem
PRODUCTION CODE => SUBSYSTEM CODE

HIGHLIGHT loc
shipped 4+ previously
#pracunittests
F1 2011 X360/PS3/PC
• Isolated new subsystem
• 502 tests, 6700 lines of test code
PRODUCTION CODE => SUBSYSTEM CODE

HIGHLIGHT loc
shipped 4+ previously
#pracunittests
F1 2011 X360/PS3/PC
• Isolated new subsystem
• 502 tests, 6700 lines of test code
• 6200 lines of production code
PRODUCTION CODE => SUBSYSTEM CODE

HIGHLIGHT loc
shipped 4+ previously
#pracunittests
A Partial Succes
PRODUCTION CODE => SUBSYSTEM CODE

HIGHLIGHT loc
shipped 4+ previously
#pracunittests
• Clean, re-usable
code
A Partial Succes
PRODUCTION CODE => SUBSYSTEM CODE

HIGHLIGHT loc
shipped 4+ previously
#pracunittests
• Clean, re-usable
code
• Fewer bugs
A Partial Succes
PRODUCTION CODE => SUBSYSTEM CODE

HIGHLIGHT loc
shipped 4+ previously
#pracunittests
• Clean, re-usable
code
• Fewer bugs
• Easy to optimise
A Partial Succes
PRODUCTION CODE => SUBSYSTEM CODE

HIGHLIGHT loc
shipped 4+ previously
#pracunittests
• Clean, re-usable
code
• Fewer bugs
• Easy to optimise
• At end, treacle-like
progress
A Partial Succes
PRODUCTION CODE => SUBSYSTEM CODE

HIGHLIGHT loc
shipped 4+ previously
Unit Test
Anti-Patterns
#pracunittests
1/4: The Opaque Anti-Pattern
types: light grey
names: bold dark grey
literals: red
else: dark grey
#pracunittests
// in LinearDescriptionFixture…
void testBackwardsToNormalLeftwardsGradient() {
LinearDescription leftDesc = DefaultFlatLinearDescription()
.withGradient(1f).withInitialValue(10.0f).withOffsetOrigin(0.0f)
.withDirection(Direction.eLeft);
TEST_GREATER(leftDesc.getValueAtOffset(-1.0f), 10.0f);
}
1/4: The Opaque Anti-Pattern
types: light grey
names: bold dark grey
literals: red
else: dark grey
#pracunittests
// in LinearDescriptionFixture…
void testBackwardsToNormalLeftwardsGradient() {
LinearDescription leftDesc = DefaultFlatLinearDescription()
.withGradient(1f).withInitialValue(10.0f).withOffsetOrigin(0.0f)
.withDirection(Direction.eLeft);
TEST_GREATER(leftDesc.getValueAtOffset(-1.0f), 10.0f);
} wat
1/4: The Opaque Anti-Pattern
types: light grey
names: bold dark grey
literals: red
else: dark grey
#pracunittests
Opaque: Hard to see HOW
void testBackwardsToNormalLeftwardsGradient() {
LinearDescription leftDesc = DefaultFlatLinearDescription()
.withGradient(1f).withInitialValue(10.0f).withOffsetOrigin(0.0f)
.withDirection(Direction.eLeft);
TEST_GREATER(leftDesc.getValueAtOffset(-1.0f), 10.0f);
}
types: light grey
names: bold dark grey
literals: red
else: dark grey
#pracunittests
Opaque: Hard to see HOW
void testBackwardsToNormalLeftwardsGradient() {
LinearDescription leftDesc = DefaultFlatLinearDescription()
.withGradient(1f).withInitialValue(10.0f).withOffsetOrigin(0.0f)
.withDirection(Direction.eLeft);
TEST_GREATER(leftDesc.getValueAtOffset(-1.0f), 10.0f);
}
types: light grey
names: bold dark grey
literals: red
else: dark grey
#pracunittests
Opaque: Hard to see HOW
void testBackwardsToNormalLeftwardsGradient() {
LinearDescription leftDesc = DefaultFlatLinearDescription()
.withGradient(1f).withInitialValue(10.0f).withOffsetOrigin(0.0f)
.withDirection(Direction.eLeft);
TEST_GREATER(leftDesc.getValueAtOffset(-1.0f), 10.0f);
}
types: light grey
names: bold dark grey
literals: red
else: dark grey
#pracunittests
Opaque: No Magic Literals
break the rules when you need to!
#pracunittests
Opaque: No Magic Literals
void testBackwardsToNormalLeftwardsGradient() {
float someValueAtOrigin = 10.0f;
float someOrigin = 0.0f;
float positiveGradient = 1.0f;
float someLeftOfOrigin = someOrigin - 1.0f;
!
LinearDescription leftDesc = DefaultFlatLinearDescription()
.withGradient(positiveGradient).withInitialValue(someValueAtOrigin)
.withOffsetOrigin(someOrigin).withDirection(Direction.eLeft);
float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin);
TEST_GREATER(valueToLeft, someValueAtOrigin);
}
break the rules when you need to!
#pracunittests
Opaque: No Magic Literals
void testBackwardsToNormalLeftwardsGradient() {
float someValueAtOrigin = 10.0f;
float someOrigin = 0.0f;
float positiveGradient = 1.0f;
float someLeftOfOrigin = someOrigin - 1.0f;
!
LinearDescription leftDesc = DefaultFlatLinearDescription()
.withGradient(positiveGradient).withInitialValue(someValueAtOrigin)
.withOffsetOrigin(someOrigin).withDirection(Direction.eLeft);
float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin);
TEST_GREATER(valueToLeft, someValueAtOrigin);
}
break the rules when you need to!
#pracunittests
Opaque: No Magic Literals
void testBackwardsToNormalLeftwardsGradient() {
float someValueAtOrigin = 10.0f;
float someOrigin = 0.0f;
float positiveGradient = 1.0f;
float someLeftOfOrigin = someOrigin - 1.0f;
!
LinearDescription leftDesc = DefaultFlatLinearDescription()
.withGradient(positiveGradient).withInitialValue(someValueAtOrigin)
.withOffsetOrigin(someOrigin).withDirection(Direction.eLeft);
float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin);
TEST_GREATER(valueToLeft, someValueAtOrigin);
}
break the rules when you need to!
#pracunittests
Opaque: No Magic Literals
void testBackwardsToNormalLeftwardsGradient() {
float someValueAtOrigin = 10.0f;
float someOrigin = 0.0f;
float positiveGradient = 1.0f;
float someLeftOfOrigin = someOrigin - 1.0f;
!
LinearDescription leftDesc = DefaultFlatLinearDescription()
.withGradient(positiveGradient).withInitialValue(someValueAtOrigin)
.withOffsetOrigin(someOrigin).withDirection(Direction.eLeft);
float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin);
TEST_GREATER(valueToLeft, someValueAtOrigin);
}
break the rules when you need to!
#pracunittests
void testBackwardsToNormalLeftwardsGradient() {
float someValueAtOrigin = 10.0f;
float someOrigin = 0.0f;
float positiveGradient = 1.0f;
float someLeftOfOrigin = someOrigin - 1.0f;
!
LinearDescription leftDesc = DefaultFlatLinearDescription()
.withGradient(positiveGradient).withInitialValue(someValueAtOrigin)
.withOffsetOrigin(someOrigin).withDirection(Direction.eLeft);
float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin);
TEST_GREATER(valueToLeft, someValueAtOrigin);
}
#pracunittests
void testBackwardsToNormalLeftwardsGradient() {
float someValueAtOrigin = 10.0f;
float someOrigin = 0.0f;
float positiveGradient = 1.0f;
float someLeftOfOrigin = someOrigin - 1.0f;
!
LinearDescription leftDesc = DefaultFlatLinearDescription()
.withGradient(positiveGradient).withInitialValue(someValueAtOrigin)
.withOffsetOrigin(someOrigin).withDirection(Direction.eLeft);
float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin);
TEST_GREATER(valueToLeft, someValueAtOrigin);
}
#pracunittests
Opaque: Informative,
Consistent Test Name
#pracunittests
Opaque: Informative,
Consistent Test Name
void nameOfFunctionUnderTest_ContextOfTest_DesiredResultOfTest() {
#pracunittests
Opaque: Informative,
Consistent Test Name
void nameOfFunctionUnderTest_ContextOfTest_DesiredResultOfTest() {
void testBackwardsToNormalLeftwardsGradient() {
#pracunittests
Opaque: Informative,
Consistent Test Name
void nameOfFunctionUnderTest_ContextOfTest_DesiredResultOfTest() {
void
void withDirection_Left_InvertsGradient() {
#pracunittests
void withDirection_Left_InvertsGradient() {
float someValueAtOrigin = 10.0f;
float someOrigin = 0.0f;
float positiveGradient = 1.0f;
float someLeftOfOrigin = someOrigin - 1.0f;
!
LinearDescription leftDesc = DefaultFlatLinearDescription()
.withGradient(positiveGradient).withInitialValue(someValueAtOrigin)
.withOffsetOrigin(someOrigin).withDirection(Direction.eLeft);
float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin);
TEST_GREATER(valueToLeft, someValueAtOrigin);
}
#pracunittests
void withDirection_Left_InvertsGradient() {
float someValueAtOrigin = 10.0f;
float someOrigin = 0.0f;
float positiveGradient = 1.0f;
float someLeftOfOrigin = someOrigin - 1.0f;
!
LinearDescription leftDesc = DefaultFlatLinearDescription()
.withGradient(positiveGradient).withInitialValue(someValueAtOrigin)
.withOffsetOrigin(someOrigin).withDirection(Direction.eLeft);
float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin);
TEST_GREATER(valueToLeft, someValueAtOrigin);
}
#pracunittests
Opaque: Arrange-Act-Assert
#pracunittests
Opaque: Arrange-Act-Assert
void withDirection_Left_InvertsGradient() {
float someValueAtOrigin = 10.0f;
float someOrigin = 0.0f;
float positiveGradient = 1.0f;
LinearDescription increasingDesc = DefaultFlatLinearDescription()
.withGradient(positiveGradient).withInitialValue(someValueAtOrigin)
.withOffsetOrigin(someOrigin);
!
LinearDescription leftDesc = increasingDesc.withDirection(Direction.eLeft);
!
float someLeftOfOrigin = someOrigin - 1.0f;
float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin);
TEST_GREATER(valueToLeft, someValueAtOrigin);
}
#pracunittests
Opaque: Arrange-Act-Assert
void withDirection_Left_InvertsGradient() {
float someValueAtOrigin = 10.0f;
float someOrigin = 0.0f;
float positiveGradient = 1.0f;
LinearDescription increasingDesc = DefaultFlatLinearDescription()
.withGradient(positiveGradient).withInitialValue(someValueAtOrigin)
.withOffsetOrigin(someOrigin);
!
LinearDescription leftDesc = increasingDesc.withDirection(Direction.eLeft);
!
float someLeftOfOrigin = someOrigin - 1.0f;
float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin);
TEST_GREATER(valueToLeft, someValueAtOrigin);
}
#pracunittests
Opaque: Arrange-Act-Assert
void withDirection_Left_InvertsGradient() {
float someValueAtOrigin = 10.0f;
float someOrigin = 0.0f;
float positiveGradient = 1.0f;
LinearDescription increasingDesc = DefaultFlatLinearDescription()
.withGradient(positiveGradient).withInitialValue(someValueAtOrigin)
.withOffsetOrigin(someOrigin);
!
LinearDescription leftDesc = increasingDesc.withDirection(Direction.eLeft);
!
float someLeftOfOrigin = someOrigin - 1.0f;
float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin);
TEST_GREATER(valueToLeft, someValueAtOrigin);
}
#pracunittests
Opaque: Arrange-Act-Assert
void withDirection_Left_InvertsGradient() {
float someValueAtOrigin = 10.0f;
float someOrigin = 0.0f;
float positiveGradient = 1.0f;
LinearDescription increasingDesc = DefaultFlatLinearDescription()
.withGradient(positiveGradient).withInitialValue(someValueAtOrigin)
.withOffsetOrigin(someOrigin);
!
LinearDescription leftDesc = increasingDesc.withDirection(Direction.eLeft);
!
float someLeftOfOrigin = someOrigin - 1.0f;
float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin);
TEST_GREATER(valueToLeft, someValueAtOrigin);
}
#pracunittests
Opaque: Arrange-Act-Assert
void withDirection_Left_InvertsGradient() {
float someValueAtOrigin = 10.0f;
float someOrigin = 0.0f;
float positiveGradient = 1.0f;
LinearDescription increasingDesc = DefaultFlatLinearDescription()
.withGradient(positiveGradient).withInitialValue(someValueAtOrigin)
.withOffsetOrigin(someOrigin);
!
LinearDescription leftDesc = increasingDesc.withDirection(Direction.eLeft);
!
float someLeftOfOrigin = someOrigin - 1.0f;
float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin);
TEST_GREATER(valueToLeft, someValueAtOrigin);
}
#pracunittests
The Opaque Anti-Pattern
breathe at end!
#pracunittests
The Opaque Anti-Pattern
• Hard to see "how"?
breathe at end!
#pracunittests
The Opaque Anti-Pattern
• Hard to see "how"?
• Demystify magic literals
breathe at end!
#pracunittests
The Opaque Anti-Pattern
• Hard to see "how"?
• Demystify magic literals
• Consistent informative test name
breathe at end!
#pracunittests
The Opaque Anti-Pattern
• Hard to see "how"?
• Demystify magic literals
• Consistent informative test name
• Arrange-Act-Assert
breathe at end!
#pracunittests
2/4: The Wet Anti-Pattern
RacingLineOffsets.setSignedDistanceToRacingLine(RacingLine, float)
On refactoring
#pracunittests
RacingLineOffsets.setSignedDistanceToRacingLine(RacingLine, float, int)
2/4: The Wet Anti-Pattern
RacingLineOffsets
On refactoring
#pracunittests
RacingLineOffsets.setSignedDistanceToRacingLine(RacingLine, float, int)
2/4: The Wet Anti-Pattern
> Test library build failed with 235 error(s)
RacingLineOffsets
On refactoring
#pracunittests
void getIdealOffset_BeyondRacingLineEdge_ReturnsEdge() {
float someRacingLineRadius = 10.0f;
float someOffsetBeyondRacingLine = someRacingLineRadius + 1.0f;
!
RacingLineOffsets beyondOffsets = new RacingLineOffsets();
float leftRacingLineEdge =
-someRacingLineRadius - someOffsetBeyondRacingLine;
beyondOffsets.setSignedDistanceToRacingLine(
RacingLine.eLeftEdge, leftRacingLineEdge);
beyondOffsets.setSignedDistanceToRacingLine(
RacingLine.eCenter, -someOffsetBeyondRacingLine);
float rightRacingLineEdge =
someRacingLineRadius - someOffsetBeyondRacingLine;
beyondOffsets.setSignedDistanceToRacingLine(
RacingLine.eRightEdge, rightRacingLineEdge);
!
OffsetRequest idealRequest =
m_raceBehaviour.getIdealOffset(beyondOffsets);
!
float idealRacingLineOffset = idealRequest.GetOffset();
TEST_EQUAL(idealRacingLineOffset, someRacingLineRadius);
}
void getIdealOffset_WithinRacingLineEdge_ReturnsOffset() {
float someRacingLineRadius = 10.0f;
float someOffsetWithinRadius = someRacingLineRadius * 0.8f;
!
RacingLineOffsets withinOffsets = new RacingLineOffsets();
float leftRacingLineEdge =
-someRacingLineRadius - someOffsetWithinRadius;
withinOfffsets.setSignedDistanceToRacingLine(
RacingLine.eLeftEdge, leftRacingLineEdge);
withinOffsets.setSignedDistanceToRacingLine(
RacingLine.eCenter, -someOffsetWithinRadius);
float rightRacingLineEdge =
someRacingLineRadius - someOffsetWithinRadius;
withinOffsets.setSignedDistanceToRacingLine(
RacingLine.eRightEdge, rightRacingLineEdge);
!
OffsetRequest idealRequest =
m_raceBehaviour.getIdealOffset(withinOffsets);
!
float idealRacingLineOffset = idealRequest.GetOffset();
TEST_EQUAL(idealRacingLineOffset, someOffsetWithinRadius);
}
#pracunittests
void getIdealOffset_BeyondRacingLineEdge_ReturnsEdge() {
float someRacingLineRadius = 10.0f;
float someOffsetBeyondRacingLine = someRacingLineRadius + 1.0f;
!
RacingLineOffsets beyondOffsets = new RacingLineOffsets();
float leftRacingLineEdge =
-someRacingLineRadius - someOffsetBeyondRacingLine;
beyondOffsets.setSignedDistanceToRacingLine(
RacingLine.eLeftEdge, leftRacingLineEdge);
beyondOffsets.setSignedDistanceToRacingLine(
RacingLine.eCenter, -someOffsetBeyondRacingLine);
float rightRacingLineEdge =
someRacingLineRadius - someOffsetBeyondRacingLine;
beyondOffsets.setSignedDistanceToRacingLine(
RacingLine.eRightEdge, rightRacingLineEdge);
!
OffsetRequest idealRequest =
m_raceBehaviour.getIdealOffset(beyondOffsets);
!
float idealRacingLineOffset = idealRequest.GetOffset();
TEST_EQUAL(idealRacingLineOffset, someRacingLineRadius);
}
void getIdealOffset_WithinRacingLineEdge_ReturnsOffset() {
float someRacingLineRadius = 10.0f;
float someOffsetWithinRadius = someRacingLineRadius * 0.8f;
!
RacingLineOffsets withinOffsets = new RacingLineOffsets();
float leftRacingLineEdge =
-someRacingLineRadius - someOffsetWithinRadius;
withinOfffsets.setSignedDistanceToRacingLine(
RacingLine.eLeftEdge, leftRacingLineEdge);
withinOffsets.setSignedDistanceToRacingLine(
RacingLine.eCenter, -someOffsetWithinRadius);
float rightRacingLineEdge =
someRacingLineRadius - someOffsetWithinRadius;
withinOffsets.setSignedDistanceToRacingLine(
RacingLine.eRightEdge, rightRacingLineEdge);
!
OffsetRequest idealRequest =
m_raceBehaviour.getIdealOffset(withinOffsets);
!
float idealRacingLineOffset = idealRequest.GetOffset();
TEST_EQUAL(idealRacingLineOffset, someOffsetWithinRadius);
}
Not DRY
#pracunittests
Wet: Helper Functions
void getIdealOffset_WithinRacingLineEdge_ReturnsOffset() {
float someRacingLineRadius = 10.0f;
float someOffsetWithinRadius =
someRacingLineRadius * 0.8f;
!
RacingLineOffsets withinOffsets = CreateRacingLineOffsets(
someRacingLineRadius, someOffsetWithinRadius);
!
OffsetRequest idealRequest =
m_raceBehaviour.getIdealOffset(withinOffsets);
!
float idealRacingLineOffset = idealRequest.GetOffset();
TEST_EQUAL(
idealRacingLineOffset, someOffsetWithinRadius);
}
void getIdealOffset_BeyondRacingLineEdge_ReturnsEdge() {
float someRacingLineRadius = 10.0f;
float someOffsetBeyondRacingLine =
someRacingLineRadius + 1.0f;
!
RacingLineOffsets beyondOffsets = CreateRacingLineOffsets(
someRacingLineRadius, someOffsetBeyondRacingLine);
!
OffsetRequest idealRequest =
m_raceBehaviour.getIdealOffset(beyondOffsets);
!
float idealRacingLineOffset = idealRequest.GetOffset();
TEST_EQUAL(
idealRacingLineOffset, withinOffsets.RightEdge);
}
#pracunittests
Wet: Helper Functions
void getIdealOffset_WithinRacingLineEdge_ReturnsOffset() {
float someRacingLineRadius = 10.0f;
float someOffsetWithinRadius =
someRacingLineRadius * 0.8f;
!
RacingLineOffsets withinOffsets = CreateRacingLineOffsets(
someRacingLineRadius, someOffsetWithinRadius);
!
OffsetRequest idealRequest =
m_raceBehaviour.getIdealOffset(withinOffsets);
!
float idealRacingLineOffset = idealRequest.GetOffset();
TEST_EQUAL(
idealRacingLineOffset, someOffsetWithinRadius);
}
void getIdealOffset_BeyondRacingLineEdge_ReturnsEdge() {
float someRacingLineRadius = 10.0f;
float someOffsetBeyondRacingLine =
someRacingLineRadius + 1.0f;
!
RacingLineOffsets beyondOffsets = CreateRacingLineOffsets(
someRacingLineRadius, someOffsetBeyondRacingLine);
!
OffsetRequest idealRequest =
m_raceBehaviour.getIdealOffset(beyondOffsets);
!
float idealRacingLineOffset = idealRequest.GetOffset();
TEST_EQUAL(
idealRacingLineOffset, withinOffsets.RightEdge);
}
#pracunittests
Wet: Helper Functions
void getIdealOffset_WithinRacingLineEdge_ReturnsOffset() {
float someRacingLineRadius = 10.0f;
float someOffsetWithinRadius =
someRacingLineRadius * 0.8f;
!
RacingLineOffsets withinOffsets = CreateRacingLineOffsets(
someRacingLineRadius, someOffsetWithinRadius);
!
OffsetRequest idealRequest =
m_raceBehaviour.getIdealOffset(withinOffsets);
!
float idealRacingLineOffset = idealRequest.GetOffset();
TEST_EQUAL(
idealRacingLineOffset, someOffsetWithinRadius);
}
void getIdealOffset_BeyondRacingLineEdge_ReturnsEdge() {
float someRacingLineRadius = 10.0f;
float someOffsetBeyondRacingLine =
someRacingLineRadius + 1.0f;
!
RacingLineOffsets beyondOffsets = CreateRacingLineOffsets(
someRacingLineRadius, someOffsetBeyondRacingLine);
!
OffsetRequest idealRequest =
m_raceBehaviour.getIdealOffset(beyondOffsets);
!
float idealRacingLineOffset = idealRequest.GetOffset();
TEST_EQUAL(
idealRacingLineOffset, withinOffsets.RightEdge);
}
#pracunittests
Wet: Helper Functions
void getIdealOffset_WithinRacingLineEdge_ReturnsOffset() {
float someRacingLineRadius = 10.0f;
float someOffsetWithinRadius =
someRacingLineRadius * 0.8f;
!
RacingLineOffsets withinOffsets = CreateRacingLineOffsets(
someRacingLineRadius, someOffsetWithinRadius);
!
OffsetRequest idealRequest =
m_raceBehaviour.getIdealOffset(withinOffsets);
!
test_tauOffsetEqual(idealRequest, someOffsetWithinRadius);
}
void getIdealOffset_BeyondRacingLineEdge_ReturnsEdge() {
float someRacingLineRadius = 10.0f;
float someOffsetBeyondRacingLine =
someRacingLineRadius + 1.0f;
!
RacingLineOffsets beyondOffsets = CreateRacingLineOffsets(
someRacingLineRadius, someOffsetBeyondRacingLine);
!
OffsetRequest idealRequest =
m_raceBehaviour.getIdealOffset(beyondOffsets);
!
test_racingLineOffsetEqual(
idealRequest, withinOffsets.RightEdge);
}
#pracunittests
Wet: Helper Functions
void getIdealOffset_WithinRacingLineEdge_ReturnsOffset() {
float someRacingLineRadius = 10.0f;
float someOffsetWithinRadius =
someRacingLineRadius * 0.8f;
!
RacingLineOffsets withinOffsets = CreateRacingLineOffsets(
someRacingLineRadius, someOffsetWithinRadius);
!
OffsetRequest idealRequest =
m_raceBehaviour.getIdealOffset(withinOffsets);
!
test_tauOffsetEqual(idealRequest, someOffsetWithinRadius);
}
void getIdealOffset_BeyondRacingLineEdge_ReturnsEdge() {
float someRacingLineRadius = 10.0f;
float someOffsetBeyondRacingLine =
someRacingLineRadius + 1.0f;
!
RacingLineOffsets beyondOffsets = CreateRacingLineOffsets(
someRacingLineRadius, someOffsetBeyondRacingLine);
!
OffsetRequest idealRequest =
m_raceBehaviour.getIdealOffset(beyondOffsets);
!
test_racingLineOffsetEqual(
idealRequest, withinOffsets.RightEdge);
}
#pracunittests
The Wet Anti-Pattern
breathe at end!
#pracunittests
The Wet Anti-Pattern
• Hard-to-maintain hacky tests?
breathe at end!
#pracunittests
The Wet Anti-Pattern
• Hard-to-maintain hacky tests?
• Keep production sensibilities in unit test code
breathe at end!
#pracunittests
The Wet Anti-Pattern
• Hard-to-maintain hacky tests?
• Keep production sensibilities in unit test code
• Stay DRY with helper functions and custom
asserts
breathe at end!
#pracunittests
The Wet Anti-Pattern
• Hard-to-maintain hacky tests?
• Keep production sensibilities in unit test code
• Stay DRY with helper functions and custom
asserts
• Do not hide the call to the function under test
breathe at end!
#pracunittests
3/4: The Deep Anti-Pattern
> Test failed:
> getOwnerAtOffset_WithOwner_ReferencesOwnerAtManyOffsets
> With Assert: VehicleID 0 != 1
on introducing a bug
chi == “kai”!
#pracunittests
void getOwnerAtOffset_WithOwner_ReferencesOwnerAtManyOffsets() {
VehicleID someOwnerID = (VehicleID)1;
LinearDescription ownedDescription =
DefaultFlatLinearDescription().withOwner(someOwnerID);
!
float someNegativeOffset = -1.0f;
float ownerAtNegativeOffset =
ownedDescription.getOwnerAtOffset(someNegativeOffset);
float somePositiveOffset = 1.0f;
float ownerAtPositiveOffset =
ownedDescription.getOwnerAtOffset(somePositiveOffset);
!
test_OwningVehicleIsEqualTo(ownerAtNegativeOffset, someOwnerID);
test_OwningVehicleIsEqualTo(ownerAtPositiveOffset, someOwnerID);
}
make it clear
#pracunittests
void getOwnerAtOffset_WithOwner_ReferencesOwnerAtManyOffsets() {
VehicleID someOwnerID = (VehicleID)1;
LinearDescription ownedDescription =
DefaultFlatLinearDescription().withOwner(someOwnerID);
!
float someNegativeOffset = -1.0f;
float ownerAtNegativeOffset =
ownedDescription.getOwnerAtOffset(someNegativeOffset);
float somePositiveOffset = 1.0f;
float ownerAtPositiveOffset =
ownedDescription.getOwnerAtOffset(somePositiveOffset);
!
test_OwningVehicleIsEqualTo(ownerAtNegativeOffset, someOwnerID);
test_OwningVehicleIsEqualTo(ownerAtPositiveOffset, someOwnerID);
}
make it clear
#pracunittests
void getOwnerAtOffset_WithOwner_ReferencesOwnerAtManyOffsets() {
VehicleID someOwnerID = (VehicleID)1;
LinearDescription ownedDescription =
DefaultFlatLinearDescription().withOwner(someOwnerID);
!
float someNegativeOffset = -1.0f;
float ownerAtNegativeOffset =
ownedDescription.getOwnerAtOffset(someNegativeOffset);
float somePositiveOffset = 1.0f;
float ownerAtPositiveOffset =
ownedDescription.getOwnerAtOffset(somePositiveOffset);
!
test_OwningVehicleIsEqualTo(ownerAtNegativeOffset, someOwnerID);
test_OwningVehicleIsEqualTo(ownerAtPositiveOffset, someOwnerID);
}
make it clear
#pracunittests
void getOwnerAtOffset_WithOwner_ReferencesOwnerAtManyOffsets() {
VehicleID someOwnerID = (VehicleID)1;
LinearDescription ownedDescription =
DefaultFlatLinearDescription().withOwner(someOwnerID);
!
float someNegativeOffset = -1.0f;
float ownerAtNegativeOffset =
ownedDescription.getOwnerAtOffset(someNegativeOffset);
float somePositiveOffset = 1.0f;
float ownerAtPositiveOffset =
ownedDescription.getOwnerAtOffset(somePositiveOffset);
!
test_OwningVehicleIsEqualTo(ownerAtNegativeOffset, someOwnerID);
test_OwningVehicleIsEqualTo(ownerAtPositiveOffset, someOwnerID);
}
>1 explicit
assumption
make it clear
#pracunittests
Deep: One Assert Per Test
void getOwnerAtOffset_WithOwnerAndNegativeOffset_ReturnsOwner() {
float someNegativeOffset = -1.0f;
!
float ownerAtNegativeOffset =
m_ownedDescription.getOwnerAtOffset(someNegativeOffset);
!
test_OwningVehicleIsEqualTo(ownerAtNegativeOffset, m_someOwnerID);
}
!
void getOwnerAtOffset_WithOwnerAndPositiveOffset_ReturnsOwner() {
// *snip*
}
#pracunittests
> Test failed:
> getOwnerAtOffset_WithOwnerAndNegativeOffset_ReturnsOwner
> With Assert: VehicleID 0 != 1
> Test failed:
> getOwnerAtOffset_WithOwnerAndPositiveOffset_ReturnsOwner
> With Assert: VehicleID 0 != 1
#pracunittests
The Deep Anti-Pattern
breathe at end!
#pracunittests
The Deep Anti-Pattern
• Test failures not fully informative?
breathe at end!
#pracunittests
The Deep Anti-Pattern
• Test failures not fully informative?
• Too many explicit assumptions per test
breathe at end!
#pracunittests
The Deep Anti-Pattern
• Test failures not fully informative?
• Too many explicit assumptions per test
• Minimise assumptions per test
breathe at end!
#pracunittests
4/4: The Wide Anti-Pattern
On introducing a bug in behaviour system
#pracunittests
4/4: The Wide Anti-Pattern
> Executed 613 test(s), 599 test(s) passed, 14 test(s) failed.
On introducing a bug in behaviour system
#pracunittests
// in DraftBehaviourFixture…
void updateImpl_WithCarToDraft_PushesAIToBehindCar() {
WorldInfo worldInfo = new WorldInfo();
float someDraftTargetVehicleOffset = 2.0f;
// *snip* blackboard setup
!
DraftBehaviour draft = new DraftBehaviour();
!
BehaviourSystem behaviourSystem = new BehaviourSystem();
behaviourSystem.addBehaviour(draft);
!
behaviourSystem.updateWithBlackboard(worldInfo);
!
MovementRequest draftMovement = behaviourSystem.getMovementRequest();
TEST_EQUAL(draftMovement.desiredRacingLineOffset, someDraftTargetVehicleOffset);
}
#pracunittests
// in DraftBehaviourFixture…
void updateImpl_WithCarToDraft_PushesAIToBehindCar() {
WorldInfo worldInfo = new WorldInfo();
float someDraftTargetVehicleOffset = 2.0f;
// *snip* blackboard setup
!
DraftBehaviour draft = new DraftBehaviour();
!
BehaviourSystem behaviourSystem = new BehaviourSystem();
behaviourSystem.addBehaviour(draft);
!
behaviourSystem.updateWithBlackboard(worldInfo);
!
MovementRequest draftMovement = behaviourSystem.getMovementRequest();
TEST_EQUAL(draftMovement.desiredRacingLineOffset, someDraftTargetVehicleOffset);
}
#pracunittests
// in DraftBehaviourFixture…
void updateImpl_WithCarToDraft_PushesAIToBehindCar() {
WorldInfo worldInfo = new WorldInfo();
float someDraftTargetVehicleOffset = 2.0f;
// *snip* blackboard setup
!
DraftBehaviour draft = new DraftBehaviour();
!
BehaviourSystem behaviourSystem = new BehaviourSystem();
behaviourSystem.addBehaviour(draft);
!
behaviourSystem.updateWithBlackboard(worldInfo);
!
MovementRequest draftMovement = behaviourSystem.getMovementRequest();
TEST_EQUAL(draftMovement.desiredRacingLineOffset, someDraftTargetVehicleOffset);
}
#pracunittests
// in DraftBehaviourFixture…
void updateImpl_WithCarToDraft_PushesAIToBehindCar() {
WorldInfo worldInfo = new WorldInfo();
float someDraftTargetVehicleOffset = 2.0f;
// *snip* blackboard setup
!
DraftBehaviour draft = new DraftBehaviour();
!
BehaviourSystem behaviourSystem = new BehaviourSystem();
behaviourSystem.addBehaviour(draft);
!
behaviourSystem.updateWithBlackboard(worldInfo);
!
MovementRequest draftMovement = behaviourSystem.getMovementRequest();
TEST_EQUAL(draftMovement.desiredRacingLineOffset, someDraftTargetVehicleOffset);
}
>0 implicit
assumptions
#pracunittests
// in DraftBehaviourFixture…
void updateImpl_WithCarToDraft_PushesAIToBehindCar() {
WorldInfo worldInfo = new WorldInfo();
float someDraftTargetVehicleOffset = 2.0f;
// *snip* blackboard setup
!
DraftBehaviour draft = new DraftBehaviour();
!
BehaviourSystem behaviourSystem = new BehaviourSystem();
behaviourSystem.addBehaviour(draft);
!
behaviourSystem.updateWithBlackboard(worldInfo);
!
MovementRequest draftMovement = behaviourSystem.getMovementRequest();
TEST_EQUAL(draftMovement.desiredRacingLineOffset, someDraftTargetVehicleOffset);
}
#pracunittests
// in DraftBehaviourFixture…
void updateImpl_WithCarToDraft_PushesAIToBehindCar() {
WorldInfo worldInfo = new WorldInfo();
float someDraftTargetVehicleOffset = 2.0f;
// *snip* blackboard setup
!
DraftBehaviour draft = new DraftBehaviour();
!
BehaviourSystem behaviourSystem = new BehaviourSystem();
behaviourSystem.addBehaviour(draft);
!
behaviourSystem.updateWithBlackboard(worldInfo);
!
MovementRequest draftMovement = behaviourSystem.getMovementRequest();
TEST_EQUAL(draftMovement.desiredRacingLineOffset, someDraftTargetVehicleOffset);
}
#pracunittests
// in DraftBehaviourFixture…
void updateImpl_WithCarToDraft_PushesAIToBehindCar() {
WorldInfo worldInfo = new WorldInfo();
float someDraftTargetVehicleOffset = 2.0f;
// *snip* blackboard setup
!
DraftBehaviour draft = new DraftBehaviour();
!
BehaviourSystem behaviourSystem = new BehaviourSystem();
behaviourSystem.addBehaviour(draft);
!
behaviourSystem.updateWithBlackboard(worldInfo);
!
MovementRequest draftMovement = behaviourSystem.getMovementRequest();
TEST_EQUAL(draftMovement.desiredRacingLineOffset, someDraftTargetVehicleOffset);
}
#pracunittests
// in DraftBehaviourFixture…
void updateImpl_WithCarToDraft_PushesAIToBehindCar() {
WorldInfo worldInfo = new WorldInfo();
float someDraftTargetVehicleOffset = 2.0f;
// *snip* blackboard setup
!
DraftBehaviour draft = new DraftBehaviour();
!
BehaviourSystem behaviourSystem = new BehaviourSystem();
behaviourSystem.addBehaviour(draft);
!
behaviourSystem.updateWithBlackboard(worldInfo);
!
MovementRequest draftMovement = behaviourSystem.getMovementRequest();
TEST_EQUAL(draftMovement.desiredRacingLineOffset, someDraftTargetVehicleOffset);
}
#pracunittests
Wide: Seams
void DraftBehaviour.updateImpl(WorldInfo wi, HeatMap heatInOut);
Game Code
Inversion of Control
Interfaces/virtual functions
Mocking frameworks/helper classes
#pracunittests
Wide: Seams
void DraftBehaviour.updateImpl(WorldInfo wi, HeatMap heatInOut);
class HeatMap { virtual void WriteHeat(float offset, float value) { … } }
Game Code
Inversion of Control
Interfaces/virtual functions
Mocking frameworks/helper classes
#pracunittests
Wide: Seams
void DraftBehaviour.updateImpl(WorldInfo wi, HeatMap heatInOut);
class HeatMap { virtual void WriteHeat(float offset, float value) { … } }
class MockHeatMap : HeatMap { override void WriteHeat(float offset, float value) { … } }
Game Code
Test Library
Inversion of Control
Interfaces/virtual functions
Mocking frameworks/helper classes
#pracunittests
Wide: Seams
void DraftBehaviour.updateImpl(WorldInfo wi, HeatMap heatInOut);
class HeatMap { virtual void WriteHeat(float offset, float value) { … } }
class MockHeatMap : HeatMap { override void WriteHeat(float offset, float value) { … } }
Game Code
Test Library
Inversion of Control
Interfaces/virtual functions
Mocking frameworks/helper classes
#pracunittests
Wide: MockHeatMapvoid updateImpl_WithCarToDraft_PushesAIToBehindCar() {
WorldInfo worldInfo = new WorldInfo();
float someDraftTargetVehicleOffset = 2.0f;
// *snip* blackboard setup
!
DraftBehaviour draft = new DraftBehaviour();
MockHeatMap mockMap = new MockHeatMap();
!
draft.updateImpl(worldInfo, mockMap);
!
float draftOffsetWithHighestHeat = mockMap.getHighestHeatOffset();
TEST_EQUAL(draftOffsetWithHighestHeat, someDraftTargetVehicleOffset);
}
#pracunittests
Wide: MockHeatMapvoid updateImpl_WithCarToDraft_PushesAIToBehindCar() {
WorldInfo worldInfo = new WorldInfo();
float someDraftTargetVehicleOffset = 2.0f;
// *snip* blackboard setup
!
DraftBehaviour draft = new DraftBehaviour();
MockHeatMap mockMap = new MockHeatMap();
!
draft.updateImpl(worldInfo, mockMap);
!
float draftOffsetWithHighestHeat = mockMap.getHighestHeatOffset();
TEST_EQUAL(draftOffsetWithHighestHeat, someDraftTargetVehicleOffset);
}
#pracunittests
Wide: MockHeatMapvoid updateImpl_WithCarToDraft_PushesAIToBehindCar() {
WorldInfo worldInfo = new WorldInfo();
float someDraftTargetVehicleOffset = 2.0f;
// *snip* blackboard setup
!
DraftBehaviour draft = new DraftBehaviour();
MockHeatMap mockMap = new MockHeatMap();
!
draft.updateImpl(worldInfo, mockMap);
!
float draftOffsetWithHighestHeat = mockMap.getHighestHeatOffset();
TEST_EQUAL(draftOffsetWithHighestHeat, someDraftTargetVehicleOffset);
}
#pracunittests
The Wide Anti-Pattern
breathe at end!
#pracunittests
The Wide Anti-Pattern
• False-negative test failures?
breathe at end!
#pracunittests
The Wide Anti-Pattern
• False-negative test failures?
• Many implicit assumptions
breathe at end!
#pracunittests
The Wide Anti-Pattern
• False-negative test failures?
• Many implicit assumptions
• Isolate code with seams, to enable
simple fake impostors
breathe at end!
#pracunittests
Recap
Respect unit test quality as much as production code quality
Write once, read many
Only 1 explicit assumption
As few as possible implicit assumptions
#pracunittests
Recap
• Respect unit test source code as much as
production source code
Respect unit test quality as much as production code quality
Write once, read many
Only 1 explicit assumption
As few as possible implicit assumptions
#pracunittests
Recap
• Respect unit test source code as much as
production source code
• Write once, read many
Respect unit test quality as much as production code quality
Write once, read many
Only 1 explicit assumption
As few as possible implicit assumptions
#pracunittests
Recap
• Respect unit test source code as much as
production source code
• Write once, read many
• Only 1 explicit assumption
Respect unit test quality as much as production code quality
Write once, read many
Only 1 explicit assumption
As few as possible implicit assumptions
#pracunittests
Recap
• Respect unit test source code as much as
production source code
• Write once, read many
• Only 1 explicit assumption
• Minimise implicit assumptions
Respect unit test quality as much as production code quality
Write once, read many
Only 1 explicit assumption
As few as possible implicit assumptions
#pracunittests
• andrew.fray@gmail.com
• @tenpn
• andrewfray.wordpress.com
• Roy Osherove: Art of Unit Testing
www.artofunittesting.com
• Michael Feathers: Working
Effectively with Legacy Code
• Steve Freeman & Nat Pryce:
Growing Object-Orientated
Software, Guided By Tests
Colour scheme by Miaka www.colourlovers.com/palette/444487/Curiosity_Killed
Feedback!
Thank the CAs!

More Related Content

What's hot

Exception Handling: Designing Robust Software in Ruby
Exception Handling: Designing Robust Software in RubyException Handling: Designing Robust Software in Ruby
Exception Handling: Designing Robust Software in Ruby
Wen-Tien Chang
 
Py.test
Py.testPy.test
Py.test
soasme
 
Python testing using mock and pytest
Python testing using mock and pytestPython testing using mock and pytest
Python testing using mock and pytest
Suraj Deshmukh
 
Effective testing with pytest
Effective testing with pytestEffective testing with pytest
Effective testing with pytest
Hector Canto
 
Testing in-python-and-pytest-framework
Testing in-python-and-pytest-frameworkTesting in-python-and-pytest-framework
Testing in-python-and-pytest-framework
Arulalan T
 
Automated Testing in Django
Automated Testing in DjangoAutomated Testing in Django
Automated Testing in Django
Loek van Gent
 
Testing in Django
Testing in DjangoTesting in Django
Testing in Django
Kevin Harvey
 
Testing untestable code - DPC10
Testing untestable code - DPC10Testing untestable code - DPC10
Testing untestable code - DPC10
Stephan Hochdörfer
 
Integration testing with spring @JAX Mainz
Integration testing with spring @JAX MainzIntegration testing with spring @JAX Mainz
Integration testing with spring @JAX Mainz
Victor Rentea
 
Frederick web meetup slides
Frederick web meetup slidesFrederick web meetup slides
Frederick web meetup slides
Pat Zearfoss
 
Pure functions and immutable objects @dev nexus 2021
Pure functions and immutable objects @dev nexus 2021Pure functions and immutable objects @dev nexus 2021
Pure functions and immutable objects @dev nexus 2021
Victor Rentea
 
Google mock for dummies
Google mock for dummiesGoogle mock for dummies
Google mock for dummies
Harry Potter
 
Testing most things in JavaScript - LeedsJS 31/05/2017
Testing most things in JavaScript - LeedsJS 31/05/2017Testing most things in JavaScript - LeedsJS 31/05/2017
Testing most things in JavaScript - LeedsJS 31/05/2017
Colin Oakley
 
Unit testing of java script and angularjs application using Karma Jasmine Fra...
Unit testing of java script and angularjs application using Karma Jasmine Fra...Unit testing of java script and angularjs application using Karma Jasmine Fra...
Unit testing of java script and angularjs application using Karma Jasmine Fra...
Samyak Bhalerao
 
Unit Test in Ruby on Rails by Minitest
Unit Test in Ruby on Rails by MinitestUnit Test in Ruby on Rails by Minitest
Unit Test in Ruby on Rails by Minitest
Hosang Jeon
 
How Testability Inspires AngularJS Design / Ran Mizrahi
How Testability Inspires AngularJS Design / Ran MizrahiHow Testability Inspires AngularJS Design / Ran Mizrahi
How Testability Inspires AngularJS Design / Ran Mizrahi
Ran Mizrahi
 
Exceptable
ExceptableExceptable
Exceptable
Aurynn Shaw
 
Angular Unit Testing from the Trenches
Angular Unit Testing from the TrenchesAngular Unit Testing from the Trenches
Angular Unit Testing from the Trenches
Justin James
 
Angular Unit Testing NDC Minn 2018
Angular Unit Testing NDC Minn 2018Angular Unit Testing NDC Minn 2018
Angular Unit Testing NDC Minn 2018
Justin James
 
OCP Java SE 8 Exam - Sample Questions - Java Streams API
OCP Java SE 8 Exam - Sample Questions - Java Streams APIOCP Java SE 8 Exam - Sample Questions - Java Streams API
OCP Java SE 8 Exam - Sample Questions - Java Streams API
Ganesh Samarthyam
 

What's hot (20)

Exception Handling: Designing Robust Software in Ruby
Exception Handling: Designing Robust Software in RubyException Handling: Designing Robust Software in Ruby
Exception Handling: Designing Robust Software in Ruby
 
Py.test
Py.testPy.test
Py.test
 
Python testing using mock and pytest
Python testing using mock and pytestPython testing using mock and pytest
Python testing using mock and pytest
 
Effective testing with pytest
Effective testing with pytestEffective testing with pytest
Effective testing with pytest
 
Testing in-python-and-pytest-framework
Testing in-python-and-pytest-frameworkTesting in-python-and-pytest-framework
Testing in-python-and-pytest-framework
 
Automated Testing in Django
Automated Testing in DjangoAutomated Testing in Django
Automated Testing in Django
 
Testing in Django
Testing in DjangoTesting in Django
Testing in Django
 
Testing untestable code - DPC10
Testing untestable code - DPC10Testing untestable code - DPC10
Testing untestable code - DPC10
 
Integration testing with spring @JAX Mainz
Integration testing with spring @JAX MainzIntegration testing with spring @JAX Mainz
Integration testing with spring @JAX Mainz
 
Frederick web meetup slides
Frederick web meetup slidesFrederick web meetup slides
Frederick web meetup slides
 
Pure functions and immutable objects @dev nexus 2021
Pure functions and immutable objects @dev nexus 2021Pure functions and immutable objects @dev nexus 2021
Pure functions and immutable objects @dev nexus 2021
 
Google mock for dummies
Google mock for dummiesGoogle mock for dummies
Google mock for dummies
 
Testing most things in JavaScript - LeedsJS 31/05/2017
Testing most things in JavaScript - LeedsJS 31/05/2017Testing most things in JavaScript - LeedsJS 31/05/2017
Testing most things in JavaScript - LeedsJS 31/05/2017
 
Unit testing of java script and angularjs application using Karma Jasmine Fra...
Unit testing of java script and angularjs application using Karma Jasmine Fra...Unit testing of java script and angularjs application using Karma Jasmine Fra...
Unit testing of java script and angularjs application using Karma Jasmine Fra...
 
Unit Test in Ruby on Rails by Minitest
Unit Test in Ruby on Rails by MinitestUnit Test in Ruby on Rails by Minitest
Unit Test in Ruby on Rails by Minitest
 
How Testability Inspires AngularJS Design / Ran Mizrahi
How Testability Inspires AngularJS Design / Ran MizrahiHow Testability Inspires AngularJS Design / Ran Mizrahi
How Testability Inspires AngularJS Design / Ran Mizrahi
 
Exceptable
ExceptableExceptable
Exceptable
 
Angular Unit Testing from the Trenches
Angular Unit Testing from the TrenchesAngular Unit Testing from the Trenches
Angular Unit Testing from the Trenches
 
Angular Unit Testing NDC Minn 2018
Angular Unit Testing NDC Minn 2018Angular Unit Testing NDC Minn 2018
Angular Unit Testing NDC Minn 2018
 
OCP Java SE 8 Exam - Sample Questions - Java Streams API
OCP Java SE 8 Exam - Sample Questions - Java Streams APIOCP Java SE 8 Exam - Sample Questions - Java Streams API
OCP Java SE 8 Exam - Sample Questions - Java Streams API
 

Viewers also liked

Monitoring and evaluation expert
Monitoring and evaluation expertMonitoring and evaluation expert
Monitoring and evaluation expert
Aavaaz Shab
 
Ccepresentation 2-101016010554-phpapp02
Ccepresentation 2-101016010554-phpapp02Ccepresentation 2-101016010554-phpapp02
Ccepresentation 2-101016010554-phpapp02
Palaxayya Hiremath
 
On a night of the full moon
On a night of the full moonOn a night of the full moon
On a night of the full moon
carrieriggs
 
Wall Event
Wall EventWall Event
Wall Event
UCalgaryMed
 
CHEF VANESSA BENISTY
CHEF  VANESSA BENISTYCHEF  VANESSA BENISTY
CHEF VANESSA BENISTY
harorentacar
 
On a night of the full moon
On a night of the full moonOn a night of the full moon
On a night of the full moon
carrieriggs
 
Studio bronx 20120210
Studio bronx 20120210Studio bronx 20120210
Studio bronx 20120210
pauspling2
 
Folio
FolioFolio
Folio
BrenoR
 
callistra roy
callistra roycallistra roy
callistra roy
Shelia Butler
 
sfdsfdsgfdhggfhfdgfd
sfdsfdsgfdhggfhfdgfdsfdsfdsgfdhggfhfdgfd
sfdsfdsgfdhggfhfdgfd
Jennifer Vieira
 
Lezing Robert Winkel - Mei architecten. Trainstorm 2030
Lezing Robert Winkel - Mei architecten. Trainstorm 2030Lezing Robert Winkel - Mei architecten. Trainstorm 2030
Lezing Robert Winkel - Mei architecten. Trainstorm 2030MeiArch
 
30928464 my-blue-brain-ppt
30928464 my-blue-brain-ppt30928464 my-blue-brain-ppt
30928464 my-blue-brain-ppt
Swathi Ranganathan
 
Remote Working at Spry Fox
Remote Working at Spry FoxRemote Working at Spry Fox
Remote Working at Spry Fox
Andrew Fray
 
Unit Testing and Test Driven Development in Unity3D
Unit Testing and Test Driven Development in Unity3DUnit Testing and Test Driven Development in Unity3D
Unit Testing and Test Driven Development in Unity3D
Andrew Fray
 
I cane
I caneI cane
I cane
vikipedia123
 
TV over the Internet: il video on-demand, Netflix e la concorrenza in Italia
TV over the Internet: il video on-demand, Netflix e la concorrenza in ItaliaTV over the Internet: il video on-demand, Netflix e la concorrenza in Italia
TV over the Internet: il video on-demand, Netflix e la concorrenza in Italia
Oscar Domenico Parisi
 

Viewers also liked (17)

Monitoring and evaluation expert
Monitoring and evaluation expertMonitoring and evaluation expert
Monitoring and evaluation expert
 
Ccepresentation 2-101016010554-phpapp02
Ccepresentation 2-101016010554-phpapp02Ccepresentation 2-101016010554-phpapp02
Ccepresentation 2-101016010554-phpapp02
 
On a night of the full moon
On a night of the full moonOn a night of the full moon
On a night of the full moon
 
Wall Event
Wall EventWall Event
Wall Event
 
CHEF VANESSA BENISTY
CHEF  VANESSA BENISTYCHEF  VANESSA BENISTY
CHEF VANESSA BENISTY
 
On a night of the full moon
On a night of the full moonOn a night of the full moon
On a night of the full moon
 
Studio bronx 20120210
Studio bronx 20120210Studio bronx 20120210
Studio bronx 20120210
 
Folio
FolioFolio
Folio
 
callistra roy
callistra roycallistra roy
callistra roy
 
sfdsfdsgfdhggfhfdgfd
sfdsfdsgfdhggfhfdgfdsfdsfdsgfdhggfhfdgfd
sfdsfdsgfdhggfhfdgfd
 
Lezing Robert Winkel - Mei architecten. Trainstorm 2030
Lezing Robert Winkel - Mei architecten. Trainstorm 2030Lezing Robert Winkel - Mei architecten. Trainstorm 2030
Lezing Robert Winkel - Mei architecten. Trainstorm 2030
 
Doc1
Doc1Doc1
Doc1
 
30928464 my-blue-brain-ppt
30928464 my-blue-brain-ppt30928464 my-blue-brain-ppt
30928464 my-blue-brain-ppt
 
Remote Working at Spry Fox
Remote Working at Spry FoxRemote Working at Spry Fox
Remote Working at Spry Fox
 
Unit Testing and Test Driven Development in Unity3D
Unit Testing and Test Driven Development in Unity3DUnit Testing and Test Driven Development in Unity3D
Unit Testing and Test Driven Development in Unity3D
 
I cane
I caneI cane
I cane
 
TV over the Internet: il video on-demand, Netflix e la concorrenza in Italia
TV over the Internet: il video on-demand, Netflix e la concorrenza in ItaliaTV over the Internet: il video on-demand, Netflix e la concorrenza in Italia
TV over the Internet: il video on-demand, Netflix e la concorrenza in Italia
 

Similar to Practical unit testing 2014

Testing sync engine
Testing sync engineTesting sync engine
Testing sync engine
Ilya Puchka
 
Testing smells
Testing smellsTesting smells
Testing smells
Sidu Ponnappa
 
DSR Testing (Part 1)
DSR Testing (Part 1)DSR Testing (Part 1)
DSR Testing (Part 1)
Steve Upton
 
Anatomy of a Gem: Bane
Anatomy of a Gem: BaneAnatomy of a Gem: Bane
Anatomy of a Gem: Bane
Daniel Wellman
 
Static Code Analysis PHP[tek] 2023
Static Code Analysis PHP[tek] 2023Static Code Analysis PHP[tek] 2023
Static Code Analysis PHP[tek] 2023
Scott Keck-Warren
 
Automated Frontend Testing
Automated Frontend TestingAutomated Frontend Testing
Automated Frontend Testing
Neil Crosby
 
The Many Ways to Test Your React App
The Many Ways to Test Your React AppThe Many Ways to Test Your React App
The Many Ways to Test Your React App
All Things Open
 
Kill the mutants - A better way to test your tests
Kill the mutants - A better way to test your testsKill the mutants - A better way to test your tests
Kill the mutants - A better way to test your tests
Roy van Rijn
 
Kill the mutants and test your tests - Roy van Rijn
Kill the mutants and test your tests - Roy van RijnKill the mutants and test your tests - Roy van Rijn
Kill the mutants and test your tests - Roy van Rijn
NLJUG
 
Functional Java 8 - Introduction
Functional Java 8 - IntroductionFunctional Java 8 - Introduction
Functional Java 8 - Introduction
Łukasz Biały
 
O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...
O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...
O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...
Rodolfo Carvalho
 
Jest: Frontend Testing richtig gemacht @WebworkerNRW
Jest: Frontend Testing richtig gemacht @WebworkerNRWJest: Frontend Testing richtig gemacht @WebworkerNRW
Jest: Frontend Testing richtig gemacht @WebworkerNRW
Holger Grosse-Plankermann
 
Test Driven Development - Workshop
Test Driven Development - WorkshopTest Driven Development - Workshop
Test Driven Development - Workshop
Anjana Somathilake
 
Unit Testing and Behavior Driven Testing with AngularJS - Jesse Liberty | Fal...
Unit Testing and Behavior Driven Testing with AngularJS - Jesse Liberty | Fal...Unit Testing and Behavior Driven Testing with AngularJS - Jesse Liberty | Fal...
Unit Testing and Behavior Driven Testing with AngularJS - Jesse Liberty | Fal...
FalafelSoftware
 
An Introduction to unit testing
An Introduction to unit testingAn Introduction to unit testing
An Introduction to unit testing
Steven Casey
 
Unit Testing JavaScript Applications
Unit Testing JavaScript ApplicationsUnit Testing JavaScript Applications
Unit Testing JavaScript Applications
Ynon Perek
 
Unit Testing Your Application
Unit Testing Your ApplicationUnit Testing Your Application
Unit Testing Your Application
Paladin Web Services
 
Rtt preso
Rtt presoRtt preso
Rtt preso
fdschoeneman
 
Leveling Up With Unit Testing - LonghornPHP 2022
Leveling Up With Unit Testing - LonghornPHP 2022Leveling Up With Unit Testing - LonghornPHP 2022
Leveling Up With Unit Testing - LonghornPHP 2022
Mark Niebergall
 
Building unit tests correctly with visual studio 2013
Building unit tests correctly with visual studio 2013Building unit tests correctly with visual studio 2013
Building unit tests correctly with visual studio 2013
Dror Helper
 

Similar to Practical unit testing 2014 (20)

Testing sync engine
Testing sync engineTesting sync engine
Testing sync engine
 
Testing smells
Testing smellsTesting smells
Testing smells
 
DSR Testing (Part 1)
DSR Testing (Part 1)DSR Testing (Part 1)
DSR Testing (Part 1)
 
Anatomy of a Gem: Bane
Anatomy of a Gem: BaneAnatomy of a Gem: Bane
Anatomy of a Gem: Bane
 
Static Code Analysis PHP[tek] 2023
Static Code Analysis PHP[tek] 2023Static Code Analysis PHP[tek] 2023
Static Code Analysis PHP[tek] 2023
 
Automated Frontend Testing
Automated Frontend TestingAutomated Frontend Testing
Automated Frontend Testing
 
The Many Ways to Test Your React App
The Many Ways to Test Your React AppThe Many Ways to Test Your React App
The Many Ways to Test Your React App
 
Kill the mutants - A better way to test your tests
Kill the mutants - A better way to test your testsKill the mutants - A better way to test your tests
Kill the mutants - A better way to test your tests
 
Kill the mutants and test your tests - Roy van Rijn
Kill the mutants and test your tests - Roy van RijnKill the mutants and test your tests - Roy van Rijn
Kill the mutants and test your tests - Roy van Rijn
 
Functional Java 8 - Introduction
Functional Java 8 - IntroductionFunctional Java 8 - Introduction
Functional Java 8 - Introduction
 
O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...
O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...
O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...
 
Jest: Frontend Testing richtig gemacht @WebworkerNRW
Jest: Frontend Testing richtig gemacht @WebworkerNRWJest: Frontend Testing richtig gemacht @WebworkerNRW
Jest: Frontend Testing richtig gemacht @WebworkerNRW
 
Test Driven Development - Workshop
Test Driven Development - WorkshopTest Driven Development - Workshop
Test Driven Development - Workshop
 
Unit Testing and Behavior Driven Testing with AngularJS - Jesse Liberty | Fal...
Unit Testing and Behavior Driven Testing with AngularJS - Jesse Liberty | Fal...Unit Testing and Behavior Driven Testing with AngularJS - Jesse Liberty | Fal...
Unit Testing and Behavior Driven Testing with AngularJS - Jesse Liberty | Fal...
 
An Introduction to unit testing
An Introduction to unit testingAn Introduction to unit testing
An Introduction to unit testing
 
Unit Testing JavaScript Applications
Unit Testing JavaScript ApplicationsUnit Testing JavaScript Applications
Unit Testing JavaScript Applications
 
Unit Testing Your Application
Unit Testing Your ApplicationUnit Testing Your Application
Unit Testing Your Application
 
Rtt preso
Rtt presoRtt preso
Rtt preso
 
Leveling Up With Unit Testing - LonghornPHP 2022
Leveling Up With Unit Testing - LonghornPHP 2022Leveling Up With Unit Testing - LonghornPHP 2022
Leveling Up With Unit Testing - LonghornPHP 2022
 
Building unit tests correctly with visual studio 2013
Building unit tests correctly with visual studio 2013Building unit tests correctly with visual studio 2013
Building unit tests correctly with visual studio 2013
 

Practical unit testing 2014

  • 2. Unit Tests Practical Andrew Fray, Spry Fox Thank you for coming. This is Practical Unit Tests, and I’m Andrew Fray. 
 My talk today is about how to structure your unit tests to aid iteration. We’re going to be looking in detail at my unit tests from a shipped AAA console project, the mistakes I made in writing them, and the concrete costs they caused. Then we’ll look at simple ways to avoid those costs in your own unit tests.
 ! definitions Post mortem Anti-patterns
  • 4. #pracunittests Backwards Is Forward: Making Better Games with Test-Driven Development http://gdcvault.com/play/1013416/Backwards-Is-Forward-Making-Better Sean Houghton, Noel Llopis http://tinyurl.com/gddtdd
  • 8. #pracunittests Unit TestSingle explicit assumption IMPORTANT functional/integration interchangable
  • 9. #pracunittests Unit TestSingle explicit assumption Integration Test Many implicit assumptions IMPORTANT functional/integration interchangable
  • 10. #pracunittests Qualities of Good Unit Tests Readable - why. up to speed. Maintainable - how. modify for iterating. Trustworthy - quick, deterministic. no external resources.
  • 11. #pracunittests Qualities of Good Unit Tests Readable Readable - why. up to speed. Maintainable - how. modify for iterating. Trustworthy - quick, deterministic. no external resources.
  • 12. #pracunittests Qualities of Good Unit Tests Readable Maintainable Readable - why. up to speed. Maintainable - how. modify for iterating. Trustworthy - quick, deterministic. no external resources.
  • 13. #pracunittests Qualities of Good Unit Tests Readable Maintainable Trustworthy Readable - why. up to speed. Maintainable - how. modify for iterating. Trustworthy - quick, deterministic. no external resources.
  • 15. #pracunittests F1 2011 X360/PS3/PC PRODUCTION CODE => SUBSYSTEM CODE
 HIGHLIGHT loc shipped 4+ previously
  • 16. #pracunittests F1 2011 X360/PS3/PC • Isolated new subsystem PRODUCTION CODE => SUBSYSTEM CODE
 HIGHLIGHT loc shipped 4+ previously
  • 17. #pracunittests F1 2011 X360/PS3/PC • Isolated new subsystem • 502 tests, 6700 lines of test code PRODUCTION CODE => SUBSYSTEM CODE
 HIGHLIGHT loc shipped 4+ previously
  • 18. #pracunittests F1 2011 X360/PS3/PC • Isolated new subsystem • 502 tests, 6700 lines of test code • 6200 lines of production code PRODUCTION CODE => SUBSYSTEM CODE
 HIGHLIGHT loc shipped 4+ previously
  • 19. #pracunittests A Partial Succes PRODUCTION CODE => SUBSYSTEM CODE
 HIGHLIGHT loc shipped 4+ previously
  • 20. #pracunittests • Clean, re-usable code A Partial Succes PRODUCTION CODE => SUBSYSTEM CODE
 HIGHLIGHT loc shipped 4+ previously
  • 21. #pracunittests • Clean, re-usable code • Fewer bugs A Partial Succes PRODUCTION CODE => SUBSYSTEM CODE
 HIGHLIGHT loc shipped 4+ previously
  • 22. #pracunittests • Clean, re-usable code • Fewer bugs • Easy to optimise A Partial Succes PRODUCTION CODE => SUBSYSTEM CODE
 HIGHLIGHT loc shipped 4+ previously
  • 23. #pracunittests • Clean, re-usable code • Fewer bugs • Easy to optimise • At end, treacle-like progress A Partial Succes PRODUCTION CODE => SUBSYSTEM CODE
 HIGHLIGHT loc shipped 4+ previously
  • 25. #pracunittests 1/4: The Opaque Anti-Pattern types: light grey names: bold dark grey literals: red else: dark grey
  • 26. #pracunittests // in LinearDescriptionFixture… void testBackwardsToNormalLeftwardsGradient() { LinearDescription leftDesc = DefaultFlatLinearDescription() .withGradient(1f).withInitialValue(10.0f).withOffsetOrigin(0.0f) .withDirection(Direction.eLeft); TEST_GREATER(leftDesc.getValueAtOffset(-1.0f), 10.0f); } 1/4: The Opaque Anti-Pattern types: light grey names: bold dark grey literals: red else: dark grey
  • 27. #pracunittests // in LinearDescriptionFixture… void testBackwardsToNormalLeftwardsGradient() { LinearDescription leftDesc = DefaultFlatLinearDescription() .withGradient(1f).withInitialValue(10.0f).withOffsetOrigin(0.0f) .withDirection(Direction.eLeft); TEST_GREATER(leftDesc.getValueAtOffset(-1.0f), 10.0f); } wat 1/4: The Opaque Anti-Pattern types: light grey names: bold dark grey literals: red else: dark grey
  • 28. #pracunittests Opaque: Hard to see HOW void testBackwardsToNormalLeftwardsGradient() { LinearDescription leftDesc = DefaultFlatLinearDescription() .withGradient(1f).withInitialValue(10.0f).withOffsetOrigin(0.0f) .withDirection(Direction.eLeft); TEST_GREATER(leftDesc.getValueAtOffset(-1.0f), 10.0f); } types: light grey names: bold dark grey literals: red else: dark grey
  • 29. #pracunittests Opaque: Hard to see HOW void testBackwardsToNormalLeftwardsGradient() { LinearDescription leftDesc = DefaultFlatLinearDescription() .withGradient(1f).withInitialValue(10.0f).withOffsetOrigin(0.0f) .withDirection(Direction.eLeft); TEST_GREATER(leftDesc.getValueAtOffset(-1.0f), 10.0f); } types: light grey names: bold dark grey literals: red else: dark grey
  • 30. #pracunittests Opaque: Hard to see HOW void testBackwardsToNormalLeftwardsGradient() { LinearDescription leftDesc = DefaultFlatLinearDescription() .withGradient(1f).withInitialValue(10.0f).withOffsetOrigin(0.0f) .withDirection(Direction.eLeft); TEST_GREATER(leftDesc.getValueAtOffset(-1.0f), 10.0f); } types: light grey names: bold dark grey literals: red else: dark grey
  • 31. #pracunittests Opaque: No Magic Literals break the rules when you need to!
  • 32. #pracunittests Opaque: No Magic Literals void testBackwardsToNormalLeftwardsGradient() { float someValueAtOrigin = 10.0f; float someOrigin = 0.0f; float positiveGradient = 1.0f; float someLeftOfOrigin = someOrigin - 1.0f; ! LinearDescription leftDesc = DefaultFlatLinearDescription() .withGradient(positiveGradient).withInitialValue(someValueAtOrigin) .withOffsetOrigin(someOrigin).withDirection(Direction.eLeft); float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin); TEST_GREATER(valueToLeft, someValueAtOrigin); } break the rules when you need to!
  • 33. #pracunittests Opaque: No Magic Literals void testBackwardsToNormalLeftwardsGradient() { float someValueAtOrigin = 10.0f; float someOrigin = 0.0f; float positiveGradient = 1.0f; float someLeftOfOrigin = someOrigin - 1.0f; ! LinearDescription leftDesc = DefaultFlatLinearDescription() .withGradient(positiveGradient).withInitialValue(someValueAtOrigin) .withOffsetOrigin(someOrigin).withDirection(Direction.eLeft); float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin); TEST_GREATER(valueToLeft, someValueAtOrigin); } break the rules when you need to!
  • 34. #pracunittests Opaque: No Magic Literals void testBackwardsToNormalLeftwardsGradient() { float someValueAtOrigin = 10.0f; float someOrigin = 0.0f; float positiveGradient = 1.0f; float someLeftOfOrigin = someOrigin - 1.0f; ! LinearDescription leftDesc = DefaultFlatLinearDescription() .withGradient(positiveGradient).withInitialValue(someValueAtOrigin) .withOffsetOrigin(someOrigin).withDirection(Direction.eLeft); float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin); TEST_GREATER(valueToLeft, someValueAtOrigin); } break the rules when you need to!
  • 35. #pracunittests Opaque: No Magic Literals void testBackwardsToNormalLeftwardsGradient() { float someValueAtOrigin = 10.0f; float someOrigin = 0.0f; float positiveGradient = 1.0f; float someLeftOfOrigin = someOrigin - 1.0f; ! LinearDescription leftDesc = DefaultFlatLinearDescription() .withGradient(positiveGradient).withInitialValue(someValueAtOrigin) .withOffsetOrigin(someOrigin).withDirection(Direction.eLeft); float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin); TEST_GREATER(valueToLeft, someValueAtOrigin); } break the rules when you need to!
  • 36. #pracunittests void testBackwardsToNormalLeftwardsGradient() { float someValueAtOrigin = 10.0f; float someOrigin = 0.0f; float positiveGradient = 1.0f; float someLeftOfOrigin = someOrigin - 1.0f; ! LinearDescription leftDesc = DefaultFlatLinearDescription() .withGradient(positiveGradient).withInitialValue(someValueAtOrigin) .withOffsetOrigin(someOrigin).withDirection(Direction.eLeft); float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin); TEST_GREATER(valueToLeft, someValueAtOrigin); }
  • 37. #pracunittests void testBackwardsToNormalLeftwardsGradient() { float someValueAtOrigin = 10.0f; float someOrigin = 0.0f; float positiveGradient = 1.0f; float someLeftOfOrigin = someOrigin - 1.0f; ! LinearDescription leftDesc = DefaultFlatLinearDescription() .withGradient(positiveGradient).withInitialValue(someValueAtOrigin) .withOffsetOrigin(someOrigin).withDirection(Direction.eLeft); float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin); TEST_GREATER(valueToLeft, someValueAtOrigin); }
  • 39. #pracunittests Opaque: Informative, Consistent Test Name void nameOfFunctionUnderTest_ContextOfTest_DesiredResultOfTest() {
  • 40. #pracunittests Opaque: Informative, Consistent Test Name void nameOfFunctionUnderTest_ContextOfTest_DesiredResultOfTest() { void testBackwardsToNormalLeftwardsGradient() {
  • 41. #pracunittests Opaque: Informative, Consistent Test Name void nameOfFunctionUnderTest_ContextOfTest_DesiredResultOfTest() { void void withDirection_Left_InvertsGradient() {
  • 42. #pracunittests void withDirection_Left_InvertsGradient() { float someValueAtOrigin = 10.0f; float someOrigin = 0.0f; float positiveGradient = 1.0f; float someLeftOfOrigin = someOrigin - 1.0f; ! LinearDescription leftDesc = DefaultFlatLinearDescription() .withGradient(positiveGradient).withInitialValue(someValueAtOrigin) .withOffsetOrigin(someOrigin).withDirection(Direction.eLeft); float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin); TEST_GREATER(valueToLeft, someValueAtOrigin); }
  • 43. #pracunittests void withDirection_Left_InvertsGradient() { float someValueAtOrigin = 10.0f; float someOrigin = 0.0f; float positiveGradient = 1.0f; float someLeftOfOrigin = someOrigin - 1.0f; ! LinearDescription leftDesc = DefaultFlatLinearDescription() .withGradient(positiveGradient).withInitialValue(someValueAtOrigin) .withOffsetOrigin(someOrigin).withDirection(Direction.eLeft); float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin); TEST_GREATER(valueToLeft, someValueAtOrigin); }
  • 45. #pracunittests Opaque: Arrange-Act-Assert void withDirection_Left_InvertsGradient() { float someValueAtOrigin = 10.0f; float someOrigin = 0.0f; float positiveGradient = 1.0f; LinearDescription increasingDesc = DefaultFlatLinearDescription() .withGradient(positiveGradient).withInitialValue(someValueAtOrigin) .withOffsetOrigin(someOrigin); ! LinearDescription leftDesc = increasingDesc.withDirection(Direction.eLeft); ! float someLeftOfOrigin = someOrigin - 1.0f; float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin); TEST_GREATER(valueToLeft, someValueAtOrigin); }
  • 46. #pracunittests Opaque: Arrange-Act-Assert void withDirection_Left_InvertsGradient() { float someValueAtOrigin = 10.0f; float someOrigin = 0.0f; float positiveGradient = 1.0f; LinearDescription increasingDesc = DefaultFlatLinearDescription() .withGradient(positiveGradient).withInitialValue(someValueAtOrigin) .withOffsetOrigin(someOrigin); ! LinearDescription leftDesc = increasingDesc.withDirection(Direction.eLeft); ! float someLeftOfOrigin = someOrigin - 1.0f; float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin); TEST_GREATER(valueToLeft, someValueAtOrigin); }
  • 47. #pracunittests Opaque: Arrange-Act-Assert void withDirection_Left_InvertsGradient() { float someValueAtOrigin = 10.0f; float someOrigin = 0.0f; float positiveGradient = 1.0f; LinearDescription increasingDesc = DefaultFlatLinearDescription() .withGradient(positiveGradient).withInitialValue(someValueAtOrigin) .withOffsetOrigin(someOrigin); ! LinearDescription leftDesc = increasingDesc.withDirection(Direction.eLeft); ! float someLeftOfOrigin = someOrigin - 1.0f; float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin); TEST_GREATER(valueToLeft, someValueAtOrigin); }
  • 48. #pracunittests Opaque: Arrange-Act-Assert void withDirection_Left_InvertsGradient() { float someValueAtOrigin = 10.0f; float someOrigin = 0.0f; float positiveGradient = 1.0f; LinearDescription increasingDesc = DefaultFlatLinearDescription() .withGradient(positiveGradient).withInitialValue(someValueAtOrigin) .withOffsetOrigin(someOrigin); ! LinearDescription leftDesc = increasingDesc.withDirection(Direction.eLeft); ! float someLeftOfOrigin = someOrigin - 1.0f; float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin); TEST_GREATER(valueToLeft, someValueAtOrigin); }
  • 49. #pracunittests Opaque: Arrange-Act-Assert void withDirection_Left_InvertsGradient() { float someValueAtOrigin = 10.0f; float someOrigin = 0.0f; float positiveGradient = 1.0f; LinearDescription increasingDesc = DefaultFlatLinearDescription() .withGradient(positiveGradient).withInitialValue(someValueAtOrigin) .withOffsetOrigin(someOrigin); ! LinearDescription leftDesc = increasingDesc.withDirection(Direction.eLeft); ! float someLeftOfOrigin = someOrigin - 1.0f; float valueToLeft = leftDesc.getValueAtOffset(someLeftOfOrigin); TEST_GREATER(valueToLeft, someValueAtOrigin); }
  • 51. #pracunittests The Opaque Anti-Pattern • Hard to see "how"? breathe at end!
  • 52. #pracunittests The Opaque Anti-Pattern • Hard to see "how"? • Demystify magic literals breathe at end!
  • 53. #pracunittests The Opaque Anti-Pattern • Hard to see "how"? • Demystify magic literals • Consistent informative test name breathe at end!
  • 54. #pracunittests The Opaque Anti-Pattern • Hard to see "how"? • Demystify magic literals • Consistent informative test name • Arrange-Act-Assert breathe at end!
  • 55. #pracunittests 2/4: The Wet Anti-Pattern RacingLineOffsets.setSignedDistanceToRacingLine(RacingLine, float) On refactoring
  • 57. #pracunittests RacingLineOffsets.setSignedDistanceToRacingLine(RacingLine, float, int) 2/4: The Wet Anti-Pattern > Test library build failed with 235 error(s) RacingLineOffsets On refactoring
  • 58. #pracunittests void getIdealOffset_BeyondRacingLineEdge_ReturnsEdge() { float someRacingLineRadius = 10.0f; float someOffsetBeyondRacingLine = someRacingLineRadius + 1.0f; ! RacingLineOffsets beyondOffsets = new RacingLineOffsets(); float leftRacingLineEdge = -someRacingLineRadius - someOffsetBeyondRacingLine; beyondOffsets.setSignedDistanceToRacingLine( RacingLine.eLeftEdge, leftRacingLineEdge); beyondOffsets.setSignedDistanceToRacingLine( RacingLine.eCenter, -someOffsetBeyondRacingLine); float rightRacingLineEdge = someRacingLineRadius - someOffsetBeyondRacingLine; beyondOffsets.setSignedDistanceToRacingLine( RacingLine.eRightEdge, rightRacingLineEdge); ! OffsetRequest idealRequest = m_raceBehaviour.getIdealOffset(beyondOffsets); ! float idealRacingLineOffset = idealRequest.GetOffset(); TEST_EQUAL(idealRacingLineOffset, someRacingLineRadius); } void getIdealOffset_WithinRacingLineEdge_ReturnsOffset() { float someRacingLineRadius = 10.0f; float someOffsetWithinRadius = someRacingLineRadius * 0.8f; ! RacingLineOffsets withinOffsets = new RacingLineOffsets(); float leftRacingLineEdge = -someRacingLineRadius - someOffsetWithinRadius; withinOfffsets.setSignedDistanceToRacingLine( RacingLine.eLeftEdge, leftRacingLineEdge); withinOffsets.setSignedDistanceToRacingLine( RacingLine.eCenter, -someOffsetWithinRadius); float rightRacingLineEdge = someRacingLineRadius - someOffsetWithinRadius; withinOffsets.setSignedDistanceToRacingLine( RacingLine.eRightEdge, rightRacingLineEdge); ! OffsetRequest idealRequest = m_raceBehaviour.getIdealOffset(withinOffsets); ! float idealRacingLineOffset = idealRequest.GetOffset(); TEST_EQUAL(idealRacingLineOffset, someOffsetWithinRadius); }
  • 59. #pracunittests void getIdealOffset_BeyondRacingLineEdge_ReturnsEdge() { float someRacingLineRadius = 10.0f; float someOffsetBeyondRacingLine = someRacingLineRadius + 1.0f; ! RacingLineOffsets beyondOffsets = new RacingLineOffsets(); float leftRacingLineEdge = -someRacingLineRadius - someOffsetBeyondRacingLine; beyondOffsets.setSignedDistanceToRacingLine( RacingLine.eLeftEdge, leftRacingLineEdge); beyondOffsets.setSignedDistanceToRacingLine( RacingLine.eCenter, -someOffsetBeyondRacingLine); float rightRacingLineEdge = someRacingLineRadius - someOffsetBeyondRacingLine; beyondOffsets.setSignedDistanceToRacingLine( RacingLine.eRightEdge, rightRacingLineEdge); ! OffsetRequest idealRequest = m_raceBehaviour.getIdealOffset(beyondOffsets); ! float idealRacingLineOffset = idealRequest.GetOffset(); TEST_EQUAL(idealRacingLineOffset, someRacingLineRadius); } void getIdealOffset_WithinRacingLineEdge_ReturnsOffset() { float someRacingLineRadius = 10.0f; float someOffsetWithinRadius = someRacingLineRadius * 0.8f; ! RacingLineOffsets withinOffsets = new RacingLineOffsets(); float leftRacingLineEdge = -someRacingLineRadius - someOffsetWithinRadius; withinOfffsets.setSignedDistanceToRacingLine( RacingLine.eLeftEdge, leftRacingLineEdge); withinOffsets.setSignedDistanceToRacingLine( RacingLine.eCenter, -someOffsetWithinRadius); float rightRacingLineEdge = someRacingLineRadius - someOffsetWithinRadius; withinOffsets.setSignedDistanceToRacingLine( RacingLine.eRightEdge, rightRacingLineEdge); ! OffsetRequest idealRequest = m_raceBehaviour.getIdealOffset(withinOffsets); ! float idealRacingLineOffset = idealRequest.GetOffset(); TEST_EQUAL(idealRacingLineOffset, someOffsetWithinRadius); } Not DRY
  • 60. #pracunittests Wet: Helper Functions void getIdealOffset_WithinRacingLineEdge_ReturnsOffset() { float someRacingLineRadius = 10.0f; float someOffsetWithinRadius = someRacingLineRadius * 0.8f; ! RacingLineOffsets withinOffsets = CreateRacingLineOffsets( someRacingLineRadius, someOffsetWithinRadius); ! OffsetRequest idealRequest = m_raceBehaviour.getIdealOffset(withinOffsets); ! float idealRacingLineOffset = idealRequest.GetOffset(); TEST_EQUAL( idealRacingLineOffset, someOffsetWithinRadius); } void getIdealOffset_BeyondRacingLineEdge_ReturnsEdge() { float someRacingLineRadius = 10.0f; float someOffsetBeyondRacingLine = someRacingLineRadius + 1.0f; ! RacingLineOffsets beyondOffsets = CreateRacingLineOffsets( someRacingLineRadius, someOffsetBeyondRacingLine); ! OffsetRequest idealRequest = m_raceBehaviour.getIdealOffset(beyondOffsets); ! float idealRacingLineOffset = idealRequest.GetOffset(); TEST_EQUAL( idealRacingLineOffset, withinOffsets.RightEdge); }
  • 61. #pracunittests Wet: Helper Functions void getIdealOffset_WithinRacingLineEdge_ReturnsOffset() { float someRacingLineRadius = 10.0f; float someOffsetWithinRadius = someRacingLineRadius * 0.8f; ! RacingLineOffsets withinOffsets = CreateRacingLineOffsets( someRacingLineRadius, someOffsetWithinRadius); ! OffsetRequest idealRequest = m_raceBehaviour.getIdealOffset(withinOffsets); ! float idealRacingLineOffset = idealRequest.GetOffset(); TEST_EQUAL( idealRacingLineOffset, someOffsetWithinRadius); } void getIdealOffset_BeyondRacingLineEdge_ReturnsEdge() { float someRacingLineRadius = 10.0f; float someOffsetBeyondRacingLine = someRacingLineRadius + 1.0f; ! RacingLineOffsets beyondOffsets = CreateRacingLineOffsets( someRacingLineRadius, someOffsetBeyondRacingLine); ! OffsetRequest idealRequest = m_raceBehaviour.getIdealOffset(beyondOffsets); ! float idealRacingLineOffset = idealRequest.GetOffset(); TEST_EQUAL( idealRacingLineOffset, withinOffsets.RightEdge); }
  • 62. #pracunittests Wet: Helper Functions void getIdealOffset_WithinRacingLineEdge_ReturnsOffset() { float someRacingLineRadius = 10.0f; float someOffsetWithinRadius = someRacingLineRadius * 0.8f; ! RacingLineOffsets withinOffsets = CreateRacingLineOffsets( someRacingLineRadius, someOffsetWithinRadius); ! OffsetRequest idealRequest = m_raceBehaviour.getIdealOffset(withinOffsets); ! float idealRacingLineOffset = idealRequest.GetOffset(); TEST_EQUAL( idealRacingLineOffset, someOffsetWithinRadius); } void getIdealOffset_BeyondRacingLineEdge_ReturnsEdge() { float someRacingLineRadius = 10.0f; float someOffsetBeyondRacingLine = someRacingLineRadius + 1.0f; ! RacingLineOffsets beyondOffsets = CreateRacingLineOffsets( someRacingLineRadius, someOffsetBeyondRacingLine); ! OffsetRequest idealRequest = m_raceBehaviour.getIdealOffset(beyondOffsets); ! float idealRacingLineOffset = idealRequest.GetOffset(); TEST_EQUAL( idealRacingLineOffset, withinOffsets.RightEdge); }
  • 63. #pracunittests Wet: Helper Functions void getIdealOffset_WithinRacingLineEdge_ReturnsOffset() { float someRacingLineRadius = 10.0f; float someOffsetWithinRadius = someRacingLineRadius * 0.8f; ! RacingLineOffsets withinOffsets = CreateRacingLineOffsets( someRacingLineRadius, someOffsetWithinRadius); ! OffsetRequest idealRequest = m_raceBehaviour.getIdealOffset(withinOffsets); ! test_tauOffsetEqual(idealRequest, someOffsetWithinRadius); } void getIdealOffset_BeyondRacingLineEdge_ReturnsEdge() { float someRacingLineRadius = 10.0f; float someOffsetBeyondRacingLine = someRacingLineRadius + 1.0f; ! RacingLineOffsets beyondOffsets = CreateRacingLineOffsets( someRacingLineRadius, someOffsetBeyondRacingLine); ! OffsetRequest idealRequest = m_raceBehaviour.getIdealOffset(beyondOffsets); ! test_racingLineOffsetEqual( idealRequest, withinOffsets.RightEdge); }
  • 64. #pracunittests Wet: Helper Functions void getIdealOffset_WithinRacingLineEdge_ReturnsOffset() { float someRacingLineRadius = 10.0f; float someOffsetWithinRadius = someRacingLineRadius * 0.8f; ! RacingLineOffsets withinOffsets = CreateRacingLineOffsets( someRacingLineRadius, someOffsetWithinRadius); ! OffsetRequest idealRequest = m_raceBehaviour.getIdealOffset(withinOffsets); ! test_tauOffsetEqual(idealRequest, someOffsetWithinRadius); } void getIdealOffset_BeyondRacingLineEdge_ReturnsEdge() { float someRacingLineRadius = 10.0f; float someOffsetBeyondRacingLine = someRacingLineRadius + 1.0f; ! RacingLineOffsets beyondOffsets = CreateRacingLineOffsets( someRacingLineRadius, someOffsetBeyondRacingLine); ! OffsetRequest idealRequest = m_raceBehaviour.getIdealOffset(beyondOffsets); ! test_racingLineOffsetEqual( idealRequest, withinOffsets.RightEdge); }
  • 66. #pracunittests The Wet Anti-Pattern • Hard-to-maintain hacky tests? breathe at end!
  • 67. #pracunittests The Wet Anti-Pattern • Hard-to-maintain hacky tests? • Keep production sensibilities in unit test code breathe at end!
  • 68. #pracunittests The Wet Anti-Pattern • Hard-to-maintain hacky tests? • Keep production sensibilities in unit test code • Stay DRY with helper functions and custom asserts breathe at end!
  • 69. #pracunittests The Wet Anti-Pattern • Hard-to-maintain hacky tests? • Keep production sensibilities in unit test code • Stay DRY with helper functions and custom asserts • Do not hide the call to the function under test breathe at end!
  • 70. #pracunittests 3/4: The Deep Anti-Pattern > Test failed: > getOwnerAtOffset_WithOwner_ReferencesOwnerAtManyOffsets > With Assert: VehicleID 0 != 1 on introducing a bug chi == “kai”!
  • 71. #pracunittests void getOwnerAtOffset_WithOwner_ReferencesOwnerAtManyOffsets() { VehicleID someOwnerID = (VehicleID)1; LinearDescription ownedDescription = DefaultFlatLinearDescription().withOwner(someOwnerID); ! float someNegativeOffset = -1.0f; float ownerAtNegativeOffset = ownedDescription.getOwnerAtOffset(someNegativeOffset); float somePositiveOffset = 1.0f; float ownerAtPositiveOffset = ownedDescription.getOwnerAtOffset(somePositiveOffset); ! test_OwningVehicleIsEqualTo(ownerAtNegativeOffset, someOwnerID); test_OwningVehicleIsEqualTo(ownerAtPositiveOffset, someOwnerID); } make it clear
  • 72. #pracunittests void getOwnerAtOffset_WithOwner_ReferencesOwnerAtManyOffsets() { VehicleID someOwnerID = (VehicleID)1; LinearDescription ownedDescription = DefaultFlatLinearDescription().withOwner(someOwnerID); ! float someNegativeOffset = -1.0f; float ownerAtNegativeOffset = ownedDescription.getOwnerAtOffset(someNegativeOffset); float somePositiveOffset = 1.0f; float ownerAtPositiveOffset = ownedDescription.getOwnerAtOffset(somePositiveOffset); ! test_OwningVehicleIsEqualTo(ownerAtNegativeOffset, someOwnerID); test_OwningVehicleIsEqualTo(ownerAtPositiveOffset, someOwnerID); } make it clear
  • 73. #pracunittests void getOwnerAtOffset_WithOwner_ReferencesOwnerAtManyOffsets() { VehicleID someOwnerID = (VehicleID)1; LinearDescription ownedDescription = DefaultFlatLinearDescription().withOwner(someOwnerID); ! float someNegativeOffset = -1.0f; float ownerAtNegativeOffset = ownedDescription.getOwnerAtOffset(someNegativeOffset); float somePositiveOffset = 1.0f; float ownerAtPositiveOffset = ownedDescription.getOwnerAtOffset(somePositiveOffset); ! test_OwningVehicleIsEqualTo(ownerAtNegativeOffset, someOwnerID); test_OwningVehicleIsEqualTo(ownerAtPositiveOffset, someOwnerID); } make it clear
  • 74. #pracunittests void getOwnerAtOffset_WithOwner_ReferencesOwnerAtManyOffsets() { VehicleID someOwnerID = (VehicleID)1; LinearDescription ownedDescription = DefaultFlatLinearDescription().withOwner(someOwnerID); ! float someNegativeOffset = -1.0f; float ownerAtNegativeOffset = ownedDescription.getOwnerAtOffset(someNegativeOffset); float somePositiveOffset = 1.0f; float ownerAtPositiveOffset = ownedDescription.getOwnerAtOffset(somePositiveOffset); ! test_OwningVehicleIsEqualTo(ownerAtNegativeOffset, someOwnerID); test_OwningVehicleIsEqualTo(ownerAtPositiveOffset, someOwnerID); } >1 explicit assumption make it clear
  • 75. #pracunittests Deep: One Assert Per Test void getOwnerAtOffset_WithOwnerAndNegativeOffset_ReturnsOwner() { float someNegativeOffset = -1.0f; ! float ownerAtNegativeOffset = m_ownedDescription.getOwnerAtOffset(someNegativeOffset); ! test_OwningVehicleIsEqualTo(ownerAtNegativeOffset, m_someOwnerID); } ! void getOwnerAtOffset_WithOwnerAndPositiveOffset_ReturnsOwner() { // *snip* }
  • 76. #pracunittests > Test failed: > getOwnerAtOffset_WithOwnerAndNegativeOffset_ReturnsOwner > With Assert: VehicleID 0 != 1 > Test failed: > getOwnerAtOffset_WithOwnerAndPositiveOffset_ReturnsOwner > With Assert: VehicleID 0 != 1
  • 78. #pracunittests The Deep Anti-Pattern • Test failures not fully informative? breathe at end!
  • 79. #pracunittests The Deep Anti-Pattern • Test failures not fully informative? • Too many explicit assumptions per test breathe at end!
  • 80. #pracunittests The Deep Anti-Pattern • Test failures not fully informative? • Too many explicit assumptions per test • Minimise assumptions per test breathe at end!
  • 81. #pracunittests 4/4: The Wide Anti-Pattern On introducing a bug in behaviour system
  • 82. #pracunittests 4/4: The Wide Anti-Pattern > Executed 613 test(s), 599 test(s) passed, 14 test(s) failed. On introducing a bug in behaviour system
  • 83. #pracunittests // in DraftBehaviourFixture… void updateImpl_WithCarToDraft_PushesAIToBehindCar() { WorldInfo worldInfo = new WorldInfo(); float someDraftTargetVehicleOffset = 2.0f; // *snip* blackboard setup ! DraftBehaviour draft = new DraftBehaviour(); ! BehaviourSystem behaviourSystem = new BehaviourSystem(); behaviourSystem.addBehaviour(draft); ! behaviourSystem.updateWithBlackboard(worldInfo); ! MovementRequest draftMovement = behaviourSystem.getMovementRequest(); TEST_EQUAL(draftMovement.desiredRacingLineOffset, someDraftTargetVehicleOffset); }
  • 84. #pracunittests // in DraftBehaviourFixture… void updateImpl_WithCarToDraft_PushesAIToBehindCar() { WorldInfo worldInfo = new WorldInfo(); float someDraftTargetVehicleOffset = 2.0f; // *snip* blackboard setup ! DraftBehaviour draft = new DraftBehaviour(); ! BehaviourSystem behaviourSystem = new BehaviourSystem(); behaviourSystem.addBehaviour(draft); ! behaviourSystem.updateWithBlackboard(worldInfo); ! MovementRequest draftMovement = behaviourSystem.getMovementRequest(); TEST_EQUAL(draftMovement.desiredRacingLineOffset, someDraftTargetVehicleOffset); }
  • 85. #pracunittests // in DraftBehaviourFixture… void updateImpl_WithCarToDraft_PushesAIToBehindCar() { WorldInfo worldInfo = new WorldInfo(); float someDraftTargetVehicleOffset = 2.0f; // *snip* blackboard setup ! DraftBehaviour draft = new DraftBehaviour(); ! BehaviourSystem behaviourSystem = new BehaviourSystem(); behaviourSystem.addBehaviour(draft); ! behaviourSystem.updateWithBlackboard(worldInfo); ! MovementRequest draftMovement = behaviourSystem.getMovementRequest(); TEST_EQUAL(draftMovement.desiredRacingLineOffset, someDraftTargetVehicleOffset); }
  • 86. #pracunittests // in DraftBehaviourFixture… void updateImpl_WithCarToDraft_PushesAIToBehindCar() { WorldInfo worldInfo = new WorldInfo(); float someDraftTargetVehicleOffset = 2.0f; // *snip* blackboard setup ! DraftBehaviour draft = new DraftBehaviour(); ! BehaviourSystem behaviourSystem = new BehaviourSystem(); behaviourSystem.addBehaviour(draft); ! behaviourSystem.updateWithBlackboard(worldInfo); ! MovementRequest draftMovement = behaviourSystem.getMovementRequest(); TEST_EQUAL(draftMovement.desiredRacingLineOffset, someDraftTargetVehicleOffset); } >0 implicit assumptions
  • 87. #pracunittests // in DraftBehaviourFixture… void updateImpl_WithCarToDraft_PushesAIToBehindCar() { WorldInfo worldInfo = new WorldInfo(); float someDraftTargetVehicleOffset = 2.0f; // *snip* blackboard setup ! DraftBehaviour draft = new DraftBehaviour(); ! BehaviourSystem behaviourSystem = new BehaviourSystem(); behaviourSystem.addBehaviour(draft); ! behaviourSystem.updateWithBlackboard(worldInfo); ! MovementRequest draftMovement = behaviourSystem.getMovementRequest(); TEST_EQUAL(draftMovement.desiredRacingLineOffset, someDraftTargetVehicleOffset); }
  • 88. #pracunittests // in DraftBehaviourFixture… void updateImpl_WithCarToDraft_PushesAIToBehindCar() { WorldInfo worldInfo = new WorldInfo(); float someDraftTargetVehicleOffset = 2.0f; // *snip* blackboard setup ! DraftBehaviour draft = new DraftBehaviour(); ! BehaviourSystem behaviourSystem = new BehaviourSystem(); behaviourSystem.addBehaviour(draft); ! behaviourSystem.updateWithBlackboard(worldInfo); ! MovementRequest draftMovement = behaviourSystem.getMovementRequest(); TEST_EQUAL(draftMovement.desiredRacingLineOffset, someDraftTargetVehicleOffset); }
  • 89. #pracunittests // in DraftBehaviourFixture… void updateImpl_WithCarToDraft_PushesAIToBehindCar() { WorldInfo worldInfo = new WorldInfo(); float someDraftTargetVehicleOffset = 2.0f; // *snip* blackboard setup ! DraftBehaviour draft = new DraftBehaviour(); ! BehaviourSystem behaviourSystem = new BehaviourSystem(); behaviourSystem.addBehaviour(draft); ! behaviourSystem.updateWithBlackboard(worldInfo); ! MovementRequest draftMovement = behaviourSystem.getMovementRequest(); TEST_EQUAL(draftMovement.desiredRacingLineOffset, someDraftTargetVehicleOffset); }
  • 90. #pracunittests // in DraftBehaviourFixture… void updateImpl_WithCarToDraft_PushesAIToBehindCar() { WorldInfo worldInfo = new WorldInfo(); float someDraftTargetVehicleOffset = 2.0f; // *snip* blackboard setup ! DraftBehaviour draft = new DraftBehaviour(); ! BehaviourSystem behaviourSystem = new BehaviourSystem(); behaviourSystem.addBehaviour(draft); ! behaviourSystem.updateWithBlackboard(worldInfo); ! MovementRequest draftMovement = behaviourSystem.getMovementRequest(); TEST_EQUAL(draftMovement.desiredRacingLineOffset, someDraftTargetVehicleOffset); }
  • 91. #pracunittests Wide: Seams void DraftBehaviour.updateImpl(WorldInfo wi, HeatMap heatInOut); Game Code Inversion of Control Interfaces/virtual functions Mocking frameworks/helper classes
  • 92. #pracunittests Wide: Seams void DraftBehaviour.updateImpl(WorldInfo wi, HeatMap heatInOut); class HeatMap { virtual void WriteHeat(float offset, float value) { … } } Game Code Inversion of Control Interfaces/virtual functions Mocking frameworks/helper classes
  • 93. #pracunittests Wide: Seams void DraftBehaviour.updateImpl(WorldInfo wi, HeatMap heatInOut); class HeatMap { virtual void WriteHeat(float offset, float value) { … } } class MockHeatMap : HeatMap { override void WriteHeat(float offset, float value) { … } } Game Code Test Library Inversion of Control Interfaces/virtual functions Mocking frameworks/helper classes
  • 94. #pracunittests Wide: Seams void DraftBehaviour.updateImpl(WorldInfo wi, HeatMap heatInOut); class HeatMap { virtual void WriteHeat(float offset, float value) { … } } class MockHeatMap : HeatMap { override void WriteHeat(float offset, float value) { … } } Game Code Test Library Inversion of Control Interfaces/virtual functions Mocking frameworks/helper classes
  • 95. #pracunittests Wide: MockHeatMapvoid updateImpl_WithCarToDraft_PushesAIToBehindCar() { WorldInfo worldInfo = new WorldInfo(); float someDraftTargetVehicleOffset = 2.0f; // *snip* blackboard setup ! DraftBehaviour draft = new DraftBehaviour(); MockHeatMap mockMap = new MockHeatMap(); ! draft.updateImpl(worldInfo, mockMap); ! float draftOffsetWithHighestHeat = mockMap.getHighestHeatOffset(); TEST_EQUAL(draftOffsetWithHighestHeat, someDraftTargetVehicleOffset); }
  • 96. #pracunittests Wide: MockHeatMapvoid updateImpl_WithCarToDraft_PushesAIToBehindCar() { WorldInfo worldInfo = new WorldInfo(); float someDraftTargetVehicleOffset = 2.0f; // *snip* blackboard setup ! DraftBehaviour draft = new DraftBehaviour(); MockHeatMap mockMap = new MockHeatMap(); ! draft.updateImpl(worldInfo, mockMap); ! float draftOffsetWithHighestHeat = mockMap.getHighestHeatOffset(); TEST_EQUAL(draftOffsetWithHighestHeat, someDraftTargetVehicleOffset); }
  • 97. #pracunittests Wide: MockHeatMapvoid updateImpl_WithCarToDraft_PushesAIToBehindCar() { WorldInfo worldInfo = new WorldInfo(); float someDraftTargetVehicleOffset = 2.0f; // *snip* blackboard setup ! DraftBehaviour draft = new DraftBehaviour(); MockHeatMap mockMap = new MockHeatMap(); ! draft.updateImpl(worldInfo, mockMap); ! float draftOffsetWithHighestHeat = mockMap.getHighestHeatOffset(); TEST_EQUAL(draftOffsetWithHighestHeat, someDraftTargetVehicleOffset); }
  • 99. #pracunittests The Wide Anti-Pattern • False-negative test failures? breathe at end!
  • 100. #pracunittests The Wide Anti-Pattern • False-negative test failures? • Many implicit assumptions breathe at end!
  • 101. #pracunittests The Wide Anti-Pattern • False-negative test failures? • Many implicit assumptions • Isolate code with seams, to enable simple fake impostors breathe at end!
  • 102. #pracunittests Recap Respect unit test quality as much as production code quality Write once, read many Only 1 explicit assumption As few as possible implicit assumptions
  • 103. #pracunittests Recap • Respect unit test source code as much as production source code Respect unit test quality as much as production code quality Write once, read many Only 1 explicit assumption As few as possible implicit assumptions
  • 104. #pracunittests Recap • Respect unit test source code as much as production source code • Write once, read many Respect unit test quality as much as production code quality Write once, read many Only 1 explicit assumption As few as possible implicit assumptions
  • 105. #pracunittests Recap • Respect unit test source code as much as production source code • Write once, read many • Only 1 explicit assumption Respect unit test quality as much as production code quality Write once, read many Only 1 explicit assumption As few as possible implicit assumptions
  • 106. #pracunittests Recap • Respect unit test source code as much as production source code • Write once, read many • Only 1 explicit assumption • Minimise implicit assumptions Respect unit test quality as much as production code quality Write once, read many Only 1 explicit assumption As few as possible implicit assumptions
  • 107. #pracunittests • andrew.fray@gmail.com • @tenpn • andrewfray.wordpress.com • Roy Osherove: Art of Unit Testing www.artofunittesting.com • Michael Feathers: Working Effectively with Legacy Code • Steve Freeman & Nat Pryce: Growing Object-Orientated Software, Guided By Tests Colour scheme by Miaka www.colourlovers.com/palette/444487/Curiosity_Killed Feedback! Thank the CAs!