CS61b学习-part4

eve2333 发布于 2024-11-08 108 次阅读


7.1 A new way

程序员如何知道他的程序可行? what evidence did you have for project0 in 61b?

  • We gave you some tests.
  • Running main and seeing if the planets move around in a proper planetary way.
  • .The real MVP: Autograder.

In the real world, programmers believe their code works because of tests they write themselves.

Knowing that it works for sure is usually impossible. This will be our new way.

编写sort及测试

Sort.java

public class Sort{
    public static void sort(String[] x){
    }
}

TestSort.java

/* Tests thethe Sort class. */
public class TestSort{
    /* Test the Sort. sort method. */
     public static void testSort(){
        String[] input ={"i", "have","an","egg"}; 
        String[] expected {"an", "egg", "have", "i"}; 

        Sort.sort(input);
        /*if (input!=expected){
            System.out.println("Error! There seems to be a problem with Sort.sort."); 
            }    
        }*/

        for(int i=0;i<input.length;i+=1){
            if(!input[i].equals(expected[i])) {
                 System.out.println("Mismatch in position " + i + ", expected: " + expected[i] + ", but got: " + input[i]);
                return;
             }
         }
    }

        public static void main(String[] args)
            testSort(); 
        }
} 

中间for循环看可以使用org的一个官方来代替

org.junit.AssertEquals(expected,input);
org.junit.Assert.assertArrayEquals(expected,input);

选择排序

Sort.java

