实验二《Java面向对象程序设计》
TDD与单元测试
前期准备:
什么是单元测试?
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如Java里单元指一个类。
正如想用程序解决问题时,如博客Intellj IDEA 简易教程所说,要会写三种码:
- 伪代码
- 产品代码
- 测试代码
什么是TDD?
TDD(Test Driven Devlopment, 测试驱动开发)先写测试代码,然后再写产品代码的开发方法叫“测试驱动开发”(TDD)。
TDD的一般步骤如下:
- 明确当前要完成的功能,记录成一个测试列表
- 快速完成编写针对此功能的测试用例
- 测试代码编译不通过(没产品代码呢)
- 编写产品代码
- 测试通过
- 对代码进行重构,并保证测试通过(重构下次实验练习)
- 循环完成所有功能的开发
Junit配置
步骤十分简单:下载junit.jar
包并引入即可,但是由于不熟练还是遇到了很多的问题:
-
第一次引入
jar
包之后误以为不需要再度引入:实际上每次需要运用TDD
或者写测试类时都需重新引入。(数据库jar
包同理) -
测试类名一定要以
Test
开头,否则会报错:在修改过后:
任务一:实现百分制成绩转成“优、良、中、及格、不及格”五级制成绩的功能
-
伪代码:
百分制转五分制: 如果成绩小于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 "错误"; } }
-
测试代码:需要兼顾正常情况、异常情况、边界情况
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)); } }
测试代码运行截图:
任务二:以TDD的方式研究学习StringBuffer
-
产品代码
public class StringBufferDemo{ StringBuffer str=new StringBuffer(); StringBufferDemo(StringBuffer str){ this.str=str; } public char charAt(int i){ return str.charAt(i); } public int capacity(){ return str.capacity(); } public int length(){ return str.length(); } public int indexOf(String buf) { return str.indexOf(buf); } }
-
测试代码
import junit.framework.TestCase; import org.junit.Test; 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(){ assertEquals('S',a.charAt(0)); assertEquals('B',a.charAt(6)); assertEquals('r',a.charAt(11)); } @Test public void capacityTest(){ assertEquals(16,a.capacity()); assertEquals(34,b.capacity()); assertEquals(70,c.capacity()); } @Test public void testlength(){ assertEquals(12,a.length()); assertEquals(24,b.length()); assertEquals(36,c.length()); } @Test public void testindexOf(){ assertEquals(0,a.indexOf("Str")); assertEquals(6,a.indexOf("Buff")); assertEquals(10,a.indexOf("er")); } }
测试代码运行结果截图
思考题
老师在博客中《积极主动敲代码,使用JUnit学习Java》提出了思考题:
capacity()
超过16个字符会再分配18个字符的空间。那超过34个字符呢?你猜猜capacity()的返回值(52?54?56?还是其他值)并修改上面的代码验证一下。
在自己测试之后感觉规律是:capacity*2+2,查询资料之后在博客关于StringBuff对象的capacitya方法返回值中得到了验证:
在上述测试代码与结果中也得到体现。
面向对象三要素:封装、继承、多态
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,依赖倒置原则)
本次实验主要学习掌握的是:
OCP
:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。(Softeware entities like classes,modules and functions should be open for extension but closed for modifications.)
DIP
:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。(High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions.)
任务三:对MyDoc类进行扩充,让其支持Float类
在仅实现Integer
类时,UML
图是正确的,但是一旦考虑到实现其他类的功能就会违反OCP
原则
实验题目:让系统支持Float类,并在MyDoc类中添加测试代码表明添加正确
正确的UML
如下:
- 代码:
abstract class Data{//Data类:打印数据
abstract public void Display();
}
class Integer extends Data{//Data的子类
int value;
Integer(){
value=5310;
}
public void Display(){//方法重写
System.out.println(value);
}
}
class Float extends Data{//Data的子类
float value;
Float(){
value=165310.2f;
}
public void Display() {//方法重写
System.out.println(value);
}
}
abstract class Factory{
abstract public Data CreatDataObject();//抽象类:创造指定数据类型,所有数据类型的父类
}
class IntegerFactory extends Factory{//Integer类,Factory子类
public Data CreatDataObject(){//方法重写
return new Integer();
}
}
class FloatFactory extends Factory{//Byte类,Factory子类
public Data CreatDataObject(){//方法重写
return new Float();
}
}
class Documents{
Data pd;
Documents(Factory pf){
pd=pf.CreatDataObject();//使pd是数据类型的上转型对象
}
public void displayData(){//调用pd子类,即不同数据类型重写的对象
pd.Display();
}
}
public class MyDoc{
static Documents b;
public static void main(String[] args) {
b=new Documents(new FloatFactory());
b.displayData();
}
}
-
运行结果截图:
-
启发:
在本次试验后我们进行的结对实验,在实现
Language
的语言选择功能时,学习模仿了本次的实验思想,实现了S.O.L.I.D原则。
任务四:以TDD的方式开发一个复数类Complex
回顾之前的实验过程,以TDD的方式进行编程
-
伪代码:
1)属性:复数包含实部和虚部两个部分, double RealPart;复数的实部 double ImagePart;复数的虚部 getRealPart():返回复数的实部 getImagePart();返回复数的虚部 setRealPart():设置复数的实部 setImagePart();设置复数的虚部 输出形式:a+bi (2)方法: ①定义构造函数 public Complex() public Complex(double R,double I) ②定义公有方法:加减乘除 Complex ComplexAdd(Complex a):实现复数加法 Complex ComplexSub(Complex a):实现复数减法 Complex ComplexMulti(Complex a):实现复数乘法 Complex ComplexDiv(Complex a):实现复数除法 ③Override Object public String toString():将计算结果转化为字符串形式并输出
-
以TDD方式编写代码:
-
测试代码:
import junit.framework.TestCase; import org.junit.Test; public class ComplexTest extends TestCase { Complex a=new Complex(0,0); Complex b=new Complex(1,1); Complex c=new Complex(-1,-1); Complex d=new Complex(20.16,53.10); Complex e=new Complex(2,3); @Test public void testgetReal(){ assertEquals(0.0,a.getRealPart()); assertEquals(-1.0,c.getRealPart()); assertEquals(20.16,d.getRealPart()); } @Test public void testgetIma(){ assertEquals(0.0,a.getImagePart()); assertEquals(-1.0,c.getImagePart()); assertEquals(53.1,d.getImagePart()); } @Test public void testComAdd(){ assertEquals("0.0",b.ComplexAdd(c).toString()); assertEquals("1.0+i",a.ComplexAdd(b).toString()); assertEquals("19.16+52.1i",c.ComplexAdd(d).toString()); assertEquals("-1.0-i",a.ComplexAdd(c).toString()); assertEquals("21.16+54.1i",b.ComplexAdd(d).toString()); } @Test public void testComSub(){ assertEquals("1.0+i",b.ComplexSub(a).toString()); assertEquals("-21.16-54.1i",c.ComplexSub(d).toString()); assertEquals("2.0+2.0i",b.ComplexSub(c).toString()); } @Test public void testComMul(){ assertEquals("0.0",a.ComplexMulti(d).toString()); assertEquals("-1.0-i",b.ComplexMulti(c).toString()); assertEquals("-20.16-53.1i",c.ComplexMulti(d).toString()); assertEquals("40.32+159.3i",d.ComplexMulti(e).toString()); } @Test public void testComDiv(){ assertEquals("0.0",a.ComplexDiv(b).toString()); assertEquals("-1.0-i",c.ComplexDiv(b).toString()); assertEquals("-0.5-0.3333333333333333i",c.ComplexDiv(e).toString()); assertEquals("10.08+17.7i",d.ComplexDiv(e).toString()); } }
-
产品代码:
public class Complex { double a,b; Complex(double m,double n){//构造函数设置实部虚部 a=m; b=n; } public double getRealPart(){//返回实部 return a; } public double getImagePart() {//返回虚部 return b; } public Complex ComplexAdd(Complex y){//加法 double m=y.getRealPart(); double n=y.getImagePart(); double x=a+m; double z=b+n; return new Complex(x,z); } public Complex ComplexSub(Complex y){ double m=y.getRealPart(); double n=y.getImagePart(); double x=a-m; double z=b-n; return new Complex(x,z); } public Complex ComplexMulti(Complex y){ double m=y.getRealPart(); double n=y.getImagePart(); double x=a*m; double z=b*n; return new Complex(x,z); } public Complex ComplexDiv(Complex y){ double m=y.getRealPart(); double n=y.getImagePart(); double x=a/m; double z=b/n; return new Complex(x,z); } @Override public java.lang.String toString() { String s=""; if (a!=0&&b>0&&b!=1){ s+= a+"+"+ b+"i"; } else if(a!=0&&b==1){ s+=a+"+i"; } else if (a!=0&&b<0&&b!=-1){ s+= a+""+b+"i"; } else if (a!=0&&b==-1){ s+=a+"-i"; } else if (a!=0&&b==0){ s+=a; } else if (a==0&&b!=0){ s+=b+"i"; } else if (a==0&&b==0){ s+="0.0"; } return s; } }
-
实验运行结果:
任务五:使用StarUML对实验二中的代码进行建模
UML 的基本介绍
- UML由3个要素构成:UML的基本构造块、支配这些构造块如何放置在一起的规则和运用于整个语言的公用机制。
- UML有3种基本的构造块:事物、关系和图。
- 事物是对模型中最具有代表性的成分的抽象,包括结构事物,如类(Class)、接口(Interface)、协作(Collaboration)、用例(UseCase)、主动类(ActiveClass)、组件(Component)和节点(Node);行为事物,如交互(Interaction)、态机(Statemachine)、分组事物(包,Package)、注释事物(注解,Note)。
- 关系用来把事物结合在一起,包括依赖、关联、泛化和实现关系。
UML绘制
StarUML
已经有For MAC
版本,本次绘图就是通过StarUML
完成的,本次实验中没有运用多个类的实验,所以以书上代码为示例绘制:
实验总结与体会
本次实验最大的收获是:
-
技能
- 学会使用
Junit
测试代码 - 学会利用
StarUML
绘制UML
图
- 学会使用
-
元知识
- 深刻理解学习了S.O.L.I.D原则,尤其是
OCP
与DIP
原则 - 学会使用
TDD
编程方法
PSP表格
步骤 需求分析 百分比 需求分析 20min 7.1% 设计 20min 7.1% 代码实现 80min 28.6% 测试 80min 42.9% 分析总结 40min 14.3% - 深刻理解学习了S.O.L.I.D原则,尤其是