⚠️ 警告:

本文所述内容仅能帮助你在 JetBrains IDEA 下以不修改代码框架中任何 package 命名的方式,优雅地完成实验内容,但注意到 实验指导-Lab0-3.3.4 中所述:

在提交至 GitHub 仓库前,请将实验代码从 Eclipse / IDEA 环境脱离开来,建议你自行使用 JDK、Ant (http://ant.apache.org)、Maven (http://maven.apache.org)、Gradle (https://gradle.org)等工具进行 build,或者在提交至 GitHub 仓库之后使用 Travis-CI (https://travis-ci.org)进行在线 build。如果因为缺少某些库文件导致你的程序无法运行,TA 不再为其评分。

这意味着完成实验后需要将基于 Eclipse / IDEA 的项目转换成某一通用的构建系统描述的项目,以应对 TA 可能采取的自动编译评测方式。

如果你已经熟悉 IDEA 的基本使用,本文后半部分会讲述如何将 IDEA 普通 Java 项目迁移至使用 Gradle 构建,可直接转跳阅读 ➡️

HIT-CS32123 软件构造课程 Lab1 项目结构大致如图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
.Lab1
├── src
│   ├── P1
│   │   ├── MagicSquare.java
│   │   └── txt
│   │   ├── 1.txt
│   │   ├── 2.txt
│   │   ├── 3.txt
│   │   ├── 4.txt
│   │   └── 5.txt
│   ├── P2
│   │   ├── .gitignore
│   │   ├── rules
│   │   │   ├── RulesOf6005.java
│   │   │   └── RulesOf6005Test.java
│   │   └── turtle
│   │   ├── Action.java
│   │   ├── DrawableTurtle.java
│   │   ├── LineSegment.java
│   │   ├── PenColor.java
│   │   ├── Point.java
│   │   ├── Turtle.java
│   │   ├── TurtleGUI.java
│   │   ├── TurtleSoup.java
│   │   └── TurtleSoupTest.java
│   └── P3
│   ├── FriendshipGraph.java
│   └── Person.java
└── test
└── P3
└── FriendshipGraphTest.java

从拿到的框架可知 P2 是一个 Eclipse 项目,而众所周知,JetBrains IDEA 比 Eclipse 好用到不知道哪里去了。因此本文将介绍如何在 IDEA 中正确配置本次实验的项目结构。

先决条件

以下先决条件在此不再赘述:

  • 配置 JDK (版本要求大于 1111
  • 下载并安装 JetBrains IDEA (本文使用的是旗舰版,社区版与旗舰版的配置过程只有细微差别)

解决方案

新建项目

只需要注意项目类型选 Java 。

图 1

另外 JDK版本只需要大于课程要求的 1111 就行,其他的无所谓,因为稍后我们将通过 IDE 里的设置将代码所用特性限制在 Java 11 之前。

配置 P1

刚建好的项目如图所示,可以看见 src 下面什么都没有:

图 2

我们将下载到的代码框架的 P1P2 文件夹直接复制到 src 下,此处你可以直接用文件资源管理器进行该操作。注意,不建议把 Spring2022_HITCS_SC_Lab1 这个项目 clone 下来,尤其不建议直接 clonesrc 里,而只需要下载就可以。完成后如图所示:

图 3

可以发现此时 IDEA 将 P1P2 识别为 Java 包,但这是不对的,因为从 P2RulesOf6005.java 看, P1P2 包括之后的 P3 更像是可以独立编译运行的 Java 项目。 rules 应该是以 P2 作为顶层目录下的一个 package 而不是像现在这样 IDE 认为它应该被修改为包 P2.rules

图 4

为了解决这个问题,按下 Ctrl+Shift+Alt+S 来打开 Project Structure ,这是 IDEA 项目中一个非常重要的面板,你可以在这里利用 IDE 管理你的项目,比如你可以在左侧的 Project 里设置这个项目的语言等级(language level),比 JDK 版本(演示机上设置的是JDK 17)低的任何等级都可以被设置,在本次实验中我们允许使用 Java 8 - 11。但现在我们选择左侧的 Module

图 5

在继续之前,我们先引入 IDEA 中 Module 的概念,它不是从 Java 9 开始支持的那个 Java 中的 Module ,但有几分相似,事实上 IDEA 中一个项目被称为 Project , Project 下允许构建可以独立运行的子组件,并设置其间的依赖关系,称为 Module 。IDEA 还有许多概念与 Eclipse 中的一些概念存在对应关系:

图 6

理解上述概念后,我们知道,解决上述问题的一个方法是:实验所需要的 P1P2P3 在 IDEA 中应该以 Module 的形式存在。接下来我们将为 P1P2 创建 Module。

点击上方的 + 号,选择 New Module ,和创建项目的窗口非常类似,我们再次选择 Java :

图 7

图 8

而路径则需要选择这个项目下的 src/P1

图 9

图 10

此处表明一个 Module 确实像是一个新的 IDEA Java 项目,它为我们开发 Java 程序提供了一个独立的空间。

完成之后点击 OK 便回到了我们的 Project Structure 面板,在点击 OK 完成创建 P1 模块之前,先点击上方的 Sources 来到下面这个面板:

图 11

根据实验要求,我们 MagicSquare.java 应处于 P1 的根目录而不是 P1src 子目录下,因此需要选择 src ,然后将上方的 Sources 按钮取消选中,之后你甚至可以直接删除 P1 下的这一个 src 文件夹;最后选择 P1 根目录本身,选中 Sources 将其作为 P1 模块的 Sources Root

图 12

点击 OK 后我们便完成了 P1 模块的创建。可以看见 P1 的图标发生了明显变化,它现在不再是一个 package 而是一个可以独立编译运行的 Module

图 13

另外根据实验指导,我们需要在 P1 下创建一个 txt 子目录来存放所有 1.txt5.txt ,注意在一个 Source Root 下,创建一个 package 等同于创建一个文件夹,所以我们可以通过在 P1 下创建一个名为 txt 的包来创建 txt 这个目录。

图 14

至此我们完成了 P1 模块的创建,你甚至可以写一个 Hello World! 程序来测试一下。

配置 P2 | JUnit 配置

P2P1 的不同之处在于它提供了一个代码框架,同时它还依赖于 JUnitP2 配置的前半部分与 P1 完全相同,为了检验是否理解了 IDEA Module 的概念,作为练习,请读者自行为 P2 创建 Module 。完成后结果如图所示:

图 15

但是注意到此时项目还是有警告的,因为我们还没有配置 JUnit ,事实上这并不是一种良好的项目结构,测试类通常不应该与源码在同一模块下。但是在 IDEA 下完成 JUnit 配置十分容易,我们只需要打开 RulesOf6005Test.java ,将鼠标悬停在有红线的 junit 上,出现的提示中选择 Add 'JUnit' to classpath

图 16

在弹出的窗口中选 OK ,IDEA 将自动为我们下载 JUnit 包,然后将其添加到 P2 模块的依赖项中。

图 17

稍等片刻,所有警告都会消失, Project StructureDependencis 面板也显示 JUnit 确实被添加到了 P2 的依赖项列表里。

图 18

点击 RulesOf6005TesttestMayUseCodeInAssignment 右侧的小三角形,然后 Run 之,可以运行这一项单元测试。虽然出错了,但是可以看见 testMayUseCodeInAssignment 是可以运行的,只是 RulesOf6005.mayUseCodeInAssignment 还没有实现而已,所以报错,但这表明我们的对 P2 的配置已经完成了。

图 19

配置 P3 | 模块间的依赖关系

P3 是一个面向对象编程的练习,也是一个体验单元测试流程的练习。我们先来回顾一下实验指导要求的目录结构:

1
2
3
4
5
6
7
8
.Lab1
├── src
│   └── P3
│   ├── FriendshipGraph.java
│   └── Person.java
└── test
└── P3
└── FriendshipGraphTest.java

P1 P2 一样,我们将 P3test 里的 P3 都视作模块,唯一不同的是,test 里的 P3 是专门用来测试 src 里的 P3 的模块,因此虽然它的目录是 test/P3 但是个人认为给模块命名时称之为 P3Test 比较好,像这样:

图 1

其他步骤不再赘述,最终效果如图所示:

图 2

图 3

按照实验要求,我们需要先在 P3 中实现 FriendshipGraphPerson 类的功能,然后在 src/P3P3Test 与此意思相同,后文将不再区分)的 FriendshipGraphTest 类中对其进行测试。

为了演示这点而不违反 Collaboration policy ,本文将在 P3 创建 FriendshipGraph 类并在里面创建一个 public static int getInt(int x) 方法,这个方法传入一个 int 并返回这个 int 本身,非常无聊,但有助于演示;然后在 P3Test 中创建一个 FriendshipGraphTest 类,其中的 public void friendshipGraphGetIntTest() 方法用于测试 FriendshipGraph.getInt 方法:

图 4

此处是本节的重点,形如 assertEquals(0, FriendshipGraph.getInt(1)) 便可以用于测试。因为在 FriendshipGraphTest 类的 friendshipGraphGetIntTest 方法中,我们既使用了 JUnit 库的方法 assertEquals ,又使用了 FriendshipGraph 中的方法,所以我们称 “FriendshipGraphTest 依赖于 JUnitFriendshipGraph” ;因为 FriendshipGraphTestP3Test 模块中, FriendshipGraphP3 模块中,所以同理,我们称 “P3Test 模块依赖于 P3 模块” 。

按照 P2 介绍的方式,我们可以很容易地为 P3Test 引入 JUnit 单元测试库,在此作为练习,效果如图所示:

图 5

此时 JUnit 可以正常使用,但是 FriendshipGraph 标红表示此处不可用,因为我们还没有设置 P3Test 依赖于 P3 。可以看见一旦我们将鼠标悬在 FriendshipGraph 上方,智能的 IDEA 就会建议我们将 P3 添加为依赖项。IDEA 给出这个正确的建议的原因之一是当前项目里只有 P3 里有 FriendshipGraph 类,具有特殊性,所以我们还是来看一下如何将一个模块设置为另一个模块的依赖项。

首先还是打开 Project Structure ,然后 选择 P3Test 模块,打开 Dependencies 面板,点击 + 号,选择 Module Dependency

图 6

在弹出的窗口中选择 P3

图 7

点击 OK ,可以看见我们成功将 P3 模块添加到 P3Test 的依赖项列表:

图 8

到此为止,我们已经完成了本次实验在 IDEA 中的全部配置。运行一下 FriendshipGraphTest 中的单元测试发现可以正确运行:

图 9

后续步骤

迁移至 Gradle 进行构建

正如前文所述,如果 TA 使用自动化测试,那么提交的项目应该与 IDE 无关。此处我们推荐使用 Gradle 管理项目。过程如下:

  • 将下列依赖项放至项目根目录下 lib 文件夹内:

    • junit-4.13.1.jar
    • hamcrest-core-1.31.jar
    • hamcrest-core-1.3-sources.jar
  • 在项目根目录下新建 settings.gradle.kts ,填写内容如下:

    1
    rootProject.name = "项目名"
  • 在项目根目录下新建 build.gradle.kts ,详细配置请参考奆佬 Nullptr 的 [软构] 记一次 Gradle 的坑

  • 接着点击 Link Gradle project ,等待 IDEA 完成 Gradle 项目初始化。

完成这一切后你可能发现 Project StructureP1 等模块被重复了两次,这是因为 Gradle 将这些模块视作 Resource 并创建在根模块下。你可以删掉其中不在项目根模块中的那一个,因为它们原本是由 IDEA 管理的,现在不需要了。