2017-2018-2 20165315 实验二《Java面向对象程序设计》实验报告

一、实验内容及步骤

1.初步掌握单元测试和TDD

  • 单元测试

任务一:三种代码

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

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

TDD(测试驱动开发):

  • 伪代码(思路)
  • 测试代码(产品预期功能)
  • 产品代码(实现预期功能)

TDD的一般步骤如下:

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

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

测试类的创建:

  • 将鼠标放在主类类名上,点击出现的黄色小灯泡,在下拉选项中选择Create Test

  • 随后在Testing Library中的下拉选项中选择Junit 3,点击OK便可创建Test
  • 注意:不要忘记@Test的书写

举例:我们要在一个MyUtil类中解决一个百分制成绩转成“优、良、中、及格、不及格”五级制成绩的功能。

测试结果:

任务二:TDD(Test Driven Devlopment, 测试驱动开发)

老师在教程里给出的程序如下:

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(int i):返回此序列中指定索引处的 char 值。第一个 char 值在索引 0 处,第二个在索引 1 处,依此类推,这类似于数组索引
  • capacity():返回当前容量。容量指可用于最新插入的字符的存储量,超过这一容量就需要再次进行分配
  • length():返回子浮窗的长度
  • indexOf(String s):返回输入的子字符串的第一个字母在母字符串的位置
  • toString(String s):返回此对象本身(它已经是一个字符串)

在产品代码里,我们需要为这五个方法加上返回值,并与我们的断言进行比较。产品代码如下:

public class StringBufferDemo {
    StringBuffer buffer;

    public static char CharAt(StringBuffer buffer, int index) {
        return buffer.charAt(index);
    }

    public static int Capacity(StringBuffer buffer) {
        return buffer.capacity();
    }

    public static int IndexOf(StringBuffer buffer, String str) {
        return buffer.indexOf(str);
    }

    public static String ToString(StringBuffer buffer) {
        return "buffer = " + buffer.toString();
    }

    public static int Length(StringBuffer buffer) {
        return buffer.length();
    }
}

根据上述该产品代码,写出对应的测试类,在测试类中我分别都使使用了3个例子来进行测试,如果出现问题,JUnit会出现红条,IDEA会提示哪一个测试用例出现问题,由此可以对应改正产品代码中的问题,直到JUnit出现绿条,任务完成。

测试代码如下:

import junit.framework.TestCase;
import org.junit.*;

public class StringBufferDemoTest extends TestCase {
    StringBuffer buffer1 = new StringBuffer("iamastudent");
    StringBuffer buffer2 = new StringBuffer("youareastudent");
    StringBuffer buffer3 = new StringBuffer("heisateacher");

    @Test
    public void testCharAt() {
        assertEquals('i', StringBufferDemo.CharAt(buffer1, 0));
        assertEquals('o', StringBufferDemo.CharAt(buffer2, 1));
        assertEquals('r', StringBufferDemo.CharAt(buffer3, 11));
    }

    @Test
    public void testCapital() {
        assertEquals(27, StringBufferDemo.Capacity(buffer1));
        assertEquals(30, StringBufferDemo.Capacity(buffer2));
        assertEquals(28, StringBufferDemo.Capacity(buffer3));
    }

    @Test
    public void testLenght() throws Exception {
        assertEquals(11, StringBufferDemo.Length(buffer1));
        assertEquals(14, StringBufferDemo.Length(buffer2));
        assertEquals(12, StringBufferDemo.Length(buffer3));
    }

    @Test
    public void testIndexOf() {
        assertEquals(0, StringBufferDemo.IndexOf(buffer1, "iam"));
        assertEquals(-1, StringBufferDemo.IndexOf(buffer2, "You"));
        assertEquals(11, StringBufferDemo.IndexOf(buffer3, "r"));
    }

    @Test
    public void testToString() {
        assertEquals("buffer = iamastudent", StringBufferDemo.ToString(buffer1));
        assertEquals("buffer = youareastudent", StringBufferDemo.ToString(buffer2));
        assertEquals("buffer = heisateacher", StringBufferDemo.ToString(buffer3));
    }
}

截图如下:

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

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

OOD中建模会用图形化的建模语言UML(Unified Modeling Language),UML是一种通用的建模语言。

过程抽象的结果是函数,数据抽象的结果是抽象数据类型(Abstract Data Type,ADT),类可以作具有继承和多态机制的ADT。数据抽象才是OOP的核心和起源。

OO三要素的第一个要素是封装,封装就是将数据与相关行为包装在一起以实现信息就隐藏,Java中用类进行封装。

封装实际上使用方法(method)将类的数据隐藏起来,控制用户对类的修改和访问数据的程度,从而带来模块化(Modularity)和信息隐藏(Information hiding)的好处;接口(interface)是封装的准确描述手段。

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

  • 点开StarUML程序后,在右下角的选项中选择Class选项建立一个类的UML图

  • 按照下图中提示分别创建对应的变量层和方法层

  • 在右下角选项中选择Generalization选项,创建类与类之间的调用关系

