unit-test-boundary-conditions

安装量: 558
排名: #1976

安装

npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill unit-test-boundary-conditions
Unit Testing Boundary Conditions and Edge Cases
Overview
This skill provides systematic patterns for testing boundary conditions, edge cases, and limit values using JUnit 5. It covers numeric boundaries (Integer.MIN_VALUE, MAX_VALUE), string edge cases (null, empty, whitespace), collection boundaries, floating-point precision, date/time limits, and concurrent access patterns.
When to Use
Use this skill when:
Testing minimum and maximum values
Testing null and empty inputs
Testing whitespace-only strings
Testing overflow/underflow scenarios
Testing collections with zero/one/many items
Verifying behavior at API boundaries
Want comprehensive edge case coverage
Instructions
Identify boundaries
List all numeric limits (MIN_VALUE, MAX_VALUE, zero), string states (null, empty, whitespace), and collection sizes (empty, single, many)
Use parameterized tests
Apply @ParameterizedTest with @ValueSource for testing multiple boundary values efficiently
Test both sides of boundaries
Test values just below, at, and just above each boundary
Verify floating point precision
Use
isCloseTo(expected, within(tolerance))
for floating point comparisons
Test collection states
Explicitly test empty (0), single (1), and many (>1) element scenarios
Handle overflow scenarios
Use Math.addExact() and Math.subtractExact() to detect overflow/underflow
Test date/time edges
Verify leap years, month boundaries, and timezone transitions
Document boundary rationale
Explain why specific boundaries matter for your domain
Examples
Setup: Boundary Testing
Maven
<
dependency
>
<
groupId
>
org.junit.jupiter
</
groupId
>
<
artifactId
>
junit-jupiter
</
artifactId
>
<
scope
>
test
</
scope
>
</
dependency
>
<
dependency
>
<
groupId
>
org.junit.jupiter
</
groupId
>
<
artifactId
>
junit-jupiter-params
</
artifactId
>
<
scope
>
test
</
scope
>
</
dependency
>
<
dependency
>
<
groupId
>
org.assertj
</
groupId
>
<
artifactId
>
assertj-core
</
artifactId
>
<
scope
>
test
</
scope
>
</
dependency
>
Gradle
dependencies
{
testImplementation
(
"org.junit.jupiter:junit-jupiter"
)
testImplementation
(
"org.junit.jupiter:junit-jupiter-params"
)
testImplementation
(
"org.assertj:assertj-core"
)
}
Numeric Boundary Testing
Integer Limits
import
org
.
junit
.
jupiter
.
params
.
ParameterizedTest
;
import
org
.
junit
.
jupiter
.
params
.
provider
.
ValueSource
;
import
static
org
.
assertj
.
core
.
api
.
Assertions
.
*
;
class
IntegerBoundaryTest
{
@ParameterizedTest
@ValueSource
(
ints
=
{
Integer
.
MIN_VALUE
,
Integer
.
MIN_VALUE
+
1
,
0
,
Integer
.
MAX_VALUE
-
1
,
Integer
.
MAX_VALUE
}
)
void
shouldHandleIntegerBoundaries
(
int
value
)
{
assertThat
(
value
)
.
isNotNull
(
)
;
}
@Test
void
shouldHandleIntegerOverflow
(
)
{
int
maxInt
=
Integer
.
MAX_VALUE
;
int
result
=
Math
.
addExact
(
maxInt
,
1
)
;
// Will throw ArithmeticException
assertThatThrownBy
(
(
)
->
Math
.
addExact
(
Integer
.
MAX_VALUE
,
1
)
)
.
isInstanceOf
(
ArithmeticException
.
class
)
;
}
@Test
void
shouldHandleIntegerUnderflow
(
)
{
assertThatThrownBy
(
(
)
->
Math
.
subtractExact
(
Integer
.
MIN_VALUE
,
1
)
)
.
isInstanceOf
(
ArithmeticException
.
class
)
;
}
@Test
void
shouldHandleZero
(
)
{
int
result
=
MathUtils
.
divide
(
0
,
5
)
;
assertThat
(
result
)
.
isZero
(
)
;
assertThatThrownBy
(
(
)
->
MathUtils
.
divide
(
5
,
0
)
)
.
isInstanceOf
(
ArithmeticException
.
class
)
;
}
}
String Boundary Testing
Null, Empty, and Whitespace
import
org
.
junit
.
jupiter
.
params
.
ParameterizedTest
;
import
org
.
junit
.
jupiter
.
params
.
provider
.
ValueSource
;
class
StringBoundaryTest
{
@ParameterizedTest
@ValueSource
(
strings
=
{
""
,
" "
,
" "
,
"\t"
,
"\n"
}
)
void
shouldConsiderEmptyAndWhitespaceAsInvalid
(
String
input
)
{
boolean
result
=
StringUtils
.
isNotBlank
(
input
)
;
assertThat
(
result
)
.
isFalse
(
)
;
}
@Test
void
shouldHandleNullString
(
)
{
String
result
=
StringUtils
.
trim
(
null
)
;
assertThat
(
result
)
.
isNull
(
)
;
}
@Test
void
shouldHandleSingleCharacter
(
)
{
String
result
=
StringUtils
.
capitalize
(
"a"
)
;
assertThat
(
result
)
.
isEqualTo
(
"A"
)
;
String
result2
=
StringUtils
.
trim
(
"x"
)
;
assertThat
(
result2
)
.
isEqualTo
(
"x"
)
;
}
@Test
void
shouldHandleVeryLongString
(
)
{
String
longString
=
"x"
.
repeat
(
1000000
)
;
assertThat
(
longString
.
length
(
)
)
.
isEqualTo
(
1000000
)
;
assertThat
(
StringUtils
.
isNotBlank
(
longString
)
)
.
isTrue
(
)
;
}
@Test
void
shouldHandleSpecialCharacters
(
)
{
String
special
=
"!@#$%^&*()_+-={}[]|\:;<>?,./"
;
assertThat
(
StringUtils
.
length
(
special
)
)
.
isEqualTo
(
31
)
;
}
}
Collection Boundary Testing
Empty, Single, and Large Collections
import
org
.
junit
.
jupiter
.
params
.
ParameterizedTest
;
import
org
.
junit
.
jupiter
.
params
.
provider
.
ValueSource
;
class
CollectionBoundaryTest
{
@Test
void
shouldHandleEmptyList
(
)
{
List
<
String
>
empty
=
List
.
of
(
)
;
assertThat
(
empty
)
.
isEmpty
(
)
;
assertThat
(
CollectionUtils
.
first
(
empty
)
)
.
isNull
(
)
;
assertThat
(
CollectionUtils
.
count
(
empty
)
)
.
isZero
(
)
;
}
@Test
void
shouldHandleSingleElementList
(
)
{
List
<
String
>
single
=
List
.
of
(
"only"
)
;
assertThat
(
single
)
.
hasSize
(
1
)
;
assertThat
(
CollectionUtils
.
first
(
single
)
)
.
isEqualTo
(
"only"
)
;
assertThat
(
CollectionUtils
.
last
(
single
)
)
.
isEqualTo
(
"only"
)
;
}
@Test
void
shouldHandleLargeList
(
)
{
List
<
Integer
>
large
=
new
ArrayList
<
>
(
)
;
for
(
int
i
=
0
;
i
<
100000
;
i
++
)
{
large
.
add
(
i
)
;
}
assertThat
(
large
)
.
hasSize
(
100000
)
;
assertThat
(
CollectionUtils
.
first
(
large
)
)
.
isZero
(
)
;
assertThat
(
CollectionUtils
.
last
(
large
)
)
.
isEqualTo
(
99999
)
;
}
@Test
void
shouldHandleNullInCollection
(
)
{
List
<
String
>
withNull
=
new
ArrayList
<
>
(
List
.
of
(
"a"
,
null
,
"c"
)
)
;
assertThat
(
withNull
)
.
contains
(
null
)
;
assertThat
(
CollectionUtils
.
filterNonNull
(
withNull
)
)
.
hasSize
(
2
)
;
}
@Test
void
shouldHandleDuplicatesInCollection
(
)
{
List
<
Integer
>
duplicates
=
List
.
of
(
1
,
1
,
2
,
2
,
3
,
3
)
;
assertThat
(
duplicates
)
.
hasSize
(
6
)
;
Set
<
Integer
>
unique
=
new
HashSet
<
>
(
duplicates
)
;
assertThat
(
unique
)
.
hasSize
(
3
)
;
}
}
Floating Point Boundary Testing
Precision and Special Values
class
FloatingPointBoundaryTest
{
@Test
void
shouldHandleFloatingPointPrecision
(
)
{
double
result
=
0.1
+
0.2
;
// Floating point comparison needs tolerance
assertThat
(
result
)
.
isCloseTo
(
0.3
,
within
(
0.0001
)
)
;
}
@Test
void
shouldHandleSpecialFloatingPointValues
(
)
{
assertThat
(
Double
.
POSITIVE_INFINITY
)
.
isGreaterThan
(
Double
.
MAX_VALUE
)
;
assertThat
(
Double
.
NEGATIVE_INFINITY
)
.
isLessThan
(
Double
.
MIN_VALUE
)
;
assertThat
(
Double
.
NaN
)
.
isNotEqualTo
(
Double
.
NaN
)
;
// NaN != NaN
}
@Test
void
shouldHandleVerySmallAndLargeNumbers
(
)
{
double
tiny
=
Double
.
MIN_VALUE
;
double
huge
=
Double
.
MAX_VALUE
;
assertThat
(
tiny
)
.
isGreaterThan
(
0
)
;
assertThat
(
huge
)
.
isPositive
(
)
;
}
@Test
void
shouldHandleZeroInDivision
(
)
{
double
result
=
1.0
/
0.0
;
assertThat
(
result
)
.
isEqualTo
(
Double
.
POSITIVE_INFINITY
)
;
double
result2
=
-
1.0
/
0.0
;
assertThat
(
result2
)
.
isEqualTo
(
Double
.
NEGATIVE_INFINITY
)
;
double
result3
=
0.0
/
0.0
;
assertThat
(
result3
)
.
isNaN
(
)
;
}
}
Date/Time Boundary Testing
Min/Max Dates and Edge Cases
class
DateTimeBoundaryTest
{
@Test
void
shouldHandleMinAndMaxDates
(
)
{
LocalDate
min
=
LocalDate
.
MIN
;
LocalDate
max
=
LocalDate
.
MAX
;
assertThat
(
min
)
.
isBefore
(
max
)
;
assertThat
(
DateUtils
.
isValid
(
min
)
)
.
isTrue
(
)
;
assertThat
(
DateUtils
.
isValid
(
max
)
)
.
isTrue
(
)
;
}
@Test
void
shouldHandleLeapYearBoundary
(
)
{
LocalDate
leapYearEnd
=
LocalDate
.
of
(
2024
,
2
,
29
)
;
assertThat
(
leapYearEnd
)
.
isNotNull
(
)
;
assertThat
(
LocalDate
.
of
(
2024
,
2
,
29
)
)
.
isEqualTo
(
leapYearEnd
)
;
}
@Test
void
shouldHandleInvalidDateInNonLeapYear
(
)
{
assertThatThrownBy
(
(
)
->
LocalDate
.
of
(
2023
,
2
,
29
)
)
.
isInstanceOf
(
DateTimeException
.
class
)
;
}
@Test
void
shouldHandleYearBoundaries
(
)
{
LocalDate
newYear
=
LocalDate
.
of
(
2024
,
1
,
1
)
;
LocalDate
lastDay
=
LocalDate
.
of
(
2024
,
12
,
31
)
;
assertThat
(
newYear
)
.
isBefore
(
lastDay
)
;
}
@Test
void
shouldHandleMidnightBoundary
(
)
{
LocalTime
midnight
=
LocalTime
.
MIDNIGHT
;
LocalTime
almostMidnight
=
LocalTime
.
of
(
23
,
59
,
59
)
;
assertThat
(
almostMidnight
)
.
isBefore
(
midnight
)
;
}
}
Array Index Boundary Testing
First, Last, and Out of Bounds
class
ArrayBoundaryTest
{
@Test
void
shouldHandleFirstElementAccess
(
)
{
int
[
]
array
=
{
1
,
2
,
3
,
4
,
5
}
;
assertThat
(
array
[
0
]
)
.
isEqualTo
(
1
)
;
}
@Test
void
shouldHandleLastElementAccess
(
)
{
int
[
]
array
=
{
1
,
2
,
3
,
4
,
5
}
;
assertThat
(
array
[
array
.
length
-
1
]
)
.
isEqualTo
(
5
)
;
}
@Test
void
shouldThrowOnNegativeIndex
(
)
{
int
[
]
array
=
{
1
,
2
,
3
}
;
assertThatThrownBy
(
(
)
->
{
int
value
=
array
[
-
1
]
;
}
)
.
isInstanceOf
(
ArrayIndexOutOfBoundsException
.
class
)
;
}
@Test
void
shouldThrowOnOutOfBoundsIndex
(
)
{
int
[
]
array
=
{
1
,
2
,
3
}
;
assertThatThrownBy
(
(
)
->
{
int
value
=
array
[
10
]
;
}
)
.
isInstanceOf
(
ArrayIndexOutOfBoundsException
.
class
)
;
}
@Test
void
shouldHandleEmptyArray
(
)
{
int
[
]
empty
=
{
}
;
assertThat
(
empty
.
length
)
.
isZero
(
)
;
assertThatThrownBy
(
(
)
->
{
int
value
=
empty
[
0
]
;
}
)
.
isInstanceOf
(
ArrayIndexOutOfBoundsException
.
class
)
;
}
}
Concurrent and Thread Boundary Testing
Null and Race Conditions
import
java
.
util
.
concurrent
.
*
;
class
ConcurrentBoundaryTest
{
@Test
void
shouldHandleNullInConcurrentMap
(
)
{
ConcurrentHashMap
<
String
,
String
>
map
=
new
ConcurrentHashMap
<
>
(
)
;
map
.
put
(
"key"
,
"value"
)
;
assertThat
(
map
.
get
(
"nonexistent"
)
)
.
isNull
(
)
;
}
@Test
void
shouldHandleConcurrentModification
(
)
{
List
<
Integer
>
list
=
new
CopyOnWriteArrayList
<
>
(
List
.
of
(
1
,
2
,
3
,
4
,
5
)
)
;
// Should not throw ConcurrentModificationException
for
(
int
num
:
list
)
{
if
(
num
==
3
)
{
list
.
add
(
6
)
;
}
}
assertThat
(
list
)
.
hasSize
(
6
)
;
}
@Test
void
shouldHandleEmptyBlockingQueue
(
)
throws
InterruptedException
{
BlockingQueue
<
String
>
queue
=
new
LinkedBlockingQueue
<
>
(
)
;
assertThat
(
queue
.
poll
(
)
)
.
isNull
(
)
;
}
}
Parameterized Boundary Testing
Multiple Boundary Cases
import
org
.
junit
.
jupiter
.
params
.
ParameterizedTest
;
import
org
.
junit
.
jupiter
.
params
.
provider
.
CsvSource
;
class
ParameterizedBoundaryTest
{
@ParameterizedTest
@CsvSource
(
{
"null, false"
,
// null
"'', false"
,
// empty
"' ', false"
,
// whitespace
"a, true"
,
// single char
"abc, true"
// normal
}
)
void
shouldValidateStringBoundaries
(
String
input
,
boolean
expected
)
{
boolean
result
=
StringValidator
.
isValid
(
input
)
;
assertThat
(
result
)
.
isEqualTo
(
expected
)
;
}
@ParameterizedTest
@ValueSource
(
ints
=
{
Integer
.
MIN_VALUE
,
0
,
1
,
-
1
,
Integer
.
MAX_VALUE
}
)
void
shouldHandleNumericBoundaries
(
int
value
)
{
assertThat
(
value
)
.
isNotNull
(
)
;
}
}
Best Practices
Test explicitly at boundaries
- don't rely on random testing
Test null and empty separately
from valid inputs
Use parameterized tests
for multiple boundary cases
Test both sides of boundaries
(just below, at, just above)
Verify error messages
are helpful for invalid boundaries
Document why
specific boundaries matter
Test overflow/underflow
for numeric operations
Constraints and Warnings
Integer overflow
Be aware that integer operations can overflow silently; use Math.addExact() to detect
Floating point precision
Never use exact equality for floating point; always use tolerance-based assertions
NaN behavior
Remember that NaN != NaN; use Float.isNaN() or Double.isNaN() for detection
Collection size limits
Be mindful of memory when testing with very large collections
String encoding
Test with various Unicode characters and encodings for internationalization
Date/time boundaries
Be aware of timezone transitions and daylight saving time changes
Array indexing
Always test index boundaries including 0, length-1, and out-of-bounds scenarios
Common Pitfalls
Testing only "happy path" without boundary cases
Forgetting null/empty cases
Not testing floating point precision
Not testing collection boundaries (empty, single, many)
Not testing string boundaries (null, empty, whitespace)
Troubleshooting
Floating point comparison fails
Use
isCloseTo(expected, within(tolerance))
.
Collection boundaries unclear
List cases explicitly: empty (0), single (1), many (>1).
Date boundary confusing
Use LocalDate.MIN , LocalDate.MAX for clear boundaries. References Integer.MIN_VALUE/MAX_VALUE Double.MIN_VALUE/MAX_VALUE AssertJ Floating Point Assertions Boundary Value Analysis
返回排行榜