2018-2019-2 20175308实验二《面向对象程序设计》实验报告
20175308 实验二《Java面向对象程序设计》实验报告
一、前期准备:
单元测试和TDD:
(一)单元测试
我们首先要会写三种代码:
- 伪代码
- 产品代码
- 测试代码
Java编程中,我们首先写伪代码
,它与具体的编程语言无关,从意图层面来解决问题,是产品代码
最自然的、最好的注释。当我们写好产品代码
后,我们还要写测试代码
。Java编程中程序员对类实现的测试即为单元测试
。在多人合作完成软件时,如何能让自己负责的模块功能定义尽量明确,且模块的质量得到稳定的、量化的保证?单元测试
就是一个很有效的解决方案。
(二)TDD(Test Driven Devlopment, 测试驱动开发)
编程中,我们往往不会选择先写产品代码
再写测试代码
,而是应该先写测试代码
,然后再写产品代码
,从而保证写出来的代码就是正确的。这种开发方法就叫做测试驱动开发(TDD)
,TDD的一般步骤如下:
- 明确当前要完成的功能,记录成一个测试列表
- 快速完成编写针对此功能的测试用例
- 测试代码编译不通过(没产品代码呢)
- 编写产品代码
- 测试通过
- 对代码进行重构,并保证测试通过(重构下次实验练习)
- 循环完成所有功能的开发
Junit的安装与使用:
-
打开idea,Preferences中点击Plugins,在market中搜索junit,如图点选
JUnitGenerator V2.0
进行安装,安装后会显示installed。
-
在安装后,按照老师博客中的操作建立与
src
同等级的test
,用于存放各程序的test文件 -
新建立的文件可以点击小灯泡,建立test文件,或者选中类名,
Go to
>test
,新建test文件并勾选要测试的方法即可
二、实验步骤:
任务一:在一个MyUtil类中解决一个百分制成绩转成“优、良、中、及格、不及格”五级制成绩的功能。
- 伪代码:
百分制转五分制:
如果成绩小于60,转成“不及格”
如果成绩在60与70之间,转成“及格”
如果成绩在70与80之间,转成“中等”
如果成绩在80与90之间,转成“良好”
如果成绩在90与100之间,转成“优秀”
其他,转成“错误”
- 产品代码:
public class MyUtil{
public static String percentage2fivegrade(int grade){
//如果成绩小于60,转成“不及格”
if (grade<60&&grade>=0)
return "不及格";
//如果成绩在60与70之间,转成“及格”
else if (grade < 70&&grade>=60)
return "及格";
//如果成绩在70与80之间,转成“中等”
else if (grade < 80&&grade>=70)
return "中等";
//如果成绩在80与90之间,转成“良好”
else if (grade < 90&&grade>=80)
return "良好";
//如果成绩在90与100之间,转成“优秀”
else if (grade <= 100&&grade>=90)
return "优秀";
//其他,转成“错误”
else
return "错误";
}
}
- 测试代码:
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));
}
@Test
public void testException(){
assertEquals("错误",MyUtil.percentage2fivegrade(-55));
assertEquals("错误",MyUtil.percentage2fivegrade(105));
}
@Test
public void testBoundary(){
assertEquals("不及格", MyUtil.percentage2fivegrade(0));
assertEquals("及格",MyUtil.percentage2fivegrade(60));
assertEquals("中等",MyUtil.percentage2fivegrade(70));
assertEquals("良好",MyUtil.percentage2fivegrade(80));
assertEquals("优秀",MyUtil.percentage2fivegrade(90));
assertEquals("优秀",MyUtil.percentage2fivegrade(100));
}
}
- 系统反馈结果,如果测试失败,IDEA会指出具体哪个测试用例出现错误,修改代码直至所有测试均通过
测试成功截图
任务二:以TDD的方式研究学习StringBuffer
老师给出的StringBufferDemo
代码如下:
public class StringBufferDemo{
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.indexOf("tring"));
System.out.println("buffer = " + buffer.toString());
}
}
对以上代码进行改写,得到每一步调用方法的返回来进行验证,改写代码如下:
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);
}
}
通过查阅相关资料和推测,各方法的作用如下:
charAt(int i)
:得到字符串中第i个位置的字符,考虑到数组下标从0开始,字符串的位置也从0开始记。capacity()
:返回当前容量。容量指可用于最新插入的字符的存储量,超过这一容量就需要再次进行分配。length()
:得到字符串长度indexOf(String buf)
:得到buf字符串第一次出现的位置,该位置为buf中第一个字符的位置
进行测试,测试代码如下:
import junit.framework.TestCase;
import org.junit.Test;
import static org.junit.Assert.*;
// 20175308
public class StringBufferDemoTest extends TestCase {
StringBuffer a = new StringBuffer("StringBuffer");
StringBuffer b = new StringBuffer("StringBufferStringBuffer");
StringBuffer c = new StringBuffer("StringBuffer tested by 175308");
@Test
public void testCharAt() throws Exception{
assertEquals('S',a.charAt(0));//验证返回是否是整个字符串中的第一个字符
assertEquals('g',b.charAt(5));//验证返回是否是整个字符串的第六个字符
assertEquals('t',c.charAt(16));
}
@Test
public void testcapacity() throws Exception{//
assertEquals(28,a.capacity());
assertEquals(40,b.capacity());
assertEquals(45,c.capacity());
}
@Test
public void testlength() throws Exception{
assertEquals(12,a.length());//验证字符串a的长度
assertEquals(24,b.length());
assertEquals(29,c.length());
}
@Test
public void testindexOf(){
assertEquals(6,a.indexOf("Buff"));
assertEquals(3,b.indexOf("ing"));
assertEquals(23,c.indexOf("1753"));
}
}
测试成功截图:
任务三:对设计模式示例进行扩充,体会OCP原则和DIP原则的应用,初步理解设计模式
我的学号后两位对6取余为2,应该使系统支持boolean类。在实验开始之前,首先要熟悉SOLID
原则:
依据OCP原则和DIP原则,我们在扩充示例使其支持Boolean类时不应该直接添加或修改已有类中的方法(OCP原则要求软件实体对修改封闭),而是采用一种抽象工厂
的方法来进行对于程序的扩充。具体代码如下:
abstract class Data {
abstract public void DisplayValue();
}
class Integer extends Data {
int value;
Integer() {
value=100;
}
public void DisplayValue(){
System.out.println (value);
}
}
class Boolean extends Data{
boolean value;
Boolean(){
value=true;
}
public void DisplayValue(){
System.out.println(value);
}
}
abstract class Factory {
abstract public Data CreateDataObject();
}
class IntFactory extends Factory {
public Data CreateDataObject(){
return new Integer();
}
}
class BooleanFactory extends Factory{
public Data CreateDataObject(){
return new Boolean();
}
}
class Document {
Data pd;
Document(Factory pf){
pd = pf.CreateDataObject();
}
public void DisplayData(){
pd.DisplayValue();
}
}
//Test class
public class MyDoc {
static Document d;
static Document e;
public static void main(String[] args) {
d = new Document(new IntFactory());
d.DisplayData();
e=new Document(new BooleanFactory()); //20175308
e.DisplayData();
}
}
运行成功截图:
任务四:以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)
- 测试代码:
import org.junit.Test;
import junit.framework.TestCase;
public class ComplexTest extends TestCase {
Complex a =new Complex(3.0,4.0);
Complex b =new Complex( 2.0,-4.0);
Complex c =new Complex(0.0,0.0);
Complex d =new Complex(-3.0,0.0);
Complex e =new Complex(-6.0,-0.8);
@Test
public void testgetRealPart()throws Exception{
assertEquals(3.0,a.getRealPart());
assertEquals(2.0,b.getRealPart());
assertEquals(0.0,c.getRealPart());
assertEquals(-3.0,d.getRealPart());
assertEquals(-6.0,e.getRealPart());
}
@Test
public void testgetImagePart()throws Exception{
assertEquals(4.0,a.getImagePart());
assertEquals(-4.0,b.getImagePart());
assertEquals(0.0,c.getImagePart());
assertEquals(0.0,d.getImagePart());
assertEquals(0.8,e.getImagePart());
}
@Test
public void testtoString()throws Exception{
assertEquals("3.0+4.0i",a.toString());
assertEquals("2.0-4.0i",b.toString());
assertEquals("0",c.toString());
assertEquals("-3.0",d.toString());
assertEquals("-6.0-8.0i",e.toString());
}
@Test
public void testComplexAdd()throws Exception{
assertEquals("5.0",a.ComplexAdd(b).toString());
assertEquals("2.0-4.0i",b.ComplexAdd(c).toString());
assertEquals("-1.0-4.0i",b.ComplexAdd(d).toString());
}
@Test
public void testComplexSub()throws Exception{
assertEquals("1.0+8.0i",a.ComplexSub(b).toString());
assertEquals("-2.0+4.0i",c.ComplexSub(b).toString());
assertEquals("3.0",c.ComplexSub(d).toString());
}
@Test
public void testComplexMulti()throws Exception{
assertEquals("22.0-4.0i",a.ComplexMulti(b).toString());
assertEquals("0",b.ComplexMulti(c).toString());
assertEquals("18.0+2.4i",d.ComplexMulti(e).toString());
}
@Test
public void testComplexDiv()throws Exception{
assertEquals("-0.2-1.25i",a.ComplexDiv(b).toString());
assertEquals("0",c.ComplexDiv(b).toString());
}
@Test
public void testequals()throws Exception{
assertEquals(true,a.equals(a));
assertEquals(false,a.equals(b));
}
}
- 产品代码:
import com.sun.jdi.DoubleValue;
import java.util.zip.CheckedOutputStream;
import java.text.DecimalFormat;
public class Complex {
double RealPart=0;
double ImagePart=0;
public Complex(){}
public Complex(double RealPart,double ImagePart){
this.RealPart=RealPart;
this.ImagePart=ImagePart;
}
public double getRealPart(){
return RealPart;
}
public double getImagePart(){
return ImagePart;
}
public String toString(){
String s = "";
double r=RealPart;
double i=ImagePart;
if(r==0&&i==0){
s="0";
}
else if(r==0&&i!=0){
s=i+"i";
}
else if(r!=0&&i<0){
s=r+""+i+"i";
}
else if(r!=0&&i==0){
s=r+"";
}
else
{
s=r+"+"+i+"i";
}
return s;
}
public boolean equals(Object obj){
if(this==obj){
return true;
}
else return false;
}
DecimalFormat df = new DecimalFormat( "0.0");
public Complex ComplexAdd(Complex a){
return new Complex(RealPart+a.getRealPart(),ImagePart+a.getImagePart());
}
public Complex ComplexSub(Complex a){
return new Complex(RealPart-a.getRealPart(),ImagePart-a.getImagePart());
}
public Complex ComplexMulti(Complex a){
double r=RealPart*a.getRealPart()-ImagePart*a.getImagePart();
double i =ImagePart*a.getRealPart()+RealPart*a.getImagePart();
return new Complex(Double.valueOf(df.format(r)),Double.valueOf(df.format(i)));
}
public Complex ComplexDiv(Complex a){
double r=(RealPart * a.ImagePart + ImagePart * a.RealPart) / (a.ImagePart * a.ImagePart + a.RealPart * a.RealPart);
double i=(ImagePart * a.ImagePart + RealPart * a.RealPart) / (a.RealPart * a.RealPart + a.RealPart * a.RealPart);
return new Complex(Double.valueOf(df.format(r)),Double.valueOf(df.format(i)));
}
}
测试成功截图:
PSP图:
步骤 | 耗时 | 百分比 |
---|---|---|
需求分析 | 2min | 4% |
设计 | 5min | 10% |
代码实现 | 12min | 24% |
测试 | 25min | 50% |
分析总结 | 6min | 12% |
任务五:对实验二中的代码进行建模
对于UML图的使用一直缺少锻炼,直到进行结对项目对项目进行建模才开始学着使用。有关UML图的使用可以在网络上找到很详尽的讲解:参考链接
任务要求类图中至少要有两个类,最为符合要求的便是MyDoc.java的代码了,UML图如下:
实验中遇到的问题及解决:
1.下载Junit后仍然出现标红无法使用。
有关这个问题的解决网络上给出了五花八门许许多多的答案,但我尝试过来一直都没有找到正确的解决方法,直到我又仔细的阅览了一下老师的博客,其实已经给了详尽的操作,里面提醒了一步倒包的操作,由于系统差别一时没有对应上。mac版IDEA解决方法如下:
- 在
File
菜单栏找到Project Structure
- 点击
Dependencies
,直接勾选即可
2.任务一中传入-55系统输出不及格而非错误。
此时是未经修改过的代码,此时代码如下:
public class MyUtil{
public static String percentage2fivegrade(int grade){
//如果成绩小于60,转成“不及格”
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 "优秀";
//其他,转成“错误”
else
return "错误";
}
}
当传入-55时,虽然-55的分数理论上一定是错误的,但是在代码中只会检测其是否小于60,满足条件后直接得出结果。该问题的解决其实很简单,只需在判断是否小于60之前先加一步判断正负的步骤即可(超出100的数字判断由最后的else负责)。出于C语言学习时的习惯,我用&&
对每一步的判断条件进行连接,形成我最后的代码。
3.复数类中涉及double运算可能会给出一个极长位数的答案
在double类型的运算中,经常由于一些不可描述的原因产生如下的结果:
理论上心里知道咋回事就可以了,但为了看到百分百的Test passed
,还是要含泪修改。
原有的代码(并非上文展示的最终版本)为(主要出错在乘除法):
ComplexMulti(Complex a){
return new Complex(RealPart*a.getRealPart()-ImagePart*a.getImagePart(),ImagePart*a.getRealPart()+RealPart*a.getImagePart());
}
public Complex ComplexDiv(Complex a){
return new Complex((RealPart * a.ImagePart + ImagePart * a.RealPart) / (a.ImagePart * a.ImagePart + a.RealPart * a.RealPart), (ImagePart * a.ImagePart + RealPart * a.RealPart) / (a.RealPart * a.RealPart + a.RealPart * a.RealPart));
}
经过查阅相关资料,java指定小数点后的位数可以通过导入import java.text.DecimalFormat;
来实现, DecimalFormat df = new DecimalFormat( "0.0");
就将小数点后的位数控制在了一位。
注意⚠️:此时的df.format(r)为string类型,还要再进行一次类型转换
将产品代码修改为任务二中的版本后,再运行一次刚才出错的test,测试通过!