5

使用 Kotlin 编写 Spring 测试

 3 years ago
source link: https://blog.ixk.me/post/writing-spring-tests-with-kotlin
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

使用 Kotlin 编写 Spring 测试

2021-05-23 • Otstar Lin •

通常情况下我们写 Spring 的项目的时候会使用 Java 语言来进行业务开发,同时使用 Java 来进行单元测试。但是 Java 由于其冗长的代码,我们在编写测试代码的效率并不高,而且我们在编写的测试代码的时候通常会考虑多种情况,代码量也就跟着急剧膨胀,带来了不小的时间浪费。最头疼的是进行 MockMvc 模拟请求测试,Java 在 15 之前都不支持多行字符串,这也就导致了我们需要一行一行的进行拼接,阅读起来非常不直观,也不能很好的利用 Intellij IDEA 的注入语言。

那么我们有什么办法可以解决这些问题呢?

我们知道 Java 是运行在 Java 虚拟机(JVM)之上的,而 JVM 本身是语言无关的,不论上层语言是使用何种语言编写的,只要能编译成字节码文件,就能在 JVM 上运行。所以 JVM 并不只是能运行 Java 的程序。比如 Kotlin、Scala、Groovy 都可以在 JVM 上运行。除了运行不同代码外,语言之间也可以互相调用,因为代码经过编译后就与上层语言没有关系了(大家都是字节码,为什么不能一起合作呢 🤣)。

所以按照这种方式,我们就可以使用更为直观和方便的语言来编写测试代码。其中 Kotlin 与 Groovy 对 Spring 有较好的兼容性,我们可以使用这两种语言来编写测试代码(本文使用 Kotlin)。

首先,我们需要先创建一个项目(当然也可以在已有项目上修改),创建的方式就和纯 Java 项目一样创建即可。

配置 Maven

创建好后就可以进行配置了,我们需要配置 Maven 的依赖和插件,即 pom.xml 文件,按照 Kotlin 的文档进行操作。

首先修改 properties 属性,同 Spring 创建后自带了一个 java.version,我们需要添加一个 kotlin.version 的属性。将版本分离出来有助于后续维护:

1<properties>
2  <java.version>11</java.version>
3  <kotlin.version>1.5.0</kotlin.version>
4</properties>

然后添加 Kotlin 标准库的依赖,需要注意,我们只需要在测试环境中使用 Kotlin,所以把 scope 定义为 test

1<dependency>
2  <groupId>org.jetbrains.kotlin</groupId>
3  <artifactId>kotlin-stdlib</artifactId>
4  <version>${kotlin.version}</version>
5  <scope>test</scope>
6</dependency>

接着就是配置 Maven 插件,用于编译 Kotlin 代码,将以下的代码直接复制到 build.plugins 标签里即可。注意不要把 Spring 的 Maven 给删了。

1<plugin>
2  <groupId>org.jetbrains.kotlin</groupId>
3  <artifactId>kotlin-maven-plugin</artifactId>
4  <version>${kotlin.version}</version>
5  <executions>
6    <execution>
7      <id>compile</id>
8      <goals>
9        <goal>compile</goal>
10      </goals>
11      <configuration>
12        <sourceDirs>
13          <sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
14          <sourceDir>${project.basedir}/src/main/java</sourceDir>
15        </sourceDirs>
16      </configuration>
17    </execution>
18    <execution>
19      <id>test-compile</id>
20      <goals>
21        <goal>test-compile</goal>
22      </goals>
23      <configuration>
24        <sourceDirs>
25          <sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
26          <sourceDir>${project.basedir}/src/test/java</sourceDir>
27        </sourceDirs>
28      </configuration>
29    </execution>
30  </executions>
31</plugin>
32<plugin>
33  <groupId>org.apache.maven.plugins</groupId>
34  <artifactId>maven-compiler-plugin</artifactId>
35  <version>3.5.1</version>
36  <executions>
37    <!-- Replacing default-compile as it is treated specially by maven -->
38    <execution>
39      <id>default-compile</id>
40      <phase>none</phase>
41    </execution>
42    <!-- Replacing default-testCompile as it is treated specially by maven -->
43    <execution>
44      <id>default-testCompile</id>
45      <phase>none</phase>
46    </execution>
47    <execution>
48      <id>java-compile</id>
49      <phase>compile</phase>
50      <goals>
51        <goal>compile</goal>
52      </goals>
53    </execution>
54    <execution>
55      <id>java-test-compile</id>
56      <phase>test-compile</phase>
57      <goals>
58        <goal>testCompile</goal>
59      </goals>
60    </execution>
61  </executions>
62</plugin>

此时该项目已经具备了编译 Kotlin 的能力。但是我们还没有导入任何 Kotlin 的测试库,只使用 JUnit 的话并不方便,我们可以加一些断言库用于便捷的编写测试:

