unit-test-parameterized

安装量: 319
排名: #2895

安装

npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill unit-test-parameterized
Parameterized Unit Tests with JUnit 5
Overview
This skill provides patterns for writing efficient parameterized unit tests using JUnit 5's @ParameterizedTest. It covers @ValueSource, @CsvSource, @MethodSource, @EnumSource, @ArgumentsSource, and custom display names to run the same test logic with multiple input values, reducing test duplication and improving coverage.
When to Use
Use this skill when:
Testing methods with multiple valid inputs
Testing boundary values systematically
Testing multiple invalid inputs for error cases
Want to reduce test duplication
Testing multiple scenarios with similar assertions
Need data-driven testing approach
Instructions
Add junit-jupiter-params dependency
Ensure junit-jupiter-params is on test classpath
Choose appropriate source
Use @ValueSource for simple values, @CsvSource for tabular data, @MethodSource for complex objects
Match parameter types
Ensure test method parameters match data source types
Use descriptive display names
Set
name = "..."
for readable test output
Test boundary values
Include edge cases, null values, and extreme values in parameters
Use @EnumSource
Test all enum values or filter specific ones
Create custom ArgumentsProvider
Build reusable data sources for complex scenarios
Keep assertions simple
Focus on single assertion per parameterized test
Examples
Setup: Parameterized Testing
Maven
<
dependency
>
<
groupId
>
org.junit.jupiter
</
groupId
>
<
artifactId
>
junit-jupiter
</
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.assertj:assertj-core"
)
}
Basic Pattern: @ValueSource
Simple Value Testing
import
org
.
junit
.
jupiter
.
params
.
ParameterizedTest
;
import
org
.
junit
.
jupiter
.
params
.
provider
.
ValueSource
;
import
static
org
.
assertj
.
core
.
api
.
Assertions
.
*
;
class
StringUtilsTest
{
@ParameterizedTest
@ValueSource
(
strings
=
{
"hello"
,
"world"
,
"test"
}
)
void
shouldCapitalizeAllStrings
(
String
input
)
{
String
result
=
StringUtils
.
capitalize
(
input
)
;
assertThat
(
result
)
.
startsWith
(
input
.
substring
(
0
,
1
)
.
toUpperCase
(
)
)
;
}
@ParameterizedTest
@ValueSource
(
ints
=
{
1
,
2
,
3
,
4
,
5
}
)
void
shouldBePositive
(
int
number
)
{
assertThat
(
number
)
.
isPositive
(
)
;
}
@ParameterizedTest
@ValueSource
(
booleans
=
{
true
,
false
}
)
void
shouldHandleBothBooleanValues
(
boolean
value
)
{
assertThat
(
value
)
.
isNotNull
(
)
;
}
}
@MethodSource for Complex Data
Factory Method Data Source
import
org
.
junit
.
jupiter
.
params
.
ParameterizedTest
;
import
org
.
junit
.
jupiter
.
params
.
provider
.
MethodSource
;
import
java
.
util
.
stream
.
Stream
;
class
CalculatorTest
{
static
Stream
<
org
.
junit
.
jupiter
.
params
.
provider
.
Arguments
>
additionTestCases
(
)
{
return
Stream
.
of
(
Arguments
.
of
(
1
,
2
,
3
)
,
Arguments
.
of
(
0
,
0
,
0
)
,
Arguments
.
of
(
-
1
,
1
,
0
)
,
Arguments
.
of
(
100
,
200
,
300
)
,
Arguments
.
of
(
-
5
,
-
10
,
-
15
)
)
;
}
@ParameterizedTest
@MethodSource
(
"additionTestCases"
)
void
shouldAddNumbersCorrectly
(
int
a
,
int
b
,
int
expected
)
{
int
result
=
Calculator
.
add
(
a
,
b
)
;
assertThat
(
result
)
.
isEqualTo
(
expected
)
;
}
}
@CsvSource for Tabular Data
CSV-Based Test Data
import
org
.
junit
.
jupiter
.
params
.
ParameterizedTest
;
import
org
.
junit
.
jupiter
.
params
.
provider
.
CsvSource
;
class
UserValidationTest
{
@ParameterizedTest
@CsvSource
(
{
"alice@example.com, true"
,
"bob@gmail.com, true"
,
"invalid-email, false"
,
"user@, false"
,
"@example.com, false"
,
"user name@example.com, false"
}
)
void
shouldValidateEmailAddresses
(
String
email
,
boolean
expected
)
{
boolean
result
=
UserValidator
.
isValidEmail
(
email
)
;
assertThat
(
result
)
.
isEqualTo
(
expected
)
;
}
@ParameterizedTest
@CsvSource
(
{
"123-456-7890, true"
,
"555-123-4567, true"
,
"1234567890, false"
,
"123-45-6789, false"
,
"abc-def-ghij, false"
}
)
void
shouldValidatePhoneNumbers
(
String
phone
,
boolean
expected
)
{
boolean
result
=
PhoneValidator
.
isValid
(
phone
)
;
assertThat
(
result
)
.
isEqualTo
(
expected
)
;
}
}
@CsvFileSource for External Data
CSV File-Based Testing
import
org
.
junit
.
jupiter
.
params
.
ParameterizedTest
;
import
org
.
junit
.
jupiter
.
params
.
provider
.
CsvFileSource
;
class
PriceCalculationTest
{
@ParameterizedTest
@CsvFileSource
(
resources
=
"/test-data/prices.csv"
,
numLinesToSkip
=
1
)
void
shouldCalculateTotalPrice
(
String
product
,
double
price
,
int
quantity
,
double
expected
)
{
double
total
=
PriceCalculator
.
calculateTotal
(
price
,
quantity
)
;
assertThat
(
total
)
.
isEqualTo
(
expected
)
;
}
}
// test-data/prices.csv:
// product,price,quantity,expected
// Laptop,999.99,1,999.99
// Mouse,29.99,3,89.97
// Keyboard,79.99,2,159.98
@EnumSource for Enum Testing
Enum-Based Test Data
import
org
.
junit
.
jupiter
.
params
.
ParameterizedTest
;
import
org
.
junit
.
jupiter
.
params
.
provider
.
EnumSource
;
enum
Status
{
ACTIVE
,
INACTIVE
,
PENDING
,
DELETED
}
class
StatusHandlerTest
{
@ParameterizedTest
@EnumSource
(
Status
.
class
)
void
shouldHandleAllStatuses
(
Status
status
)
{
assertThat
(
status
)
.
isNotNull
(
)
;
}
@ParameterizedTest
@EnumSource
(
value
=
Status
.
class
,
names
=
{
"ACTIVE"
,
"INACTIVE"
}
)
void
shouldHandleSpecificStatuses
(
Status
status
)
{
assertThat
(
status
)
.
isIn
(
Status
.
ACTIVE
,
Status
.
INACTIVE
)
;
}
@ParameterizedTest
@EnumSource
(
value
=
Status
.
class
,
mode
=
EnumSource
.
Mode
.
EXCLUDE
,
names
=
{
"DELETED"
}
)
void
shouldHandleStatusesExcludingDeleted
(
Status
status
)
{
assertThat
(
status
)
.
isNotEqualTo
(
Status
.
DELETED
)
;
}
}
Custom Display Names
Readable Test Output
import
org
.
junit
.
jupiter
.
params
.
ParameterizedTest
;
import
org
.
junit
.
jupiter
.
params
.
provider
.
ValueSource
;
class
DiscountCalculationTest
{
@ParameterizedTest
(
name
=
"Discount of {0}% should be calculated correctly"
)
@ValueSource
(
ints
=
{
5
,
10
,
15
,
20
}
)
void
shouldApplyDiscount
(
int
discountPercent
)
{
double
originalPrice
=
100.0
;
double
discounted
=
DiscountCalculator
.
apply
(
originalPrice
,
discountPercent
)
;
double
expected
=
originalPrice
*
(
1
-
discountPercent
/
100.0
)
;
assertThat
(
discounted
)
.
isEqualTo
(
expected
)
;
}
@ParameterizedTest
(
name
=
"User role {0} should have {1} permissions"
)
@CsvSource
(
{
"ADMIN, 100"
,
"MANAGER, 50"
,
"USER, 10"
}
)
void
shouldHaveCorrectPermissions
(
String
role
,
int
expectedPermissions
)
{
User
user
=
new
User
(
role
)
;
assertThat
(
user
.
getPermissionCount
(
)
)
.
isEqualTo
(
expectedPermissions
)
;
}
}
Combining Multiple Sources
ArgumentsProvider for Complex Scenarios
import
org
.
junit
.
jupiter
.
api
.
extension
.
ExtensionContext
;
import
org
.
junit
.
jupiter
.
params
.
ParameterizedTest
;
import
org
.
junit
.
jupiter
.
params
.
provider
.
Arguments
;
import
org
.
junit
.
jupiter
.
params
.
provider
.
ArgumentsProvider
;
import
org
.
junit
.
jupiter
.
params
.
provider
.
ArgumentsSource
;
import
java
.
util
.
stream
.
Stream
;
class
RangeValidatorArgumentProvider
implements
ArgumentsProvider
{
@Override
public
Stream
<
?
extends
Arguments
>
provideArguments
(
ExtensionContext
context
)
{
return
Stream
.
of
(
Arguments
.
of
(
0
,
0
,
100
,
true
)
,
// Min boundary
Arguments
.
of
(
100
,
0
,
100
,
true
)
,
// Max boundary
Arguments
.
of
(
50
,
0
,
100
,
true
)
,
// Middle value
Arguments
.
of
(
-
1
,
0
,
100
,
false
)
,
// Below range
Arguments
.
of
(
101
,
0
,
100
,
false
)
// Above range
)
;
}
}
class
RangeValidatorTest
{
@ParameterizedTest
@ArgumentsSource
(
RangeValidatorArgumentProvider
.
class
)
void
shouldValidateRangeCorrectly
(
int
value
,
int
min
,
int
max
,
boolean
expected
)
{
boolean
result
=
RangeValidator
.
isInRange
(
value
,
min
,
max
)
;
assertThat
(
result
)
.
isEqualTo
(
expected
)
;
}
}
Testing Edge Cases with Parameters
Boundary Value Analysis
class
BoundaryValueTest
{
@ParameterizedTest
@ValueSource
(
ints
=
{
Integer
.
MIN_VALUE
,
// Absolute minimum
Integer
.
MIN_VALUE
+
1
,
// Just above minimum
-
1
,
// Negative boundary
0
,
// Zero boundary
1
,
// Just above zero
Integer
.
MAX_VALUE
-
1
,
// Just below maximum
Integer
.
MAX_VALUE
// Absolute maximum
}
)
void
shouldHandleAllBoundaryValues
(
int
value
)
{
int
incremented
=
MathUtils
.
increment
(
value
)
;
assertThat
(
incremented
)
.
isNotLessThan
(
value
)
;
}
@ParameterizedTest
@CsvSource
(
{
", false"
,
// null
"'', false"
,
// empty
"' ', false"
,
// whitespace only
"a, true"
,
// single character
"abc, true"
// normal
}
)
void
shouldValidateStrings
(
String
input
,
boolean
expected
)
{
boolean
result
=
StringValidator
.
isValid
(
input
)
;
assertThat
(
result
)
.
isEqualTo
(
expected
)
;
}
}
Repeat Tests
Run Same Test Multiple Times
import
org
.
junit
.
jupiter
.
api
.
RepeatedTest
;
class
ConcurrencyTest
{
@RepeatedTest
(
100
)
void
shouldHandleConcurrentAccess
(
)
{
// Test that might reveal race conditions if run multiple times
AtomicInteger
counter
=
new
AtomicInteger
(
0
)
;
counter
.
incrementAndGet
(
)
;
assertThat
(
counter
.
get
(
)
)
.
isEqualTo
(
1
)
;
}
}
Best Practices
Use @ParameterizedTest
to reduce test duplication
Use descriptive display names
with
(name = "...")
Test boundary values
systematically
Keep test logic simple
- focus on single assertion
Organize test data logically
- group similar scenarios
Use @MethodSource
for complex test data
Use @CsvSource
for tabular test data
Document expected behavior
in test names
Common Patterns
Testing error conditions
:
@ParameterizedTest
@ValueSource
(
strings
=
{
""
,
" "
,
null
}
)
void
shouldThrowExceptionForInvalidInput
(
String
input
)
{
assertThatThrownBy
(
(
)
->
Parser
.
parse
(
input
)
)
.
isInstanceOf
(
IllegalArgumentException
.
class
)
;
}
Testing multiple valid inputs
:
@ParameterizedTest
@ValueSource
(
ints
=
{
1
,
2
,
3
,
5
,
8
,
13
}
)
void
shouldBeInFibonacciSequence
(
int
number
)
{
assertThat
(
FibonacciChecker
.
isFibonacci
(
number
)
)
.
isTrue
(
)
;
}
Constraints and Warnings
Parameter count must match
The number of parameters from source must match test method signature
Type conversion is automatic
JUnit converts source values to target parameter types when possible
@ValueSource limitation
Only supports literals (strings, ints, longs, doubles); not objects or null
CSV escaping
Strings containing commas must be enclosed in single quotes in @CsvSource
MethodSource visibility
@MethodSource methods must be static, can be private but must be in same class
Display name placeholders
Use {0}, {1}, etc. to reference parameters in display names
Test execution order
Parameterized tests execute each parameter set as a separate test invocation
Troubleshooting
Parameter not matching
Verify number and type of parameters match test method signature.
Display name not showing
Check parameter syntax in
name = "..."
.
CSV parsing error
Ensure CSV format is correct and quote strings containing commas. References JUnit 5 Parameterized Tests @ParameterizedTest Documentation Boundary Value Analysis
返回排行榜