标签:
一、 实验内容
1. 初步掌握单元测试和TDD
2. 理解并掌握面向对象三要素:封装、继承、多态
3. 初步掌握UML建模
4. 熟悉S.O.L.I.D原则
5. 了解设计模式
二、 实验步骤
【实验一】单元测试
(1) 三种代码
当我们想用程序解决问题时,要会写三种码:伪代码、产品代码、测试代码。
需求:我们要在一个MyUtil类中解决一个百分制成绩转成“优、良、中、及格、不及格”五级制成绩的功能。
先写伪代码,伪代码可以用汉语写,推荐用英语写,伪代码与具体编程语言无关,不要写与具体编程语言语法相关的语句(如用malloc分配内存,这样只能用C语言编程了),伪代码从意图层面来解决问题,最终,伪代码是产品代码最自然的、最好的注释。针对上面的问题,我们可以通过伪代码这样解决:
百分制转五分制:
如果成绩小于60,转成“不及格”
如果成绩在60与70之间,转成“及格”
如果成绩在70与80之间,转成“中等”
如果成绩在80与90之间,转成“良好”
如果成绩在90与100之间,转成“优秀”
其他,转成“错误”
有了伪代码,我们用java语言翻译一下,就是可用的产品代码了,翻译好的MyUtil.java如下:
写了产品代码,我们还要写测试代码,证明自己的代码没有问题。Java编程时,程序员对类实现的测试叫单元测试。类XXXX的单元测试,我们一般写建一个XXXXTest的类,针对MyUtil类我们写一个MyUtilTest.java的测试模块,代码如下:
public class MyUtilTest { public static void main(String[] args) { // 百分制成绩是50时应该返回五级制的“不及格” if(MyUtil.percentage2fivegrade(50) != "不及格") System.out.println("test failed!"); else System.out.println("test passed!"); } }
这里我们设计了一个测试用例(Test Case),测试用例是为某个特殊目标而编制的一组测试输入、执行条件以及预期结果,以便测试某个程序路径或核实是否满足某个特定需求。测试输入是“50”,预期结果是“不及格”。在Eclipse中运行结果:
只有一组输入的测试是不充分的,我们把一般情况都测试一下,代码如下:
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!"); } }
下面测试异常情况,输入为负分或大于100的成绩,代码如下:
public class MyUtilTest { public static void main(String[] args) { //测试出错情况 if(MyUtil.percentage2fivegrade(-10) != "错误") System.out.println("test failed 1!"); else if(MyUtil.percentage2fivegrade(115) != "错误") System.out.println("test failed 2!"); else System.out.println("test passed!"); } }
发现负分时与期望不一致,这里有一个bug,原因是判断不及格时没有要求成绩大于零。于是修改MyUtil.java,增加对负分的判断,代码如下:
public class MyUtilTest { public static void main(String[] args) { //测试出错情况 if(MyUtil.percentage2fivegrade(-10) != "错误") System.out.println("test failed 1!"); else if(MyUtil.percentage2fivegrade(115) != "错误") System.out.println("test failed 2!"); else System.out.println("test passed!"); } }
我们还没有测试边界情况,输入为“0,60,70,80,90,100”这些边界情况进行测试的代码如下:
public class MyUtilTest { public static void main(String[] args) { //测试边界情况 if(MyUtil.percentage2fivegrade(0) != "不及格") System.out.println("test failed 1!"); else if(MyUtil.percentage2fivegrade(60) != "及格") System.out.println("test failed 2!"); else if(MyUtil.percentage2fivegrade(70) != "中等") System.out.println("test failed 3!"); else if(MyUtil.percentage2fivegrade(80) != "良好") System.out.println("test failed 4!"); else if(MyUtil.percentage2fivegrade(90) != "优秀") System.out.println("test failed 5!"); else if(MyUtil.percentage2fivegrade(100) != "优秀") System.out.println("test failed 6!"); else System.out.println("test passed!"); } }
测试结果如下:
我们发现边界情况中输入100时有一个Bug。我们修改MyUtil.java,把判断优秀的条件中包含输入为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 "错误"; } }
(2) TDD(Test Driven Devlopment, 测试驱动开发)
增加第一个测试用例testNormal
,注意测试用例前一定要有注解@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)); } }
MyUtil
类还不存在,类中的percentage2fivegrade方法也不存在,我们在TDDDemo
的src
目录中新建一个MyUtil
的类,并实现percentage2fivegrade方法。
测试结果出现了一个红条(red bar),说明测试没通过,红条上面汇总了测试情况,运行了一个测试,没有错误,一个测试没通过。下面原因说的很清楚:测试代码第十行传入55时,期望结果是“不及格”,代码返回了“错误”,修改MyUtil.Java。
测试结果出现了一个绿条(green bar),说明测试通过了。TDD的目标是"Clean Code That Works",TDD的slogan是"Keep the bar green, to Keep the code clean"。
TDD的编码节奏是:
抽象一词的本意是指人在认识思维活动中对事物表象因素的舍弃和对本质因素的抽取。抽象是人类认识复杂事物和现象时经常使用的思维工具,抽象思维能力在程序设计中非常重要,"去粗取精、化繁为简、由表及里、异中求同"的抽象能力很大程度上决定了程序员的程序设计能力。
抽象就是抽出事物的本质特征而暂时不考虑他们的细节。对于复杂系统问题人们借助分层次抽象的方法进行问题求解;在抽象的最高层,可以使用问题环境的语言,以概括的方式叙述问题的解。在抽象的较低层,则采用过程化的方式进行描述。在描述问题解时,使用面向问题和面向实现的术语。 程序设计中,抽象包括两个方面,一是过程抽象,二是数据抽象。
面向对象(Object-Oriented)的三要素包括:封装、继承、多态。面向对象的思想涉及到软件开发的各个方面,如面向对象分析(OOA)、面向对象设计(OOD)、面向对象编程实现(OOP)。OOA根据抽象关键的问题域来分解系统,关注是什么(what)。OOD是一种提供符号设计系统的面向对象的实现过程,用非常接近问题域术语的方法把系统构造成“现实世界”的对象,关注怎么做(how),通过模型来实现功能规范。OOP则在设计的基础上用编程语言(如Java)编码。贯穿OOA、OOD和OOP的主线正是抽象。 OOD中建模会用图形化的建模语言UML(Unified Modeling Language),UML是一种通用的建模语言,我们实验中使用umbrello进行建模,Windows中推荐大家使用 StarUML。
过程抽象的结果是函数,数据抽象的结果是抽象数据类型(Abstract Data Type,ADT),类可以作具有继承和多态机制的ADT。数据抽象才是OOP的核心和起源。
OO三要素的第一个要素是封装,封装就是将数据与相关行为包装在一起以实现信息就隐藏。Java中用类进行封装,比如一个Dog类:
public class Dog { private String color; public String getColor() { return color; } public void setColor(String color) { this.color = color; } public String bark(){ return "汪汪"; } public String toString(){ return "The Dog‘s color is " + this.getColor() +", and it shouts "+ this.bark() + "!"; } }
封装实际上使用方法将类的数据隐藏起来,控制用户对类的修改和访问数据的程度,从而带来模块化和信息隐藏的好处;接口是封装的准确描述手段。 Dog
类通过使用类和访问控制(private,public)隐藏了属性color
,开放了接口setColor()
,getColor()
,bark()
和toString
。Dog
类是一个模块,我们可以通过下面的代码使用它,测试代码与运行结果如下
我们可以用UML中的类图来描述类Dog
,首先我们在实验楼的环境中打开shell,在命令行中输入umbrello
,打开UML建模软件umbrello,先单击工具栏上的类图标,再在
class diagram(类图)
中单击一下,会弹出一个圣诞框,输入类名Dog
,
我们把鼠标放到
Dog
类上,单击右键,选择Properties
,在弹出的对话框中的Display
中去掉Public Only
选项,
我们把鼠标放到
Dog
类上,单击右键,选择New->Attribute
,在弹出的对话框中的填好Type
,Name
,并选好Visibility
,我们把鼠标放到Dog
类上,单击右键,选择New->Operation
,在弹出的对话框中的填好Type
,Name
,并选好Visibility
,
我们可以看到,在UML 里,一个类的属性能显示它的名字,类型,初始化值,属性也可以显示private,public,protected。 类的方法能显示它们的方法名,参数,返回类型,以及方法的private,public,protected属性。其中:
使用UML可以让我们不必关注细节。同样,我们可以建立一个Cat
类(请大家模仿Dog类实现Cat类),
这时的测试类如以下UML图所示:
注意:UML类图要展示类之间的静态关系,AnimalTest
类依赖Dog
类和Cat
类,UML中依赖用带箭头的直线表示。 对应的测试代码和运行结果如下:
我们看到Dog
类和Cat
类都有Color
属性和相应的setter和getter方法,明显违反了前面提到的DRY
原则,我们可以通过继承解决这个问题,把Color
属性和相应的setter和getter方法放到父类Animal
中,如以下UML较图所示:
注意UML类图中继承的表示法,是用一个带三角的直线指向父类,通过继承,我们消除了Dog
类和Cat
类中的重复代码,符合DRY
的要求。 继承指一个类的定义可以基于另外一个已经存在的类,即子类基于父类,从而实现父类代码的重用。既存类称作基类、超类、父类(base class、super class、parent class),新类称作派生类、继承类、子类(derived class、inherited class、child class)。继承关系表达了”Is a kind of“的关系,称为“ISA”关系。继承的关键在于确认子类为父类的一个特殊类型 。继承是实现软件可重用的根基,是提高软件系统的可扩展性与可维护性的主要途径。 如上面所示,以封装为基础,继承可以实现代码复用,需要注意的是,继承更重要的作用是实现多态。 面向对象中允许不同类的对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不同的行为方式,我们称此现象为多态性。Java中,多态是指不同的类对象调用同一个签名的成员方法时将执行不同代码的现象。多态是面向对象程序设计的灵活性和可扩展性的基础。 我们再看看上一个类图,我们可以进一步抽象,把Dog
类中的bark()
和Cat
类中的meow()
抽象成一个抽象方法shout()
,Dog
类和Cat
类中覆盖这个方法,如以下UML图所示: 大家注意UML类图中的Animal
类中的shout()
方法是抽象方法,是斜体的,Animal
类是抽象类,也是斜体的。
对应的代码如下:
public abstract class Animal { private String color; public String getColor() { return color; } public void setColor(String color) { this.color = color; } public abstract String shout(); } public class Dog extends Animal{ public String shout(){ return "汪汪"; } public String toString(){ return "The Dog‘s color is " + this.getColor() +", and it shouts "+ this.shout() + "!"; } } public class Cat extends Animal{ public String shout(){ return "喵喵"; } public String toString(){ return "The Cat‘s color is " + this.getColor() +", and it shouts "+ this.shout() + "!"; } }
【实验三】练习
标签:
原文地址:http://www.cnblogs.com/bonjourvivi/p/4485607.html