1<dependency>
2  <groupId>io.kotest</groupId>
3  <artifactId>kotest-runner-junit5-jvm</artifactId>
4  <version>4.5.0</version>
5  <scope>test</scope>
6</dependency>
7<dependency>
8  <groupId>io.kotest</groupId>
9  <artifactId>kotest-assertions-core-jvm</artifactId>
10  <version>4.5.0</version>
11  <scope>test</scope>
12</dependency>
13<dependency>
14  <groupId>io.kotest</groupId>
15  <artifactId>kotest-assertions-json</artifactId>
16  <version>4.5.0</version>
17  <scope>test</scope>
18</dependency>

Kotest 也是一个和 JUnit 类似的单元测试工具,不过我们只会用到断言的功能,因为 Kotest 虽然有 Spring 测试的支持,不过可能会遇到很多奇奇怪怪的问题(至少我是没弄好,还不如直接用 JUnit)。

配置好测试环境了,我们就可以开始编写业务代码了。本文就不用实际的项目进行测试了,就随便写了一个控制器和几个函数:

1@RestController
2public class IndexController {
3
4    @GetMapping("/index")
5    public String get() {
6        return "index";
7    }
8
9    @PostMapping("/index")
10    public String post(@RequestParam("value") final String value) {
11        return value;
12    }
13
14    @PutMapping("/json")
15    public JsonNode json(@RequestBody JsonNode node) {
16        return node;
17    }
18}
1@Service
2public class UserService {
3
4    public String getUserName(final Long id) {
5        return "username: " + id;
6    }
7}

有了业务代码就可以进行测试了,首先我们需要在 test 文件夹里创建一个 kotlin 文件夹,用于存放 Kotlin 的测试代码(直接在 java 文件夹里写应该也可以,不过还是规范一点好),然后将 kotlin 设为测试文件夹(IDEA 不会自动识别)

2320210523204833.png

用于测试 UserService 的代码如下:

1@SpringBootTest
2class UserServiceTest {
3    @Autowired
4    private lateinit var userService: UserService
5
6    @Test
7    fun getUserName() {
8        userService.getUserName(1) shouldBe "username: 1"
9        userService.getUserName(2) should {
10            it shouldBe "username: 2"
11            it.length shouldBe 11
12        }
13    }
14}

可以看到 Kotlin 提供了非常多的语法糖,在测试的时候可以避免编写出不直观且重复的代码。相比之下 Java 的代码就显得有些不太直观了:

1@SpringBootTest
2class UserServiceTest {
3
4    @Autowired
5    private UserService userService;
6
7    @Test
8    void getUserName() {
9        assertEquals("username: 1", userService.getUserName(1L));
10        final String userName = userService.getUserName(2L);
11        assertEquals("username: 2", userName);
12        assertEquals(11, userName.length());
13    }
14}

MockMvc 测试

其实对于普通的单元测试代码是 Java 还好,但是在 MockMvc 的测试中 Kotlin 的优势就体现出来了。Spring 为 Kotlin 提供了大量的 DSL 支持:

1@WebMvcTest(IndexController::class)
2class MockMvcTest {
3    @Autowired
4    private lateinit var mockMvc: MockMvc
5
6    @Test
7    fun get() {
8        mockMvc.get("/index").andExpect {
9            status { isOk() }
10            content {
11                contentTypeCompatibleWith("text/plain")
12                string("index")
13            }
14        }
15    }
16
17    @Test
18    fun post() {
19        mockMvc.post("/index") {
20            param("value", "test value")
21        }.andExpect {
22            status { isOk() }
23            content {
24                string("test value")
25            }
26        }.andDo {
27            print()
28            handle {
29                println(it.response.characterEncoding)
30            }
31        }
32    }
33
34    @Test
35    fun json() {
36        mockMvc.put("/json") {
37            contentType = MediaType.APPLICATION_JSON
38            accept = MediaType.APPLICATION_JSON
39            content = """
40                {
41                  "key": "value",
42                  "key2": {
43                    "key3": [1, 2, 3]
44                  }
45                }
46            """.trimIndent()
47        }.andExpect {
48            status { isOk() }
49            content {
50                contentType(MediaType.APPLICATION_JSON)
51            }
52            jsonPath("$.key") {
53                value("value")
54            }
55            jsonPath("$.key2.key3.length()") {
56                value(3)
57            }
58        }
59    }
60}

最近都在写云笔记的项目(微服务),之后打算做成毕设,也算加个能拿得出手的项目,本文的想法也是在头疼 MockMvc 测试的 Json 请求参数实在难以阅读,突然想到的,如果你也有相关的烦恼的时候也可以试试,不过大多数开发者应该还是会使用 Java 来编写测试代码吧,毕竟更加熟悉😂。

前不久拿到了趋势科技的暑假实习 offer,不用再担心暑假没地方实习了🤣,只不过应该会挺晚才放假,难受😥。可以稍微摸鱼下了,不过后面还有秋招还是要接着准备呀。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK