Project 1 EC:自动评分器
介绍#
这个项目是可选项目,总共价值 32 分额外加分。在这个项目中,你将为 Project 1 的数据结构部分构建一个基础自动评分器。该项目可能颇具挑战,并且属于额外加分内容,因此在 Office Hours 和 Ed 上会被安排为最低帮助优先级。
在骨架中,我们为 Project 1 额外加分提供了以下文件:
student/StudentArrayDeque.java:一个有缺陷的ArrayDeque实现。tester/ArrayDequeSolution.java:一个正确的ArrayDeque实现。tester/AssertEqualsStringDemo.java:演示如何使用assertEquals。tester/StudentArrayDequeLauncher.java:演示如何使用StudentArrayDeque。
你会再次注意到,我们在这里使用包,把学生实现与测试基础设施分离开来。
获取骨架文件#
和以前一样,使用命令 git pull skeleton master 拉取骨架。
随机测试#
告诉你一个有趣的秘密:Project 1 的自动评分器在很大程度上依赖随机测试。例如,Gradescope 上的 JUnit 测试会随机调用你的 LinkedListDeque 类以及课程的正确实现 LinkedListDequeSolution 的方法;一旦发现任何不一致,测试就会失败,并打印出导致失败的操作序列。在项目这一部分,你将使用同样的思路,假装自己正在为该类编写自动评分器。
这里还会介绍如何制作漂亮的 JUnit 错误消息。这实际上是一项非常重要的技能。如果你的测试具有容易阅读而且有帮助的错误消息,未来的雇主会非常喜欢你。
开始项目时,首先应确保 IntelliJ 已经正确导入项目。尝试运行 StudentArrayDequeLauncher.java。如果正常工作,你应当看到 0 到 9 之间的数字被打印出来,但顺序不一定相同。如果遇到问题,请按照 Lab 2 中的说明操作。
任务 I#
接下来,在 tester 包中创建名为 TestArrayDequeEC.java 的 JUnit 测试文件。文件开头应包含所需的 package 声明:
package tester;java然后确保添加必要的 import:
import static org.junit.Assert.*;
import org.junit.Test;
import student.StudentArrayDeque;java在这个文件中编写一个用 @Test 注解标记的 JUnit 测试。测试方法叫什么并不重要。你的测试应随机调用 StudentArrayDeque 和 ArrayDequeSolution 的方法,直到二者在某个输出上产生分歧。可以使用 StdRandom 库生成随机数。
把 StudentArrayDequeLauncher 当作指导。如果复制粘贴其中的代码,请务必使用 @source 标记注明来源。你在 Project 1 中编写的测试在这里也可能很有用。
这项额外加分作业不会测试 equals(Object o) 和 iterator() 方法,因此只为其他方法编写测试。
在这个项目中,Deque 的类型必须使用 Integer,即 StudentArrayDeque<Integer>。只使用 addFirst、addLast、removeFirst 和 removeLast 方法就应能够找到错误;当然,你也可以尝试其他方法。
你的测试不应引发 NullPointerException。确保永远不要从空的 ArrayDeque 中删除,因为 Integer x = ad.removeFirst() 可能导致 NullPointerException。此外,在本项目中,从 Deque 取出值时始终使用 Integer,不要使用 int;也就是说,不要写 int x = ad.removeFirst()。为什么这会产生问题,请阅读下方“常见问题”。
任务 II#
当你成功让测试稳定失败后,更棘手的部分开始了。仅仅告诉学生代码失败,只会带来眼泪、悲伤、困惑以及深夜的 Ed 帖子。因此,你要修改自动评分器,让它告诉学生真正有用的信息。
为此,我们将利用 assertEquals(message, expected, actual) 方法。它会向用户输出有帮助的消息。
此方法如何使用,可以查看 examples 文件夹中的 AssertEqualsStringDemo.java。
修改 TestArrayDequeEC.java,使传递给 assertEquals 的 message 参数包含一系列会导致 StudentArrayDeque 输出错误答案的操作。
传给 assertEquals 的字符串消息必须是一系列方法调用,并且序列中的最后一次调用会产生错误返回值。例如,若先在前端添加 5,再在前端添加 3,随后从前端删除时得到错误值,那么传给 assertEquals 的 String 消息必须恰好是下面这样,每条命令之间有换行:
addFirst(5)
addFirst(3)
removeFirst()text不需要把 expected 和 actual 值放进 String 消息,因为它们会作为 expected 和 actual 参数单独传给 assertEquals。换句话说,你的消息不应该长这样:
addFirst(5)
addFirst(3)
removeFirst(), student was 3, correct was 7text它也不应该长这样:
addFirst(5)
addFirst(3)
removeFirst(): 3
removeLast(): 4text提示#
- 编写一次比较整个 Deque 的测试可能不是好主意。假设你写了一个返回
false的compareDeques(studentDeque, solutionDeque)方法。即使该方法返回false,它也没有告诉你是哪一个操作导致了失败。测试单个操作的输出要容易得多,例如比较student.removeFirst()与solution.removeFirst()。 - 如果你坚持一次比较整个 Deque,
assertEquals不会按你希望的方式工作。例如,即便deque1和deque2中的所有元素都相同,assertEquals(deque1, deque2)也不会返回 true。如果确实想比较整个 Deque,需要编写自己的比较方法;不过老实说,这项作业完全没有必要这样做。 StdRandom类是生成随机数最简单的方式。可查看其官方文档了解方法列表。- 本作业不需要进行任何异常捕获或抛出,我们此时还没有在 CS 61B 中学习这些内容。
- 在执行操作的同时构建失败序列!不要等检测到失败之后才尝试构造它,那样非常困难。
常见问题#
怎样为 printDeque() 编写测试?#
这会相当复杂,而且我们的自动评分器也没有聪明到能够读取你的输出。请测试其他方法。如果你真的非常好奇,可以搜索 “redirect standard output”。
我遇到了 “reference to assertEquals is ambiguous” 错误。#
遇到神秘错误消息时,应当始终尝试在网上搜索。请记住,成为能够自给自足的程序员是 CS 61B 的主要目标之一。Google 的第一个结果应该就足够了。
我不断遇到 NullPointerException#
首先,确保没有尝试从可用大小之外的位置执行 get。其次,如果你写了类似 int result = deque.removeFirst() 的代码,请改写为 Integer result = deque.removeFirst()。
发生这一错误,是因为 Java 会自由地把 Integer(装箱类型)转换为 int(基本类型),这叫作拆箱。可是只有引用类型能够为 null;因此,如果尝试自动把 null Integer 转换为 int,你的代码就会得到 NullPointerException。StudentArrayDeque 本身有缺陷,可能错误地返回 null,从而在你的代码中触发这个问题。
自动评分器在抱怨我的失败序列。#
可以想象,这个 gold project 的自动评分器是一头奇怪而复杂的野兽,因为它必须对自动评分器的输出进行自动评分。为了简化事情,失败 assert 的 String 参数必须包含失败序列,并且只能包含失败序列;所有测试必须因为 assert 失败而失败,不能因为 null pointer exception 失败。传给 assert 语句的 String 参数中不能包含任何无关信息。
自动评分器仍然在抱怨我的失败序列。#
必须包含引发失败的那个操作。例如,如果 size() 返回错误值,就必须在失败序列中包含 size(),因为要求提供的是“一系列方法调用,其中序列中的最后一次调用产生错误返回值”。还要确保失败序列只出现一次。
我把这些都试过了,自动评分器仍然在抱怨我的失败序列。#
从在线自动评分器中复制它报告的失败序列,编写一个简单的 Quick.java 文件:创建 studentDeque,执行列出的操作,并打印最后一步的结果。你很可能会发现,实际结果与测试在自动评分器中报告的并不相同。最可能的原因是漏记了某个操作,或者加入了实际上没有执行的操作。
原始页面:https://sp21.datastructur.es/materials/proj/proj1/proj1ec ↗