跳到主要内容

2024-08-06

One-liner

雁子南飞时,故人忆往事。 --- 《醉柳引·故人安在兮》 · 月雩


Springboot mock database

@SpringBootTest
@AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.NONE)
@AutoConfigureMockMvc
public class ReaderControllerTest {
@MockBean
private ReaderRepository readerRepository;

@Autowired
private MockMvc mockMvc;

@Test
void createReaderTest() throws Exception {
var reader = ReaderData.getReader();

when(readerRepository.findByEmail(anyString())).thenReturn(Optional.empty());
when(readerRepository.save(null)).thenReturn(reader);

var json = """
{ "name": "%s", "email": "%s" }
""".formatted(reader.getName(), reader.getEmail());

mockMvc
.perform(
post("/user").contentType("application/json").content(json))
.andExpect(status().isOk());
}
}

Spock vs JUnit

Spock 和 JUnit 都是 Java 测试框架,但它们在设计理念、特性和使用方式上有一些显著的区别。

共同点

  • 测试框架:两者都是用于编写和运行自动化测试的框架。
  • 集成工具:都可以与常见的构建工具(如 Maven 和 Gradle)集成。
  • 单元测试支持:两者都支持编写单元测试。

不同点

1. 语法与语言特性

  • Spock:使用 Groovy 语言编写测试,这使得测试代码更简洁和更具可读性。Spock 测试规范 (Specification) 是基于 Groovy DSL 的。
    class MySpec extends Specification {
    def "feature method"() {
    expect:
    1 + 1 == 2
    }
    }
  • JUnit:使用 Java 语言编写测试。JUnit 的测试类和方法通过注解来标识。
    public class MyTest {
    @Test
    public void testAddition() {
    assertEquals(2, 1 + 1);
    }
    }

2. 测试生命周期

  • Spock:使用 setup, setupSpec, cleanup, cleanupSpec 方法来定义测试的生命周期方法。
    class MySpec extends Specification {
    def setup() { /* 每个测试方法前执行 */ }
    def cleanup() { /* 每个测试方法后执行 */ }
    def setupSpec() { /* 类加载后执行一次 */ }
    def cleanupSpec() { /* 类卸载前执行一次 */ }
    }
  • JUnit:使用 @Before, @After, @BeforeClass, @AfterClass 注解来定义生命周期方法。
    public class MyTest {
    @Before
    public void setUp() { /* 每个测试方法前执行 */ }
    @After
    public void tearDown() { /* 每个测试方法后执行 */ }
    @BeforeClass
    public static void beforeAll() { /* 类加载后执行一次 */ }
    @AfterClass
    public static void afterAll() { /* 类卸载前执行一次 */ }
    }

3. 行为驱动开发(BDD)支持

  • Spock:内置支持 BDD,提供了 given-when-then 的测试结构,使得测试用例更加可读和表达性更强。
    class MySpec extends Specification {
    def "addition should work correctly"() {
    given: "two numbers"
    int a = 1
    int b = 1

    when: "they are added"
    int result = a + b

    then: "the result is correct"
    result == 2
    }
    }
  • JUnit:通过第三方库(如 Cucumber)支持 BDD,但本身不直接支持 given-when-then 结构。
    public class MyTest {
    @Test
    public void testAddition() {
    // given
    int a = 1;
    int b = 1;

    // when
    int result = a + b;

    // then
    assertEquals(2, result);
    }
    }

4. 参数化测试

  • Spock:内置参数化测试支持,通过 where 块可以轻松定义多组测试数据。
    class MySpec extends Specification {
    def "addition should work correctly"() {
    expect:
    a + b == result

    where:
    a | b || result
    1 | 1 || 2
    2 | 3 || 5
    }
    }
  • JUnit:需要使用 @ParameterizedTest 注解和其他注解(如 @ValueSource@CsvSource)来定义参数化测试。
    @ParameterizedTest
    @CsvSource({
    "1, 1, 2",
    "2, 3, 5"
    })
    public void testAddition(int a, int b, int result) {
    assertEquals(result, a + b);
    }

5. Mocking

  • Spock:内置强大的 mocking 支持,可以直接在测试中使用 MockStubSpy 等。
    class MySpec extends Specification {
    def "should use mock"() {
    given:
    def mock = Mock(MyService)

    when:
    mock.doSomething()

    then:
    1 * mock.doSomething()
    }
    }
  • JUnit:需要使用第三方 mocking 框架(如 Mockito)来实现 mocking 功能。
    @ExtendWith(MockitoExtension.class)
    public class MyTest {
    @Mock
    MyService myService;

    @Test
    public void testMock() {
    myService.doSomething();
    verify(myService, times(1)).doSomething();
    }
    }

总结

  • Spock:以简洁、可读性强的语法和内置的 BDD 支持为特点,适合需要编写复杂测试逻辑和希望测试代码更具表达力的开发者。
  • JUnit:更加传统和广泛使用的 Java 测试框架,适合希望保持 Java 语法一致性和依赖最小化的开发者。

两者各有优劣,选择哪一个取决于项目需求和团队偏好。