Java敏捷开发案例

wen java案例 2

本文目录导读:

Java敏捷开发案例

  1. 案例背景与用户故事 (User Story)
  2. 技术栈选型 (Tech Stack)
  3. 敏捷开发流程实践
  4. 代码实战 (TDD导向)
  5. 持续集成与代码质量
  6. 每日站会示例 (Daily Standup)
  7. Sprint评审演示
  8. 敏捷回顾要点

这是一个关于 Java敏捷开发 的完整案例介绍,涵盖 Spring Boot + JPA + TDD 的典型实践。

我们将模拟一个最经典的业务场景:在线书店用户注册与图书搜索 功能开发。


案例背景与用户故事 (User Story)

Scrum 框架为例,产品经理 (PO) 定义了第一个Sprint的任务:

US-001:用户注册

作为访客,我希望通过邮箱和密码注册,以便成为会员并购买图书 验收标准

  1. 邮箱不能重复。
  2. 密码长度必须 >= 6位。
  3. 注册成功后返回用户ID。

US-002:图书搜索

作为会员,我希望按书名模糊搜索,以便快速找到想买的书 验收标准

  1. 支持关键词模糊搜索。
  2. 无结果时返回空列表,非null。

技术栈选型 (Tech Stack)

  • 框架:Spring Boot 3.x
  • ORM:Spring Data JPA (Hibernate)
  • 数据库:H2 (开发/测试用) 或 MySQL
  • 测试:JUnit 5 + Mockito + AssertJ
  • 构建:Maven / Gradle
  • 开发模式TDD (测试驱动开发) + 结对编程

敏捷开发流程实践

我们按 3天Sprint 演示:

  • Day 1 (上午):计划会议 + 任务拆分
  • Day 1 (下午):TDD开发用户注册
  • Day 2:TDD开发图书搜索
  • Day 3:集成测试 + 代码审查 + 演示

代码实战 (TDD导向)

1 第1次迭代:编写测试(红)

test层:UserServiceTest.java

@SpringBootTest
class UserServiceTest {
    @Autowired
    private UserService userService;
    @MockBean
    private UserRepository userRepository;
    @Test
    void shouldRegisterUserSuccessfully() {
        // Given
        RegisterRequest request = new RegisterRequest("test@example.com", "password123");
        when(userRepository.existsByEmail("test@example.com")).thenReturn(false);
        when(userRepository.save(any(User.class))).thenAnswer(invocation -> {
            User user = invocation.getArgument(0);
            user.setId(1L);
            return user;
        });
        // When
        UserResponse response = userService.register(request);
        // Then
        assertThat(response).isNotNull();
        assertThat(response.getUserId()).isEqualTo(1L);
        assertThat(response.getEmail()).isEqualTo("test@example.com");
    }
    @Test
    void shouldThrowExceptionWhenEmailExists() {
        // Given
        RegisterRequest request = new RegisterRequest("existing@example.com", "password123");
        when(userRepository.existsByEmail("existing@example.com")).thenReturn(true);
        // When & Then
        assertThrows(DuplicateEmailException.class, () -> {
            userService.register(request);
        });
    }
    @Test
    void shouldThrowExceptionWhenPasswordTooShort() {
        // Given
        RegisterRequest request = new RegisterRequest("test@example.com", "123");
        // When & Then
        assertThrows(InvalidPasswordException.class, () -> {
            userService.register(request);
        });
    }
}

2 第1次迭代:实现代码(绿)

实体层:User.java

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(unique = true, nullable = false)
    private String email;
    @Column(nullable = false)
    private String password;
    @CreationTimestamp
    private LocalDateTime createdAt;
}

服务层:UserService.java

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    public UserResponse register(RegisterRequest request) {
        // 验证密码长度
        if (request.getPassword().length() < 6) {
            throw new InvalidPasswordException("Password must be at least 6 characters");
        }
        // 检查邮箱唯一性
        if (userRepository.existsByEmail(request.getEmail())) {
            throw new DuplicateEmailException("Email already registered");
        }
        // 创建并保存用户
        User user = new User();
        user.setEmail(request.getEmail());
        user.setPassword(passwordEncoder.encode(request.getPassword()));
        User savedUser = userRepository.save(user);
        // 返回响应
        return new UserResponse(savedUser.getId(), savedUser.getEmail());
    }
}

