test-unit
npx skills add https://github.com/sundny8/hydpromptkit --skill test-unit
Agent 安装分布
Skill 文档
åå æµè¯ä»£ç çæ
è¯è¨è¦æ±
æµè¯ä»£ç 注éåæµè¯æè¿°å¿ é¡»ä½¿ç¨ä¸æï¼å æ¬ @DisplayName æµè¯åç§°ãæ¹æ³æ³¨éãåºæ¯è¯´æçã
åè½è¯´æ
çæé«è´¨éçåå æµè¯ä»£ç ï¼å æ¬æµè¯ç¨ä¾è®¾è®¡ãMock 对象ãæè¨éªè¯çã
ææ¡£ä¾èµ
çæåå æµè¯æ¶ï¼å¯ä»¥åèä»¥ä¸ææ¡£ï¼
- è®¾è®¡ææ¡£ï¼å¯éï¼ï¼åè /projectdocs/design/{name}_design.md
ä½¿ç¨æ¹å¼
å½ç¨æ·æä¾å¾ æµä»£ç ï¼ç±»/æ¹æ³/模åï¼ï¼ä»¥åå¯éçè®¾è®¡ææ¡£è·¯å¾ææµè¯èå´è¯´ææ¶ï¼çæåå æµè¯ä»£ç ã
è¾å ¥è¦æ±
åèè®¾è®¡ææ¡£ï¼å¯éï¼ï¼
- 妿æè®¾è®¡ææ¡£ï¼å¯ä»¥åè
design/genå½ä»¤çæçè®¾è®¡ææ¡£ - è®¾è®¡ææ¡£ä½ç½®ï¼
/projectdocs/design/{project}_design_{timestamp}.md - ä»è®¾è®¡ææ¡£ä¸æåï¼
- æ¥å£å®ä¹åä¸å¡é»è¾
- æ°æ®æ¨¡åååæ®µéªè¯è§å
- å¼å¸¸åºæ¯åè¾¹çæ¡ä»¶
åèä»£ç æä»¶ï¼
- å¯ä»¥ç´æ¥ä¸ºç°æä»£ç æä»¶çæåå æµè¯
- æ¯æä¸º ServiceãControllerãMapper çå±çææµè¯
åæ®µçæçç¥ï¼
- æ¯æä¼ å ¥åæ°è¿è¡å段çæ
- ç¨æ·å¯ä»¥æå®çææµè¯çèå´ï¼
- æç±»çæï¼
unit --class=UserServiceï¼åªçææå®ç±»çæµè¯ï¼ - ææ¹æ³çæï¼
unit --method=createUserï¼åªçææå®æ¹æ³çæµè¯ï¼ - æå±çæï¼
unit --layer=serviceï¼åªçææå®å±çæµè¯ï¼ - ææ¨¡åçæï¼
unit --module=userï¼çææå®æ¨¡åçæææµè¯ï¼ - æåºæ¯çæï¼
unit --scenario=normal,exceptionï¼åªçææå®åºæ¯çæµè¯ï¼
- æç±»çæï¼
æµè¯åå
1. FIRST åå
- Fastï¼å¿«éï¼ï¼æµè¯åºè¯¥å¿«éè¿è¡
- Independentï¼ç¬ç«ï¼ï¼æµè¯ä¹é´ä¸åºç¸äºä¾èµ
- Repeatableï¼å¯éå¤ï¼ï¼æµè¯ç»æåºè¯¥ä¸è´
- Self-Validatingï¼èªæéªè¯ï¼ï¼æµè¯åºè¯¥ææç¡®çéè¿/å¤±è´¥ç»æ
- Timelyï¼åæ¶ï¼ï¼æµè¯åºè¯¥åæ¶ç¼å
2. AAA 模å¼
- Arrangeï¼åå¤ï¼ï¼è®¾ç½®æµè¯æ°æ®åç¯å¢
- Actï¼æ§è¡ï¼ï¼æ§è¡è¢«æµè¯çæ¹æ³
- Assertï¼æè¨ï¼ï¼éªè¯ç»ææ¯å¦ç¬¦å颿
3. æµè¯è¦çåå
- æ£å¸¸åºæ¯æµè¯
- è¾¹çæ¡ä»¶æµè¯
- å¼å¸¸åºæ¯æµè¯
- å¹¶ååºæ¯æµè¯ï¼å¦éè¦ï¼
æµè¯çææ¥éª¤
1. åæè¢«æµä»£ç
- çè§£æ¹æ³åè½åä¸å¡é»è¾
- è¯å«è¾å ¥åæ°åè¿åå¼
- è¯å«ä¾èµçå¤é¨æå¡
- è¯å«å¯è½çå¼å¸¸æ åµ
2. 设计æµè¯ç¨ä¾
- ååºæææµè¯åºæ¯
- ç¡®å®æµè¯æ°æ®
- 设计 Mock çç¥
- ç¡®å®æè¨å 容
3. çææµè¯ä»£ç
- å建æµè¯ç±»
- çææµè¯æ¹æ³
- æ·»å Mock 对象
- ç¼åæè¨è¯å¥
- æ·»å æµè¯æ³¨é
4. ä¼åæµè¯ä»£ç
- æåå ¬å ±æµè¯æ°æ®
- ä½¿ç¨æµè¯å·¥å ·ç±»
- ä¿ææµè¯ä»£ç ç®æ´
- ç¡®ä¿æµè¯å¯è¯»æ§
æµè¯æ¡æ¶æ¯æ
JUnit 5
@Test
@DisplayName("æµè¯æè¿°")
void testMethod() {
// Arrange
// Act
// Assert
}
Mockito
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
AssertJ
assertThat(result)
.isNotNull()
.hasSize(3)
.extracting(User::getName)
.containsExactly("Alice", "Bob", "Charlie");
æµè¯ç¨ä¾æ¨¡æ¿
åºç¡æµè¯æ¨¡æ¿
@SpringBootTest
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
@DisplayName("åå»ºç¨æ· - æååºæ¯")
void testCreateUser_Success() {
// Arrange
UserDTO userDTO = new UserDTO();
userDTO.setName("Alice");
userDTO.setEmail("alice@example.com");
User savedUser = new User();
savedUser.setId(1L);
savedUser.setName("Alice");
when(userRepository.save(any(User.class)))
.thenReturn(savedUser);
// Act
Long userId = userService.createUser(userDTO);
// Assert
assertThat(userId).isEqualTo(1L);
verify(userRepository, times(1)).save(any(User.class));
}
@Test
@DisplayName("åå»ºç¨æ· - é®ç®±éå¤")
void testCreateUser_EmailExists() {
// Arrange
UserDTO userDTO = new UserDTO();
userDTO.setEmail("alice@example.com");
when(userRepository.existsByEmail("alice@example.com"))
.thenReturn(true);
// Act & Assert
assertThatThrownBy(() -> userService.createUser(userDTO))
.isInstanceOf(BusinessException.class)
.hasMessage("é®ç®±å·²åå¨");
verify(userRepository, never()).save(any(User.class));
}
@Test
@DisplayName("åå»ºç¨æ· - åæ°ä¸ºç©º")
void testCreateUser_NullParameter() {
// Act & Assert
assertThatThrownBy(() -> userService.createUser(null))
.isInstanceOf(IllegalArgumentException.class);
}
}
æµè¯åºæ¯è¦ç
1. æ£å¸¸åºæ¯
@Test
@DisplayName("æ¥è¯¢ç¨æ· - ç¨æ·åå¨")
void testGetUser_UserExists() {
// æµè¯æ£å¸¸çä¸å¡æµç¨
}
2. è¾¹çæ¡ä»¶
@Test
@DisplayName("å页æ¥è¯¢ - 页ç 为0")
void testPageQuery_PageNumZero() {
// æµè¯è¾¹çå¼
}
@Test
@DisplayName("å页æ¥è¯¢ - æ¯é¡µå¤§å°ä¸ºæå¤§å¼")
void testPageQuery_MaxPageSize() {
// æµè¯è¾¹çå¼
}
3. å¼å¸¸åºæ¯
@Test
@DisplayName("å é¤ç¨æ· - ç¨æ·ä¸åå¨")
void testDeleteUser_UserNotFound() {
// æµè¯å¼å¸¸æ
åµ
}
@Test
@DisplayName("æ´æ°ç¨æ· - æ°æ®åºå¼å¸¸")
void testUpdateUser_DatabaseException() {
// æµè¯å¼å¸¸æ
åµ
}
4. 空å¼å¤ç
@Test
@DisplayName("åå»ºç¨æ· - åæ°ä¸ºnull")
void testCreateUser_NullParameter() {
// æµè¯ç©ºå¼å¤ç
}
@Test
@DisplayName("æ¥è¯¢ç¨æ· - è¿å空å表")
void testQueryUsers_EmptyResult() {
// æµè¯ç©ºç»æ
}
Mock çç¥
1. Mock å¤é¨ä¾èµ
// Mock Repository
@Mock
private UserRepository userRepository;
// Mock å¤é¨æå¡
@Mock
private EmailService emailService;
// Mock Redis
@Mock
private RedisTemplate<String, Object> redisTemplate;
2. Mock è¿åå¼
// è¿åç¹å®å¼
when(userRepository.findById(1L))
.thenReturn(Optional.of(user));
// è¿å空å¼
when(userRepository.findById(999L))
.thenReturn(Optional.empty());
// æåºå¼å¸¸
when(userRepository.save(any()))
.thenThrow(new DataAccessException("æ°æ®åºå¼å¸¸"));
3. éªè¯è°ç¨
// éªè¯æ¹æ³è¢«è°ç¨
verify(userRepository).save(any(User.class));
// éªè¯è°ç¨æ¬¡æ°
verify(emailService, times(2)).sendEmail(anyString());
// éªè¯ä»æªè°ç¨
verify(userRepository, never()).delete(any());
// éªè¯è°ç¨é¡ºåº
InOrder inOrder = inOrder(userRepository, emailService);
inOrder.verify(userRepository).save(any());
inOrder.verify(emailService).sendEmail(anyString());
æè¨æå·§
åºç¡æè¨
// JUnit æè¨
assertEquals(expected, actual);
assertTrue(condition);
assertNotNull(object);
assertThrows(Exception.class, () -> method());
// AssertJ æè¨ï¼æ¨èï¼
assertThat(actual).isEqualTo(expected);
assertThat(condition).isTrue();
assertThat(object).isNotNull();
assertThatThrownBy(() -> method())
.isInstanceOf(Exception.class);
éåæè¨
assertThat(list)
.isNotEmpty()
.hasSize(3)
.contains("Alice", "Bob")
.doesNotContain("Charlie");
assertThat(list)
.extracting(User::getName)
.containsExactly("Alice", "Bob", "Charlie");
对象æè¨
assertThat(user)
.isNotNull()
.extracting(User::getName, User::getAge)
.containsExactly("Alice", 25);
assertThat(user)
.hasFieldOrPropertyWithValue("name", "Alice")
.hasFieldOrPropertyWithValue("age", 25);
åæ°è¯´æ
–class=<ç±»å>
æå®ä¸ºæä¸ªç±»çææµè¯ã
示ä¾ï¼
test/unit --class=UserService
test/unit --class=OrderController
test/unit --class=ProductMapper
–method=<æ¹æ³å>
æå®åªä¸ºæä¸ªæ¹æ³çææµè¯ï¼éè¦é å –class 使ç¨ï¼ã
示ä¾ï¼
test/unit --class=UserService --method=createUser
test/unit --class=UserService --method=updateUser,deleteUser
–layer=<å±çº§>
æå®ä¸ºæä¸ªå±çº§çææç±»çææµè¯ã
å¯ç¨å±çº§ï¼
service– Service å±controller– Controller å±mapper– Mapper/DAO å±util– å·¥å ·ç±»
示ä¾ï¼
test/unit --layer=service
test/unit --layer=controller
test/unit --layer=mapper
–module=<模åå>
æå®ä¸ºæä¸ªæ¨¡åçææç±»çææµè¯ã
示ä¾ï¼
test/unit --module=user
test/unit --module=order
test/unit --module=product
–scenario=<åºæ¯å表>
æå®åªçææäºåºæ¯çæµè¯ç¨ä¾ï¼å¤ä¸ªåºæ¯ç¨éå·åéã
å¯ç¨åºæ¯ï¼
normal– æ£å¸¸åºæ¯exception– å¼å¸¸åºæ¯boundary– è¾¹çæ¡ä»¶null– 空å¼å¤ç
示ä¾ï¼
test/unit --scenario=normal
test/unit --scenario=exception
test/unit --scenario=normal,exception
test/unit --scenario=normal,exception,boundary,null
ç»å使ç¨
å¯ä»¥ç»åå¤ä¸ªåæ°ä½¿ç¨ï¼
# 为æå®æ¨¡åçæå®å±çææµè¯
test/unit --module=user --layer=service
# 为æå®ç±»çææå®åºæ¯çæµè¯
test/unit --class=UserService --scenario=exception
# 为æå®æ¨¡åçæå®å±çææå®åºæ¯çæµè¯
test/unit --module=order --layer=service --scenario=normal,exception
# 为æå®ç±»çæå®æ¹æ³çææµè¯
test/unit --class=UserService --method=createUser,updateUser
æä½³å®è·µ
1. æµè¯å½å
// æ¨èï¼æ¹æ³å_åºæ¯_é¢æç»æ
testCreateUser_EmailExists_ThrowsException()
testGetUser_UserNotFound_ReturnsNull()
testUpdateUser_ValidData_Success()
// æä½¿ç¨ @DisplayName
@DisplayName("åå»ºç¨æ· - é®ç®±å·²åå¨ - æåºå¼å¸¸")
2. æµè¯é离
// æ¯ä¸ªæµè¯ç¬ç«å夿°æ®
@BeforeEach
void setUp() {
// å夿µè¯æ°æ®
}
@AfterEach
void tearDown() {
// æ¸
çæµè¯æ°æ®
}
3. æµè¯è¦çç
- ç®æ ï¼ä»£ç è¦çç > 80%
- æ ¸å¿ä¸å¡é»è¾ï¼100% è¦ç
- å·¥å ·ç±»ã常éç±»ï¼å¯éå½éä½è¦æ±
4. é¿å çåæ³
- â æµè¯ä¹é´æä¾èµå ³ç³»
- â æµè¯è®¿é®ç宿°æ®åº
- â æµè¯ä¾èµå¤é¨æå¡
- â æµè¯å å« Thread.sleep()
- â è¿åº¦ä½¿ç¨ Mock
注æäºé¡¹
- ä¿æç®åï¼æµè¯ä»£ç åºè¯¥æ¯è¢«æµä»£ç æ´ç®å
- ä¸ä¸ªæµè¯ä¸ä¸ªæè¨ï¼æ¯ä¸ªæµè¯åªéªè¯ä¸ä¸ªåºæ¯
- æµè¯å¯è¯»æ§ï¼æµè¯åºè¯¥æäºçè§£
- é¿å é»è¾ï¼æµè¯ä¸ä¸åºè¯¥æå¤æé»è¾
- åæ¶æ´æ°ï¼ä»£ç ä¿®æ¹ååæ¥æ´æ°æµè¯
- åæ®µçæï¼å¯¹äºå¤§å项ç®ï¼å»ºè®®ä½¿ç¨
--moduleæ--layeråæ°åæ®µçæ - åºæ¯éæ©ï¼å¯ä»¥ä½¿ç¨
--scenarioåæ°åªçæç¹å®åºæ¯çæµè¯