CS61B项目练习笔记(二)--Lab3
经验点:
Stopwatch
的使用- 在类的两个实现之间执行比较测试。
- 随机调用类内部的方法。
- 在类的两个实现之间执行随机比较测试。
- 使用 IntelliJ 中的恢复按钮。
- 向断点添加条件。
- 创建异常断点。
Stopwatch
库的使用
1 | package timingtest; |
import edu.princeton.cs.algs4.Stopwatch;
导入库Stopwatch sw = new Stopwatch();
开始计时double timeInSeconds = sw.elapsedTime();
结束计时,返回时间
在类的两个实现之间执行比较测试。
测试代码的一种技术是进行“比较测试”。在这样的测试中,我们有两个相同类的实现。一个实现是已知的(或坚信的)是正确的,另一个正在开发中,尚未验证。
例如,我们提供了类 AListNoResizing
。此类不支持任何调整大小操作,只是具有 1000 的硬编码数组大小。这意味着它实际上没有用,因为它永远不能容纳超过 1000 个项目。但是,由于它非常简单,我们对它的工作充满信心。
相比之下,我们也提供了课程 BuggyAList
。此类具有一个基础数组,该数组会根据存储的数据量向上和向下调整大小。由于调整大小有点棘手,因此我们更加怀疑此类的正确性。顾名思义,它确实在某处有一个错误。本实验的其余部分的目标是找到此 bug。
1 | package randomizedtest; |
随机函数调用
原则上,可以仔细制作一组比较测试,最终找到错误。但是,另一种补充策略是使用随机方法,在该方法中,我们对两个实现进行随机调用,并使用 JUnit 方法来验证它们是否始终返回相同的值。
作为随机调用方法的函数示例,下面的代码随机调用 AList
addLast
对象 AListNoResizing
, size
总共调用其中一个函数的 N 次。
1 | package randomizedtest; |
调试器功能-条件断点和恢复
- 在显示 的
int operationNumber = StdRandom.uniform(0, 2);
行上设置断点。 - 然后,使用 debug 选项在 IntelliJ 中的这一行停止。
- 单击可视化工具,您将看到一个包含大量空值的数组,这些空值最终将存储要添加到列表中的数据。
- 单击“单步执行”,您将看到 operationNumber 设置为 0 或 1。这是因为该
StdRandom.uniform(0, 2)
函数返回 [0, 2] 范围内的随机整数,即排除正确的参数。如果选择的数字为 0,则将在列表末尾添加一个随机数。如果选择的数字是 1,则将打印尺寸。 - 单击调试器上的
resume
按钮(下面以黄色突出显示),我们的代码将再次遇到它,命中断点。 - 尝试单击“恢复”几次,您将看到值开始填充数组。请注意,每次单击“恢复”时,代码都会运行(就像您多次按下单步执行一样),直到它再次返回到断点。
- 我们还可以从可视化工具切换回能够查看打印语句的输出。为此,
Debugger
请再次单击(旁边Java Visualizer
)并继续单击恢复。在某些计算机上,您可能需要单击Debugger
而不是Console
。单击“恢复”的每个类型,都会看到另一个 print 语句,对应于对 addLast 或 size 的调用。 - 现在让我们尝试一个条件断点。右键单击断点,您会看到一个弹出框,上面写着“条件:”。在框中,键入
L.size() == 12
。 - 单击“恢复”,代码将一直运行,直到满足断点的条件,即大小为 12。尝试一下,然后单击可视化工具,您应该会看到大小现在为 12,数组中有 12 个项目。如果您不小心点击得太远,很遗憾,您必须重新启动测试。
这两个新功能(恢复和条件断点)对实验 3 的其余部分没有用。但是,它们可能会在将来的项目中派上用场,您需要在实验 4 中使用它们。此时应删除条件断点,以便它不会影响实验室的其余部分。
执行随机比较
1 | public class TestBuggyAList { |
这就提出了一个关于随机测试的重要观点:如果你应用随机操作,并且这个错误相当模糊,你的随机操作序列可能无法检测到这个错误!有一些方法可以改进随机测试来避免这个问题,但这超出了我们课程的范围。
另一个注意事项:随机测试不应替代精心设计的单元测试!我个人通常倾向于在可能的情况下进行非随机测试,并将随机测试视为一种补充测试方法。有关此问题的辩论,请参阅此链接。
异常断点的使用
请单击“运行 -> 查看断点”。您应该会看到一个类似这样的窗口弹出窗口:
单击左侧显示“任何例外”的复选框,然后单击显示“条件:”的复选框,然后在窗口中输入:
1 | this instanceof java.lang.ArrayIndexOutOfBoundsException |
完成此操作后,断点窗口应如下所示:
单击“调试”按钮,代码应在异常即将发生时停止。单击可视化工具,并尝试找出代码崩溃的原因。现在可以开始真正的问题解决了!
注意:如果在未指定条件的情况下使用调试功能,则代码将停止在一些不同的神秘位置。确保在未指定条件的情况下,绝不会选中“任何异常”。这是因为启动 JUnit 测试的过程会生成一堆最终被忽略的异常。这远远超出了我们课程的范围。如果使用完执行断点,则应取消选中左上角的“Java Exceptions Breakpoints”框。
答案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 private void resize(int capacity) {
Item[] a = (Item[]) new Object[capacity];
for (int i = 0; i < size; i += 1) {
a[i] = items[i];
}
items = a;
}
public Item removeLast() {
if ((size < items.length / 4) && (size > 4)) {
resize(size / 4);
}
Item x = getLast();
items[size - 1] = null;
size = size - 1;
return x;
}观察可知,是
removeLast()
方法在传入resize
方法值的时候过小,导致新收缩的数组太小,没办法存储所有原数组的值,答案为resize(size / 4)
改为resize(items.length / 4)