public class Sort {
    /** Sorts strings destructively. */
    public static void sort(String[] x){
        // Find the smallest item [done]
        // Move it to the front [done]
        //Selection sort the rest (using recursion??)
        int smallestIndex = findSmallest(x);
        swap(x,a0,smallestIndex);
        sort(x[1:);!!!!!!!!!!!!!
    }

    /** Swap item a with b. */
    public static void swap(String[] x, int a, int b){
        String temp = x[a];
        x[a]=x[b];
        x[b] = temp;
    }

    /**return the index of the smallest string in x*/
    public static int findSmallest(String[] x){
        int smallestIndex =0;
        for(int i=0;i<x.length;i+=1){


        }

这里由于字符串和int的api不同,需要转换,要微微修改一下

现在实现搜索的代码,进行某种递归调用, ​

在java中是不可能做到的,你不能对数组进行子索引或者获取a的中间地址,每当给你一个数组,得到所有的东西就可以了.现在有一个反直觉的东西:创建一个辅助方法,从特定位置开始对数组进行排序,索引.换句话说要得到最小的商品,我们需要弄清楚我们交换的地方,不是两个数字位置0但是两个位置,

这种排序方法基本上是让球滚动,能够有一个排序的助手,如果我们设置从位置start开始,从0开始,这将有效排序正确的数组

public class Sort {
    /** Sorts strings destructively. */
    public static void sort(String[] x){
        // Find the smallest item [done]
        // Move it to the front [done]
        // Selection sort the rest (using recursion?)
        sort(x,start:0);
    }

    /*Sorts x starting at position start.*/
    private static void sort(String[] x, int start){
        int smallestIndex = findSmallest(x);
        swap(x, start, smallestIndex);
        sort(x, start:start +1);
    }

    /*Swap item a with b.*/
    public static void swap(String[] x, int a, int b) {
        String temp = x[a];
        x[a]=x[b];
        x[b]= temp;
    }
    
    /*Return the index of the smallest string in x.*/
    public static int findSmallest(String[] x){
        int smallestIndex = 0;
        for (int i= 0; i< x.length; i += 1){
            int cmp=x[i].compareTo(x[smallestIndex]);
            //from the internet,if x[i]x[smallestIndex],cmp will be -1.
            if (cmp< 0){
                smallestIndex=i;
            }
        }
        return smallestIndex;
    }
}

运行发现超出索引out of bounds exception:通过dubugger和可视化工具,但是索引4不存在,递归方法打断,在smallestIndex前面中加入if(start==x.length){return ;}

第一次交换正常,逐步进行

7.9 Debugging and Testing, BFFs_哔哩哔哩_bilibili老师由于语言不通有点难以看懂,实际上这真是思考过程,sort.java

public class Sort {
    /** Sorts strings destructively. */
    public static void sort(String[] x){
        // Find the smallest item [done]
        // Move it to the front [done]
        // Selection sort the rest (using recursion?)
        sort(x,start:0);
    }

    /*Sorts x starting at position start.*/
    private static void sort(String[] x, int start){
        int smallestIndex = findSmallest(x);
        swap(x, start, smallestIndex);
        sort(x, start:start +1);
    }

    /*Swap item a with b.*/
    public static void swap(String[] x, int a, int b) {
        String temp = x[a];
        x[a]=x[b];
        x[b]= temp;
    }
    
    /*Return the index of the smallest string in x.Starting at start*/
    //从start开始
    public static int findSmallest(String[] x,int start){
        int smallestIndex = start;
        for (int i= 0; i< x.length; i += 1){
            int cmp=x[i].compareTo(x[smallestIndex]);
            //from the internet,if x[i]x[smallestIndex],cmp will be -1.
            if (cmp< 0){
                smallestIndex=i;
            }
        }
        return smallestIndex;
    }
}

TestSort.java

/* Tests the the Sort class. */
public class TestSort() {
    /* Test the Sort. sort method. */
    public static void testSort() {

        String[] input = {"i", "have", "an", "egg"};
        String[] expected = {"an", "egg", "have", "i"};
        Sort.sort(input);
        org.junit.Assert.assertArrayEquals(expected, input);
    }

    /* Test the Sort. findSmalLest method.*/
    public static void testFindSmallest() {
        String[] input = {"i", "have", "an", "egg"};
        int expected = 2;

        int actual Sort.findSmallest(input, 0);
        org.junit.Assert.assertEquals(expected, actual);

        String[] input2 = {"there", "are", "many", "pigs"};
        int expected2 = 2;

        int actual2 = Sort.findSmallest(input2, 2);
        org.junit.Assert.assertEquals(expected2, actual2);
    }

    /* Test the Sort. swap method.*/
    public static void testSwap() {
        String[] input = {"i", "have", "an", "egg"};
        int a = 0;
        int b = 2;
        String[] expected = {"an", "have", "i", "egg"};
        Sort.swap(input, a, b);
        org.junit.Assert.assertArrayEquals(expected, input);
    }

    public static void main(String[] args) {
        //testSort();
        testFindSmallest();
    }
}

The Evolution of our Design

  • Created testSort:testSort()
  • Created a sort skeleton:sort(String[] inputs)
  • Created testFindSmallest:testFindSmallest()
  • Created findSmallest:String findSmallest(String[] input)
  • Created testSwap:testSwap()     Used Google to figure out how to compare strings.
  • Created swap:swap(String[] input, int a, int b)    Used debugger to fix. 
  • Changed findSmallest:int findSmallest(String[] input)
  • Added helper method:sort(String[] inputs, int k)
  • Used debugger to realize fundamental design flaw in findSmallest
  • Modified findSmallest:int findSmallest(String[] input, int k)

好的,我们现在已经彻底完成了排序。当然,在现实世界中,我们会为我们的排序方法构建一个更全面的测试套件,而不仅仅是单次调用,但对于今天61B课程的目的来说,我们就到此为止了。所以我想对这个过程做一个小小的反思,并专注于一些我认为最重要的要点,希望你们能从中带走这些要点。因此,当你在现实世界中构建东西,无论是课堂环境还是你可能想要做的任何项目,这个过程往往是逐步的,你需要不断在完全不同的事情之间切换,甚至在工作过程中也要对项目的整个设计进行更改。我们刚才就看到了这一点,在编写我们的排序方法的过程中,我们必须完全改变`findSmallest`的签名并确保它也能正常工作,这种情况非常普遍。测试的好处在于它们提供了一种方式,让你不必同时记住太多的东西。比如我们可以设置一些支持点,让某部分大脑可以放在上面,这样它就可以安全地待在那里,而不需要自己支撑起所有重量,这可能是个有点奇怪的比喻,但没关系。我的意思是,比如通过`testSort`,我们能够暂时切换到`testFindSmallest`,专注于`findSmallest`,然后再回到之前的工作上下文,即继续处理排序。也许看这个视频很难看到我四处跳跃,但基本上我们是在处理排序,然后切换到`findSmallest`,之后又能够切换回来。而测试所做的就是使在这两个上下文之间的切换变得容易,因为我们可以一次只关注一个。

另一个好处是,这些测试提供了稳定性,你知道你的代码的基本部分是工作的。这为你程序的其余部分提供了一个坚实的基础。`findSmallest`和`swamp`这些功能都有很好的表现,这些小的部分都能工作得很好,因此如果你在代码的某个地方进行了更改,你有所有这些测试能够评估所有的部分,并告诉你何时出现问题,我认为这是非常好的。当涉及到较大的项目时,测试的好处在于它允许你重构代码,而所谓的重构是指取现有的代码片段,并以使它们更高效、更容易阅读或有某种一般性改进的方式进行更改,但仍然具有相同的功能,并且知道它仍然有效。假设我想要完全改变我在`findSmallest`中使用的方法,也许我用一个`while`循环代替`for`循环,或者谁知道呢,因为我认为我有一个更快或更有效的方法,那么我可以这样做,然后看到该函数仍然做正确的事情,这种重构的能力真的很好。或者我可以决定,嘿,你知道吗,我要尝试用`findLargest`来实现,但我有所有这些测试其他部分的测试,所以转向基于`findLargest`的解决方案并不难。

你们会发现,在你们到达项目二的时候,它已经在不远处了,你们将重写各种各样的东西,意识到哦,等一下,这个想法在某些基本方面是错误的,或者看起来效率低下,让我们制作一个新的版本。这些测试将使你们的生活更加轻松,拥有所有这些测试小部分将使确保整个程序能够运行变得更加容易。这是我们的一些过程。现在还有一个最后的问题,我想在代码中解决,那就是关于我们测试的问题,所以我将在下一个视频中再详细谈谈这个问题,但基本上不断地注释和取消注释测试是很烦人的。 ​

让我们来谈谈Jo-Ann实际上是什么,以及一些建议,让你们的测试代码更加简洁和整洁。首先,我要提醒你们我们已经使用了Junit的哪些功能,即我们使用了一个叫做assertEquals的方法,它接受两个参数,测试它们是否相等,如果不相等,程序将以某种详细的错误信息终止。如果所有的断言都通过了,你就不会收到任何消息,它是空白的。但如果我修改我的测试,比如说让它失败,那么我们就会收到一条错误消息。好的,所以我们已经看到了这一点,但是Junit不仅仅如此,还有其他的断言方法,如assertEqualsassertFalseassertNotNull等等,这些方法列在这个链接上,你可以去查看它们。Junit还能做其他更复杂的事情,这些都是很棒的,虽然我们在课堂上不会讨论这些,但请参阅实验3,你们不必为了这门课学习它们,但你们可能会觉得它们很有用。

现在,有两件事我想在课堂上介绍,它们非常重要。一是如何让代码表现得更好。这里有一种叫做注解(annotation)的东西,我的目的是解决这些消息有些丑陋的问题,以及手动调用所有测试有点烦人的问题——如果测试通过了你不会收到任何消息,如果你想专注于某个特定的测试,就必须把它注释掉,这很麻烦。所以我的解决方案是在每个测试前面加上一个Junit测试的注解,看起来像这样@org.junit.Test

删除main方法,然后就大叫说不含main error,点击不运行main的运行,(这是这个图标前两个中的一个,我要使用这个左指的红色三角形,抱歉,是左指的红色三角形,右指的绿色三角形)——现在它将运行我所有的测试,它说方法不应该被声明为静态的

将TestSort中第5行public static void testSort() {中的static删除。IntelliJ不是调用主方法,而是调用了一个Junit运行器,突然间出现了很多我们不太理解的东西。这堂课的最后一部分有一些额外的幻灯片记得看,我不打算为此做视频,但它更多地谈到了这背后的原理。但目前你所需要知道的是,你放上了这个神奇的Junit测试注解,并改变了在IntelliJ中的运行方式,也就是这些小三角形,它就会按你想要的方式运行。顺便说一句,你实际上可以通过点击运行测试来单独运行测试,那时它只会运行一个测试,如果你想切换回去,你可以来这里,而不是做测试排序(test sort),而是选择顶级的测试排序(test sort),再次使用这些三角形,你就会得到所有三个测试的结果。

最后一件事,可以使用Java中的导入语句来简化事情。所以第一个导入我将要做的是import org.junit.Test;,这样做的结果是,我不必每次都输入@org.junit.Test,我只需说@Test

ppt剩余部分

最后一点

今天的课程有点马拉松的感觉,对吧,但我们还没有结束呢。
哦,这挺有趣的,我确实很开心,有时候甚至满心欢喜。那么,让我们反思一下关于测试的不同思考方式,我将从一开始就提到的自动评分器开始说起。所以,我认为创建者们认为它们是一种神奇的神谕,告诉你代码是否工作。
实际上,我们在这门课程中使用的自动评分器基于Junit,加上了一些名为JH61B的库里的额外东西,这个库是我自己编写的。自动评分器不一定是个问题,对吧?它们有很多优点,其中一个是你不必花所有的时间去做无聊的事情,比如写那些真正不能教你什么东西的测试,因为你们的时间是非常宝贵的资源,在教学环境中,学生的时间是最珍贵的,所以我们尽量节省你们的时间,只让你们做我们认为对你有实际指导意义的工作。另一个当然是它帮助我们确定你的成绩,因为在现代社会中,我们不知为何要给你字母成绩,这有助于我们完成这项任务。它还增加了正确性的游戏感,因为有这些美味的分数可以尝试获得,而自动评分器分配这些分数基本上把它变成了一种游戏。当然,这不总是好事,我见过学生们整晚都在试图追回最后一个分数,尽管它实际上不会影响他们的生活,点击游戏确实有趣,所以这既好又不好。但是,自动评分器依赖性不是那么好的原因之一当然是它们在现实世界中不存在,所以你可以养成这些坏习惯。另一个原因是自动评分器给你的错误可能难以理解,也许我们预想的你遇到的问题并不是实际发生的问题,因此错误信息毫无用处。第三个原因是它非常慢,如果你必须提交代码并等待五分钟,然后才能做出更改,这可能不太好。当然,如果评分器出了问题或行为异常,你就得等着有人来修复它,这会浪费你的时间。现在,自动评分器可能导致的行为,我谈到过坏习惯,我要说的是最糟糕的情况之一,有时我确实看到发生了这种情况,被称为自动评分器驱动开发。在这种方法中,这是你可以在核心中采用的最糟糕的编程方式。
所以,你可能有这些习惯,我现在提出来,如果你有的话,可以试着改掉它们,没事的。
遵循这种工作流程的学生会阅读并大致了解他们试图做什么,然后编写整个程序,可能是两百行代码,谁知道呢,他们编译代码,当然在编译过程中会出现一堆编译错误。如果你使用的是像IntelliJ这样的开发环境,实际上在编写过程中就会修复编译错误,这很好,但我见过在项目零中,例如,当学生们从命令行工作时,他们会先写大量的代码,然后才修复编译错误。然后,一旦所有的编译错误都被修复,你就会将其发送给自动评分器,然后你会收到大量的错误,因为你甚至还没有在本地运行过代码。所以,你接下来要做的就是重复以下步骤:随机运行自动评分器,添加你认为会有帮助的打印语句,最终你会得到类似这样的输出,自动评分器吐出了一些数组,说在这里得到了这个位置,一些变量,最后在底部是测试失败的消息,你几乎是在从所有这些文本中挖掘线索,试图找出哪里出了问题。我想你会尝试对代码做一些修改来修复bug,一遍又一遍地重复这些步骤,直到最终达到正确的状态。好吧,这种方式可以让你得到一些东西,你可以这样学习,但这只是极其缓慢,我认为这样做会浪费大量时间,因为你试图获得那些额外的分数。所以我要说这种工作流程极其缓慢,是一个坏习惯,从安全角度来看,它对你学习过程的安全性构成了威胁,我是说你某种程度上只是在猜测和检查,也许是明智的猜测和检查,但就是很乱,好吗?

现在,我要指出的是,我对调试时使用打印语句并没有意见,我认为它们可以非常有用,使用起来也很简单,这很好,但我认为它们是一种弱工具,但我经常使用它们,事实上,这里的代码是我试图调试一个我在Python中编写的快速排序实现时实际做的。我认为添加一些打印语句是个好主意,因为我以为它已经很接近正确了,实际上我在试图修复时离正确答案还差得很远,我已经不记得具体的故事了。所以,我有时会在本地这么做,我希望很久没有和自动评分器一起做过这种事情了,希望你也不会这样做。所以,我用这个例子作为极端案例,现在让我告诉你这种新世界为你解锁了什么。
单元测试为你做了什么,所以单元测试的想法是,与其依赖于某种庞大的神奇自动评分器,不如为你的程序的每一个部分编写测试,或者至少是大部分部分,我们将这些部分称为单元,Junit使得这一点变得简单。
我是说你之前见过这段代码,它非常简单,看看这个,我的`testSort`方法现在几乎是空的。
关于这一点有几个很好的方面,一是它让你对自己的基本组件,如`swap`和`findSmallest`充满信心,你对它们的工作感到非常满意,它们就像是你可以随时调用来为你做事的可靠助手。二是它有助于减少调试所需的时间,我可以将注意力限制在一个方法上,避免任何涉及从一个地方跳到另一个地方的打印语句。三是它可以帮助澄清我试图完成的任务,如果我试图排序,我必须知道什么是排序,才能写出`testSort`,如果我正确地编写了`findSmallest`,那么当我为`findSmallest`编写测试时,它有助于澄清我试图做什么。我们现在可能不想这样做,因为每个部分都需要时间,而在一个时间极为宝贵的教学环境中,61B与其他所有生活问题竞争,你可能还有其他课程,我不知道,也许你有孩子,也许你有工作,这占用了你生活中其他部分的时间,比如睡觉,无论是什么。所以我们不必一直这样做。

testSort--->sort---->swap and findSmallest

                    testSwap            testFindSmallest

但我们确实希望你熟悉它。另一个问题是,它可能会建立虚假的信心,比如你编写了一个`testSwap`,但实际上`swap`中有一个bug,而`testSwap`中也有另一个bug,这导致你无法看到那个bug,所以你最终可能会产生虚假的信心,认为你的“小帮手”正在按照你的意愿行事,但实际上并非如此。另外,测试依赖于其他单元的单元也很难,例如,在项目一中,你怎么单独测试`addFirst`方法呢?你需要某种方式来获取返回的数据,或者可能`addFirst`和`removeFirst`之间有一些复杂的交互揭示了一个bug,所以有时候很难实际测试特定的单元。

Steps to developing according to TDD:
Identify a new feature.
Write a unit test for that feature.
Run the test. It should fail
Write code that passes test. (GREEN)
         Implementation is certifiably good!
Optional: Refactor code to make it faster, cleaner, etc.

Not required in 61B. You might hate this!
But testing is a good idea.
Interesting perspective: Red-Shirt, Red, Green, Refactor. 

现在,一旦你脑子里有了单元测试的概念,为了给你一种与自动评分器驱动开发相反的哲学,让我们来谈谈测试驱动开发。这是一个现实世界中非常流行的概念,虽然它现在有点失宠,但仍存在于某些地方,我不想深入探讨,但根据这个大写的T测试驱动开发的过程,首先是确定你想做的事情,比如新增一个特性,“我希望我的类能够排序”,或者“我希望能够处理比特币交易”,不管是什么。然后我要做的是首先为这个特性编写一个测试,这比编写代码更早发生,它是测试驱动的,这是第一步。然后我运行测试,它应该失败,这在我的IDE或其他我用来运行测试代码的工具中应该是红色的,显示代码不起作用,这很好,因为它给了你一个要解决的任务,并告诉你测试至少在不应该通过时没有通过。然后你编写通过该测试的代码,进入绿色模式,在Eclipse中这可能看起来像这样,在IntelliJ中则是那条绿色的条形图,这非常令人兴奋,我们知道代码是有效的,然后我可以说,你知道吗,现在我的代码有效了,我实际上可以让它变得更好,所以现在的代码是经过认证的好代码,任何我现在做的更改,比如我可以让代码更快或更美观,我都可以这样做并看到它仍然有效,如果在加速过程中引入了一个bug,我可以立即知道,而不会破坏我正在构建的整个系统。现在这不是61B的要求,我只是提一下,你可能不喜欢这个想法,也许你根本不喜欢先写测试的想法,但我要说的是测试是一个非常好的主意,关于为什么使用正式的测试驱动开发流程的人后来逐渐远离了它,有一个有趣的视角,我发现这是关于该流程的最佳文章之一,所以测试驱动开发是对那种天真地一遍遍运行自动评分器的工作流的一种极端偏离,在这种情况下,我会在当地运行它,画面稍微不同一点,哎呀,出错了,但我不打算修复它,天哪,我在干什么,好吧,继续浪费你的时间。

所以对你来说最好的可能是介于这两种方法之间,一种是你一遍遍运行自动评分器,希望它能工作,另一种是测试驱动开发,介于这两者之间的东西对你来说可能是好的。

好了,我提到过单元测试并不能解决我们所有的问题,正如我们前面所暗示的那样,我们希望测试在集成测试的概念中覆盖多个单元,所以像RaidDeck这样的例子,我们有所有这些以复杂方式交互的单元,因此即使是名为“单元”的Junit,尽管其重点是单元测试,实际上也可以用于这个过程,即使这不是Junit的主要意图。所以,好的一点是它允许你运行确保所有部件正确交互或整个系统按预期工作的测试,但集成测试的缺点是手动进行可能会非常繁琐,比如我测试的系统是一个完整的游戏,就像你在项目二中可能要构建的那种,手动测试可能会非常痛苦,另一方面,自动化涉及用户复杂交互的东西可能非常具有挑战性。另一个问题是,在这种高度抽象的层次上测试所有部件组合在一起的情况,可能会错过偶尔发生的微妙或罕见错误,而单元测试则可以让我全力以赴确保`swap`正常工作。所以,这三种工具对你和61B都很有用,即使用自动评分器、使用单元测试,然后对我们整个系统进行某种大规模的集成测试,实际上我们的自动评分器测试是单元测试和集成测试的结合体。好的,所以在我们结束今天的课程之前,还有一些最后的想法,Junit让编写测试变得超级简单,我强烈建议你编写测试,但你也不想写太多。我觉得在这次讲座中为`swap`编写测试可能有点傻,因为`swap`是一个非常基础的操作,所以我说只有当它们真的有用时才写。

Parting Thoughts
1.JUnit makes testing easy.

2.You should write tests.

  •  But not too many.
  •  Only when they might be useful!
  •  Write tests first when it feels appropriate [I do this a lot]. 
  •  Lab 3, Project 1B, and Project 2 will give you practice!
  •  Most of the class won't require writing lots of tests (to save you time).

3.Some people really like TDD. Feel free to use it in 61B.

  • See today's optional reading for thoughts from the creator of Ruby on Rails and others.

现在我要说的是,我个人通常喜欢先写测试,这有助于我明确自己想做什么,并给我一个立足之地,所以如果你觉得这样做很好,感觉合适的话,那就太好了,但要知道我非常喜欢这样做,特别是当我为这个课程编写自动评分器时,我会为自动评分器代码编写大量的测试。对于这个课程来说,特别是在实验室3、项目1B和项目2中,测试将会非常重要,之后会有许多作业,但在课程的任何地方,当你觉得测试对你有用时,就去写吧,但在大多数课程中,我们不会要求你像写测试一样,因为我想为你节省一些时间,但我也希望你接触这些概念,因为它们非常重要。我还想提一下,有些人非常喜欢测试驱动开发或这一理念的变体,所以如果你愿意,可以自由使用,如果感兴趣的话,网站上有今天的一些可选阅读材料,你可以从中看到关于测试驱动开发的不同观点。好吧,这就是关于测试的讲座内容了,未来的讲座中我们将更多地讨论一些Java语法,今天就到这里,再见。