实验二

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实验二相关的知识,希望对你有一定的参考价值。

一、单元测试和TDD

用程序解决问题时,要学会写以下三种代码:

  • 伪代码
  • 产品代码
  • 测试代码

正确的顺序应为:伪代码(思路)→ 测试代码(产品预期功能)→ 产品代码(实现预期功能),这种开发方法叫“测试驱动开发”(TDD)。TDD的一般步骤如下:

  • 明确当前要完成的功能,记录成一个测试列表
  • 快速完成编写针对此功能的测试用例
  • 测试代码编译不通过(没产品代码呢)
  • 编写产品代码
  • 测试通过
  • 对代码进行重构,并保证测试通过(重构下次实验练习)
  • 循环完成所有功能的开发

基于TDD,可以有效避免过度开发的现象,因为我们只需要让测试通过即可。

任务一:实现百分制成绩转成优、良、中、及格、不及格五级制成绩的功能

以这个任务为例,我们来对TDD方法进行一次小小的实践。

首先要明白自己的程序需要进行哪些操作?要实现什么目标?伪代码可以帮我们理清思路。

百分制转五分制:

如果成绩小于60,转成“不及格”

如果成绩在60与70之间,转成“及格”

如果成绩在70与80之间,转成“中等”

如果成绩在80与90之间,转成“良好”

如果成绩在90与100之间,转成“优秀”

其他,转成“错误”

伪代码不需要说明具体调用的方法名,甚至不需要强调你打算使用的语言,理清思路即可。

其次,选择一种语言把伪代码实现,也就成了产品代码

public class MyUtil{

    public static String percentage2fivegrade(int grade){

        //如果成绩小于0,转成“错误”

        if ((grade < 0))

            return "错误";

            //如果成绩小于60,转成“不及格”

        else if (grade < 60)

            return "不及格";

            //如果成绩在60与70之间,转成“及格”

        else if (grade < 70)

            return "及格";

            //如果成绩在70与80之间,转成“中等”

        else if (grade < 80)

            return "中等";

            //如果成绩在80与90之间,转成“良好”

        else if (grade < 90)

            return "良好";

            //如果成绩在90与100之间,转成“优秀”

        else if (grade <= 100)

            return "优秀";

            //如果成绩大于100,转成“错误”

        else

            return "错误";

    }

}

产品代码是为用户提供的,为了保证正确性,我们需要对自己的程序进行测试,考虑所有可能的情况,来判断结果是否合乎要求。这是我们就需要写测试代码

根据我们现在的理解,测试代码不就是不断调用System.out.println(),来判断输出是否合乎预期嘛?所以可能会写成下面这种代码:

public class MyUtilTest {

    public static void main(String[] args) {

        if(MyUtil.percentage2fivegrade(55) != "不及格")

            System.out.println("test failed!");

        else if(MyUtil.percentage2fivegrade(65) != "及格")

            System.out.println("test failed!");

        else if(MyUtil.percentage2fivegrade(75) != "中等")

            System.out.println("test failed!");

        else if(MyUtil.percentage2fivegrade(85) != "良好")

            System.out.println("test failed!");

        else if(MyUtil.percentage2fivegrade(95) != "优秀")

            System.out.println("test failed!");

        else

            System.out.println("test passed!");

    }

}

如果输出test passed!,那就代表通过测试咯~

可是...如果输出test failed!呢?那事情就很糟糕了。我们需要把每一个测试用例的结果都打印出来,看看到底是哪里出现了该死的“test failed”。本题中的测试用例很少,那如果做大的项目时有成千上万个用例呢?

不用担心!Java中有单元测试工具JUnit来辅助进行TDD,我们用TDD的方式把前面百分制转五分制的例子重写一次,体会一下有测试工具支持的开发的好处。

鼠标放在需要测试的类上单击,出现一个黄色灯泡,点击选择“Create Test”,就可以新建一个测试类啦~

 

那么,我们可以这么写测试代码:

import org.junit.Test;

import junit.framework.TestCase; //注意导包!!!

public class MyUtilTest extends TestCase {

    @Test

    public void testNormal() {

        assertEquals("不及格", MyUtil.percentage2fivegrade(55));

        assertEquals("及格", MyUtil.percentage2fivegrade(65));

        assertEquals("中等", MyUtil.percentage2fivegrade(75));

        assertEquals("良好", MyUtil.percentage2fivegrade(85));

        assertEquals("优秀", MyUtil.percentage2fivegrade(95));

    }

}

如果出现问题,IDEA就会提示我们具体是哪一步出错了。比如下面这个实例(我把95分的断言值改成了“良好”,我们知道测试肯定不会通过):

 

可以看出,IDEA提示我们Expected:良好; Actual:优秀,最后一行还有at test2.MyUtilTest.testNormal(MyUtilTest.java:11),这下我们明白了,是testNormal()方法中一个为“良好”的断言错了。这样一来,是不是比上面那种方法方便很多了呢?

