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

时间:2021-08-17 16:44:24

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

一、实验内容及步骤

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

  • 单元测试

任务一:三种代码

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

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

TDD(测试驱动开发):

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

TDD的一般步骤如下:

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

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

测试类的创建:

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

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

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

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

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

测试结果:

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

任务二: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));
}
}

截图如下:

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

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图

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

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

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

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

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

最终结果截图:

  • 较为简单:

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

  • 较为复杂:

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

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();
}
}

运行结果截图:

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

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

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

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

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

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

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

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

解决方法:

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

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

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

三、实验体会与总结

我通过本次实验学会了如何编写测试代码、如何绘画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