最终结果截图:

  • 较为简单:

  • 较为复杂:

3.设计模式

面向对象三要素是“封装、继承、多态”,任何面向对象编程语言都会在语法上支持这三要素。如何借助抽象思维用好三要素特别是多态还是非常困难的,S.O.L.I.D类设计原则是一个很好的指导:

  • SRP(Single Responsibility Principle,单一职责原则)
  • OCP(Open-Closed Principle,开放-封闭原则)
  • LSP(Liskov Substitusion Principle,Liskov替换原则)
  • ISP(Interface Segregation - Principle,接口分离原则)
  • DIP(Dependency Inversion Principle,依赖倒置原则)

任务四:对MyDoc类进行扩充,让其支持Long类,初步理解设计模式

OCP是OOD中最重要的一个原则,OCP的内容是:
软件实体(类,模块,函数等)应该对扩充开放,对修改封闭。

OCP可以用以下手段实现:

  • 抽象和继承
  • 面向接口编程

老师给出的以Int型为例的代码如下:

abstract class Data{
    public abstract void DisplayValue(); 
} 
class Integer extends Data { 
  int value; 
   Integer(){
      value=100;  
   }  
   public void DisplayValue(){
       System.out.println(value); 
   }  
}
class Document { 
     Data pd; 
     Document() { 
        pd=new Integer(); 
     } 
     public void DisplayData(){
        pd.DisplayValue(); 
     }     
} 
public class MyDoc {
    static Document d;    
    public static void main(String[] args) {
        d = new Document(); 
        d.DisplayData();      
    }   
}

在上述代码的基础上,要求系统支持Long类,这是一个合理的要求,要支持Long类,Document类要修改两个地方,这违反了OCP原则,使用多态可以解决部分问题:

产品代码:

// Server Classes 
abstract class Data {
    abstract public void DisplayValue();
}

class Integer extends Data {
    int value;

    Integer() {
        value = 100;
    }

    public void DisplayValue() {
        System.out.println(value);
    }
}

class Long extends Data {
    long value;

    Long() {
        value = 111111111;
    }

    public void DisplayValue() {
        System.out.println(value);
    }
}

// Pattern Classes
abstract class Factory {
    abstract public Data CreateDataObject();
}

class IntFactory extends Factory {
    public Data CreateDataObject() {
        return new Integer();
    }
}

class LongFactory extends Factory {
    public Data CreateDataObject() {
        return new Long();
    }
}

//Client classes
class Document {
    Data pd;

    Document(Factory pf) {
        pd = pf.CreateDataObject();
    }

    public void DisplayData() {
        pd.DisplayValue();
    }
}

//Test class
public class MyDoc {
    static Document d1, d2;

    public static void main(String[] args) {
        d1 = new Document(new IntFactory());
        d2 = new Document(new LongFactory());
        d1.DisplayData();
        d2.DisplayData();
    }
}

运行结果截图:

4.练习

任务五:以TDD的方式开发一个复数类Complex

  • 伪代码
// 定义属性并生成getter,setter
double RealPart;
double ImagePart;
// 定义构造函数
public Complex()
public Complex(double R,double I)

//Override Object
public boolean equals(Object obj)
public String toString()

// 定义公有方法:加减乘除
Complex ComplexAdd(Complex a)
Complex ComplexSub(Complex a)
Complex ComplexMulti(Complex a)
Complex ComplexDiv(Complex a)
  • 产品代码
public class Complex {
    // 定义属性并生成getter,setter
    private double RealPart;
    private double ImagePart;

    public double getterRealPart() {
        return this.RealPart;
    }

    public double getterImagePart() {
        return this.ImagePart;
    }

    public static double getterRealPart(double RealPart) {
        return RealPart;
    }

    public static double getterImagePart(double ImagePart) {
        return ImagePart;
    }

    public void setterRealPart(double RealPart) {
        this.RealPart = RealPart;
    }

    public void setterImagePart(double ImagePart) {
        this.ImagePart = ImagePart;
    }

    // 定义构造函数
    public Complex() {
        this.RealPart = 0;
        this.ImagePart = 0;
    }

    public Complex(double R, double I) {
        this.RealPart = R;
        this.ImagePart = I;
    }

    //Override Object
    public boolean equals(Object obj) {
        Complex complex = (Complex) obj;
        if (this == obj) {
            return true;
        } else if (!(obj instanceof Complex)) {
            return false;
        } else if (getterRealPart() != complex.getterRealPart()) {
            return false;
        } else if (getterImagePart() != complex.getterImagePart()) {
            return false;
        } else
            return true;
    }

    public String toString() {
        if (getterRealPart() == 0)
            return getterImagePart() + "i";
        else if (getterImagePart() == 0)
            return getterRealPart() + "";
        else if (getterImagePart() < 0)
            return getterRealPart() + "" + getterImagePart() + "i";
        else
            return getterRealPart() + "+" + getterImagePart() + "i";
    }