当然了,测试代码不能随便一写草草了事,作为开发者的我们必须要考虑得足够周全,尤其是各种边界情况以及非法输入等等。比如本题我们需要添加testExceptions()、testBoundary()等等各种方法进行测试。这些老师的教程里写得很详细,我在此就不赘述了。

任务二:以TDD的方式研究学习StringBuffer

这个任务主要锻炼我们自己写JUnit测试用例的能力。老师在教程里给出的程序如下:

public static void main(String [] args){

       StringBuffer buffer = new StringBuffer();

       buffer.append(‘S‘);

       buffer.append("tringBuffer");

       System.out.println(buffer.charAt(1));

       System.out.println(buffer.capacity());

       System.out.println(buffer.length());

       System.out.println(buffer.indexOf("tring"));

       System.out.println("buffer = " + buffer.toString());

首先我们需要对这个程序进行改写,写成上面的产品代码那种类型的,以便于我们进行测试。

对于这个程序,我们先来看一看哪些方法需要测试?有四个,charAt()、capacity()、length()、indexOf。在产品代码里,我们需要为这四个方法加上返回值,并与我们的断言进行比较。产品代码如下:

public class StringBufferDemo{

   StringBuffer buffer = new StringBuffer();

   public StringBufferDemo(StringBuffer buffer){

       this.buffer = buffer;

   }

   public Character charAt(int i){

       return buffer.charAt(i);

   }

   public int capacity(){

       return buffer.capacity();

   }

   public int length(){

       return buffer.length();

   }

   public int indexOf(String buf) {

       return buffer.indexOf(buf);

   }

}

接下来我们需要对调用各种方法的返回值进行猜测。查询API文档可知,对charAt(int i)的解释为:“返回此序列中指定索引处的 char 值。第一个 char 值在索引 0 处,第二个在索引 1 处,依此类推,这类似于数组索引。”indexOf(String s)则返回输入的子字符串的第一个字母在母字符串的位置。这两个看起来都比较好理解,那capacity()和length()呢?我在学习StringBuffer的时候也遇到了这样的困惑,对于这两者之间的区别问题,可以参考我博客的“问题解决”模块。

基于以上的思考和学习,我们可以对各个方法的返回值有一个精确地分析,接下来只需要比较产品代码中的方法与我们的断言值是否相等即可。

出于对老师这篇博客中问题的思考,我设置了长度不同的三个字符串进行测试,代码如下:

public class StringBufferDemoTest extends TestCase {

    StringBuffer a = new StringBuffer("StringBuffer");//测试12个字符(<=16)

    StringBuffer b = new StringBuffer("StringBufferStringBuffer");//测试24个字符(>16&&<=34)

    StringBuffer c = new StringBuffer("StringBufferStringBufferStringBuffer");//测试36个字符(>=34)

    @Test

    public void testcharAt() throws Exception{

        assertEquals(‘S‘,a.charAt(0));

        assertEquals(‘g‘,a.charAt(5));

        assertEquals(‘r‘,a.charAt(11));

    }

    @Test

    public void testcapacity() throws Exception{

        assertEquals(28,a.capacity());

        assertEquals(40,b.capacity());

        assertEquals(52,c.capacity());

    }

    @Test

    public void testlength() throws Exception{

        assertEquals(12,a.length());

        assertEquals(24,b.length());

        assertEquals(36,c.length());

    }

    @Test

    public void testindexOf() throws Exception{

        assertEquals(0,a.indexOf("Str"));

        assertEquals(5,a.indexOf("gBu"));

        assertEquals(10,a.indexOf("er"));

    }

}

可以看到,出现了“green bar”,说明测试通过了,我们对StringBuffer也有了更加深刻的认识。

返回目录

二、面向对象三要素:封装、继承、多态

面向对象(Object-Oriented)的三要素包括:封装、继承、多态。面向对象的思想涉及到软件开发的各个方面,如面向对象分析(OOA)、面向对象设计(OOD)、面向对象编程实现(OOP)。OOA根据抽象关键的问题域来分解系统,关注是什么(what)。OOD是一种提供符号设计系统的面向对象的实现过程,用非常接近问题域术语的方法把系统构造成“现实世界”的对象,关注怎么做(how),通过模型来实现功能规范。OOP则在设计的基础上用编程语言(如Java)编码。贯穿OOA、OOD和OOP的主线正是抽象。

任务三:使用StarUML对实验二中的代码进行建模

UML是一种通用的建模语言,可以非常直观地表现出各个结构之间的关系。

以上是关于实验二的主要内容,如果未能解决你的问题,请参考以下文章

通信原理实验二 角度调制实验

20165223 实验二 面向对象程序设计

计算机系统 实验二 数据表示实验

实验二 组合逻辑电路设计;实验三 时序逻辑电路设计

区块链技术与应用实验报告(实验二)

实验二 20155335 实验报告 固件程序设计