Skip to main content
  1. 归档/

OO Unit2 总结

·251 words·2 mins·
心得 学习 BUAA 面向对象
Table of Contents

量子电梯、影子电梯、猴子电梯、开子电梯……


JUnit、投喂包和编译打包相关
#

JUnit
#

OO 第二单元电梯,为了方便我们进行输入输出,课程组提供了 dataInput 程序对我们编译好的 jar 包进行管道输入,并提供一个 elevatorX.jar 作为我们代码的依赖。然而在使用过程中有诸多不便:

  1. 每次测试程序,都需要打包一遍,再从 out/ 文件夹下拖出来
  2. Windows 下 powershell 还运行不了(奇奇怪怪,是为什么呢?)

即便好心的睿睿准备了一个更好用的数据投喂机,但它需要作为 main 方法来使用,而且还不能上传到公测提交中(当然会查重)。

这时候,我们可以使用曾经在 oop 中接触到的 JUnit 方法,将 testMain/dataInput 等定时向我们的程序提供输入的方法编写为一个测试单元,运用测试单元就可以不用担心两个 main 方法的问题啦。

因为 JUnit 方法虽然可以上传提交,但是仍然会经过查重,这里不提供详细代码。所以下面仅提供一个我的操作流程,供大家参考。

软盘+蟒蛇图标
#

看到 dataInput 那个经典图标,我就知道这是 PyInstaller 打包的 Python 程序,于是从网上搜索了如何将它反编译出源码,并顺利得到了 dataInput 的源码。嗯,这么点源码打包成 5MB 的程序,太有 PyInstaller 那味了。

AI 机器,小子!
#

其实不管是 dataInput 的源码,还是 TestMain 的源码,都有被查重的风险,所以最简单的办法当然是拿着这些源码去找 AI,让它根据源码为我们写一个 MainClass.main()(或者 Main.main() 之类)的 JUnit 方法就好了。不过 AI 生成也不一定完全能够保证不被查重(

然后照着 oop 的方法设置 JUnit 就好了。

输入输出重定向
#

我发现测试方法的运行配置里没有“重定向输入”的选择,只有“将控制台输出保存到文件”的选择。也就是说我们得在 JUnit 方法里实现输入输出重定向。

我们可以使用 java.nio.file 下的 PathsPathFiles 等类来实现重定向。后面带 s 的俩类提供了许多静态方法。

Path testFilePath = Paths.get(System.getProperty("user.dir"), "stdin.txt");
Path outputFilePath = Paths.get(System.getProperty("user.dir"), "usrout.txt");
List<String> lines = Files.readAllLines(testFilePath);
PrintStream outputStream = new PrintStream(Files.newOutputStream(outputFilePath.toFile().toPath()));
System.setOut(outputStream);

这里我们将标准输出重定向到了 usrout.txt 文件,这个文件在用户当前工作目录下,也就是我们的项目的根目录下。

stdin.txt 就是带时间戳的输入,也放在同一目录下,使用 Paths.get()Files.readAllLines() 读取到 List<String> 中。

清理线程
#

实际使用 JUnit 方法时遇到了一些问题:

  1. MainClass.main() 方法一般都是创建线程、启动线程,然后就不管这些线程的死活了。
  2. JUnit 方法执行完成之后,IDEA 就直接把整个程序结束了。

也就是说,当 JUnit 方法把输入定时交给程序完成之后,程序就结束了,电梯线程以及其他线程就会被终止。

即便我们在 JUnit 方法里创建 main 方法的线程(比如叫 mainThread),然后在最后使用 mainThread.join() 等待线程结束。

但我们等待的只是 mainThread 这一线程的结束:当 main 方法运行完成之后,它就结束了。我们并不能确定 main 方法中创建的其他线程的状态。

所以解决办法有两个:

  1. main 方法中创建并收集线程,并在最后使用 thread.join() 方法等待所有线程结束。
  2. 在 JUnit 方法中一开始记录程序运行中的所有线程;在方法结束之前,再次记录程序运行中的所有线程,减去前面的进程,就可以得到 main 方法中创建的进程了。之后再等待这些线程结束就好了。

对于第二个方法,部分代码如下:

Set<Thread> initThreads = new HashSet<>(Thread.getAllStackTraces().keySet());
// ... after start main thread
Set<Thread> curThreads = new HashSet<>(Thread.getAllStackTraces().keySet());
curThreads.removeAll(initThreads);
Set<Thread> userThreads = new HashSet<>();
for (Thread t : curThreads) {
    if (t.isAlive() && !t.isDaemon()) {
        userThreads.add(t);
    }
}

这里需要排除一些 Daemon 守护线程,一般作垃圾回收、释放内存等用途。

编译打包
#

在编写测评机的时候,我遇到了编译打包源码的问题。

首先是编译源码,javac 不能识别并找到官方包依赖。

其次是打包时,jar 也不能识别并找到官方包依赖。

查了一番资料后,我得到了以下解决方法:

javac -d <project_dir> <main_class_path> -sourcepath <src_path> -classpath <path_to_elevator1_jar> #带依赖编译源码
jar xf <path_to_elevator1_jar> # 解压官方包
# 将解压后的文件复制到 <project_dir> 下
jar cfm <jar_name> <manifest_path> -C <project_dir> . # 将文件夹打包成 jar

其中:

  • <project_dir> 就是源码的工作目录
  • <main_class_path>main 方法所在类文件的位置
  • <src_path> 是指 main 方法所在类文件的所在目录,注意和 <project_dir> 的不同
  • <manifest_path> 是在 jar 包中固定位置与格式的一个 MANIFEST.MF 文件,可以参考其他打包好的 jar 包编写。

然后就可以得到正确的包了。

tsxb
Author
tsxb
Evaluate. Focus. Moderate.