    // 定义公有方法:加减乘除
    Complex ComplexAdd(Complex a) {
        return new Complex(this.getterRealPart() + a.getterRealPart(), getterImagePart() + a.getterImagePart());
    }

    Complex ComplexSub(Complex a) {
        return new Complex(this.getterRealPart() - a.getterRealPart(), getterImagePart() - a.getterImagePart());
    }

    Complex ComplexMulti(Complex a) {
        return new Complex(this.getterRealPart() * a.getterRealPart() - a.getterImagePart() * this.getterImagePart(), a.getterImagePart() * this.getterRealPart() + a.getterRealPart() * this.getterImagePart());
    }

    Complex ComplexDiv(Complex a) {
        Complex c = new Complex();
        if (a.equals(c)) {
            System.out.println("错误,分母不能为零!");
        }
        return new Complex(this.getterRealPart() / a.getterRealPart(), this.getterImagePart() / a.getterImagePart());
    }
}
  • 测试代码
import junit.framework.TestCase;
import org.junit.Test;

public class ComplexTest extends TestCase {
    Complex complex1 = new Complex(3, 4);
    Complex complex2 = new Complex(1, -2);
    Complex complex3 = new Complex(1, 1);

    @Test
    public void testgetterRealPart() throws Exception {
        assertEquals(3.0, Complex.getterRealPart(3.0));
        assertEquals(1.0, Complex.getterRealPart(1.0));
        assertEquals(-2.0, Complex.getterRealPart(-2.0));
    }

    @Test
    public void testgetterImagePart() throws Exception {
        assertEquals(4.0, Complex.getterImagePart(4.0));
        assertEquals(-2.0, Complex.getterImagePart(-2.0));
        assertEquals(0.0, Complex.getterImagePart(0.0));
    }

    @Test
    public void testAdd() throws Exception {
        assertEquals("4.0+2.0i", complex1.ComplexAdd(complex2).toString());
        assertEquals("4.0+5.0i", complex1.ComplexAdd(complex3).toString());
        assertEquals("2.0-1.0i", complex2.ComplexAdd(complex3).toString());
    }

    @Test
    public void testSub() throws Exception {
        assertEquals("2.0+6.0i", complex1.ComplexSub(complex2).toString());
        assertEquals("2.0+3.0i", complex1.ComplexSub(complex3).toString());
        assertEquals("-3.0i", complex2.ComplexSub(complex3).toString());
    }

    @Test
    public void testMulti() throws Exception {
        assertEquals("11.0-2.0i", complex1.ComplexMulti(complex2).toString());
        assertEquals("-1.0+7.0i", complex1.ComplexMulti(complex3).toString());
        assertEquals("3.0-1.0i", complex2.ComplexMulti(complex3).toString());
    }

    @Test
    public void testDiv() throws Exception {
        assertEquals("3.0-2.0i", complex1.ComplexDiv(complex2).toString());
        assertEquals("3.0+4.0i", complex1.ComplexDiv(complex3).toString());
        assertEquals("1.0-2.0i", complex2.ComplexDiv(complex3).toString());
    }
}

二、实验过程中遇到的问题及解决

  • 一开始以为Test类是自己手动在Test目录下创建,导致结果不会出现测试条

解决过程:

1.将鼠标放在主类类名上,点击出现的黄色小灯泡,在下拉选项中选择Create Test

2.随后在Testing Library中的下拉选项中选择Junit 3,点击OK便可创建Test

3.注意:不要忘记@Test的书写

  • 在书写测试类时,StringBuffer()中的capacity()方法总是出错

解决方法:
参考capacity()的用法可知,StringBuffer在内部维护一个字符数组,当你使用缺省的构造函数来创建StringBuffer对象的时候, StringBuffer的容量被初始化为16个字符,也就是说缺省容量就是16个字符。当StringBuffer达到最大容 量的时候,它会将自身容量增加到当前的2倍再加2,也就是(2*旧值+2)。

也就是说,对于空字符串,调用capacity()方法初始分配值为16;大小超过16时则扩充容量为34,再次扩充得到70。

三、实验体会与总结

我通过本次实验学会了如何编写测试代码、如何绘画UML图以及在TDD模式下编写代码。

在自己上手实践操作过程中,加深了对平时不清楚的知识点的理解,也掌握了的Junit的用法。在使用测试代码的时候,既可以测试到代码是否正确,又规范了编程习惯,单元测试提供了一种高效快速的测试代码正确性的方法。

四、PSP(Personal Software Process)时间

步骤 耗时 百分比
步骤 耗时 百分比
需求分析 20min 12%
设计 30min 18%
代码实现 50min 30%
测试 30min 18%
分析总结 40min 22%

五、代码链接

https://gitee.com/BESTI-IS-JAVA-2018/ch1/tree/master/20165315exp2

posted on 2018-04-20 17:42  yh666  阅读(246)  评论(0编辑  收藏  举报