3 第2次迭代:图书搜索功能

测试:BookServiceTest.java

@Test
void shouldReturnBooksWhenKeywordMatchesTitle() {
    // Given
    String keyword = "Java";
    Book book1 = new Book(1L, "Java 入门", "作者A");
    Book book2 = new Book(2L, "Java 高级", "作者B");
    when(bookRepository.findByTitleContaining(keyword)).thenReturn(List.of(book1, book2));
    // When
    List<BookResponse> result = bookService.searchByTitle(keyword);
    // Then
    assertThat(result).hasSize(2);
    assertThat(result.get(0).getTitle()).contains("Java");
}
@Test
void shouldReturnEmptyListWhenKeywordNotFound() {
    // Given
    when(bookRepository.findByTitleContaining("PHP")).thenReturn(Collections.emptyList());
    // When
    List<BookResponse> result = bookService.searchByTitle("PHP");
    // Then
    assertThat(result).isEmpty();  // 不是 null
}

实现:BookService.java

public List<BookResponse> searchByTitle(String keyword) {
    List<Book> books = bookRepository.findByTitleContaining(keyword);
    return books.stream()
        .map(book -> new BookResponse(book.getId(), book.getTitle(), book.getAuthor()))
        .collect(Collectors.toList());
}

仓库层:BookRepository.java

public interface BookRepository extends JpaRepository<Book, Long> {
    List<Book> findByTitleContaining(String keyword);
}

持续集成与代码质量

在敏捷开发中,不仅写代码,还要保证质量:

1 CI/CD 配置

# .github/workflows/ci.yml (GitHub Actions)
name: Java CI
on: [push, pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
      - name: Build with Maven
        run: mvn clean verify  # 包括测试
      - name: Generate Coverage Report
        run: mvn jacoco:report
      - name: Upload Coverage
        uses: codecov/codecov-action@v3

2 代码审查 Checklist

检查项 通过?
测试覆盖率高(>80%)
无硬编码配置
遵循单一职责原则
方法 < 30行
无 System.out

每日站会示例 (Daily Standup)

场景:Sprint 第2天

开发者A

  • ✅ 昨天完成了 US-001 的注册功能(已完成测试)
  • 🔄 今天开始 US-002 的搜索功能
  • ❌ 遇到问题:JPA模糊查询性能,计划使用全文索引

开发者B

  • ✅ 昨天搭建了 H2 测试数据库配置
  • 🔄 今天写集成测试
  • ❌ 没有障碍

Sprint评审演示

最终展示时,可使用 SwaggerPostman 演示:

POST /api/users/register
Content-Type: application/json
{
    "email": "alice@example.com",
    "password": "secure123"
}

成功响应:

{
    "userId": 1001,
    "email": "alice@example.com"
}
GET /api/books/search?keyword=Java

响应:

[
    {"id": 1, "title": "Java 入门", "author": "张三"},
    {"id": 2, "title": "Java 高级编程", "author": "李四"}
]

敏捷回顾要点

在项目交付后,团队应进行 Sprint Retrospective

做得好的 需要改进的
TDD 显著减少bug 前期估算不够准确
每日站会高效 需要更多自动化测试
代码审查及时 文档更新滞后

这个案例展示了 Java敏捷开发 的完整闭环:

  1. 需求 → 用户故事 + 验收标准
  2. 开发 → TDD (红-绿-重构)
  3. 协作 → 每日站会 + 结对编程
  4. 质量 → CI/CD + 代码审查
  5. 交付 → Sprint评审 + 回顾

对于团队来说,关键不在于工具多先进,而在于 反馈循环是否快测试是否充分团队沟通是否顺畅

如果你有具体的业务场景想用敏捷方式实现(比如支付、购物车、订单系统),我可以为你定制一个更详细的案例。

抱歉,评论功能暂时关闭!