20145129实验报告(二)

20145129实验报告(二)

实验目的

  • 初步掌握单元测试和TDD;
  • 理解并掌握面向对象三要素:封装、继承、多态;
  • 初步掌握UML建模
  • 熟悉S.O.L.I.D原则
  • 了解设计模式

实验内容

(一)单元测试

三种代码

  • 三种代码分别有:伪代码、产品代码、测试代码
  • 伪代码:伪代码可以用汉语写但不要写与具体编程语言语法相关的语句,从意图层面来解决问题。
  • 产品代码:对伪代码用特定编程语言翻译一下就是可用的产品代码
  • 测试代码:Java编程时,程序员对类实现的测试叫单元测试,测试用例是为某个特殊目标而编制的一组测试输入、执行条件以及预期结果,以便测试某个程序路径或核实是否满足某个特定需求,且只有一组输入的测试是不充分的。
  • 实际操作:
    • 先建立学号文件夹,之后的程序都存放进这里。

    • 操作过程遇到的问题:无

TDD(Test Driven Devlopment, 测试驱动开发)

  • 先写测试代码,然后再写产品代码的开发方法叫“测试驱动开发”(TDD)。
  • TDD的一般步骤:
    • 明确当前要完成的功能,记录成一个测试列表
    • 快速完成编写针对此功能的测试用例
    • 测试代码编译不通过(没产品代码)
    • 编写产品代码
    • 测试通过
    • 对代码进行重构,并保证测试通过
    • 循环完成所有功能的开发
  • TDD的编码节奏是:
    • 增加测试代码,JUnit出现红条
    • 修改产品代码
    • JUnit出现绿条,任务完成
  • 实际操作:
    • 打开Eclipse新建一个TDDDemo的Java项目,新建一个测试目录test,并新建一个测试用例类MyUtilTest(注意测试用例前一定要有注解@Test)且输入代码:

- 修改错误并进行测试
    - MyUtil类还不存在,类中的percentage2fivegrade方法也不存在,新建类并定义方法:

    - Junit后,显示红色,说明测试没通过,测试代码第十行传入55时,期望结果是“不及格”,代码返回了“错误”,修改代码后通过测试,如图:

    - 增加一个测试边界情况的用例testBoundary,发现没有通过测试,在修改代码后,测试通过:

  • 操作过程遇到的问题:在新建测试用例类时,操作过程与教程里有些不同,但不影响后面操作。

(二)面向对象三要素

抽象

  • 程序设计中,抽象包括两个方面,一是过程抽象,二是数据抽象。
  • 操作过程遇到的问题:按照教程中给的代码运行后发现输出了三个3,而不是1、2、3,检查原代码后发现方法中原本println(n)应该为println(i):

封装、继承与多态

  • 面向对象(Object-Oriented)的三要素包括:封装、继承、多态。
  • 面向对象的思想涉及到软件开发的各个方面,如面向对象分析(OOA)、面向对象设计(OOD)、面向对象编程实现(OOP)。
  • OOA根据抽象关键的问题域来分解系统,关注是什么(what)。OOD是一种提供符号设计系统的面向对象的实现过程,用非常接近问题域术语的方法把系统构造成“现实世界”的对象,关注怎么做(how),通过模型来实现功能规范。OOP则在设计的基础上用编程语言(如Java)编码。贯穿OOA、OOD和OOP的主线正是抽象。
  • OOD中建模会用图形化的建模语言UML(Unified Modeling Language),UML是一种通用的建模语言。
  • 数据抽象才是OOP的核心和起源。OO三要素的第一个要素是封装,封装就是将数据与相关行为包装在一起以实现信息就隐藏。
  • 封装实际上使用方法(method)将类的数据隐藏起来,控制用户对类的修改和访问数据的程度,从而带来模块化(Modularity)和信息隐藏(Information hiding)的好处;接口(interface)是封装的准确描述手段。
  • 继承指一个类的定义可以基于另外一个已经存在的类,即子类基于父类,从而实现父类代码的重用。继承关系表达了”Is a kind of“的关系,称为“ISA”关系。继承的关键在于确认子类为父类的一个特殊类型。继承是实现软件可重用的根基,是提高软件系统的可扩展性与可维护性的主要途径。
  • 面向对象中允许不同类的对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不同的行为方式,我们称此现象为多态性。Java中,多态是指不同的类对象调用同一个签名的成员方法时将执行不同代码的现象。多态是面向对象程序设计的灵活性和可扩展性的基础。
  • 在UML 里,一个类的属性能显示它的名字,类型,初始化值,属性也可以显示private,public,protected。+表示public、#表示 protected、-表示 private。UML中依赖用带箭头的直线表示。UML类图中继承的表示法,是用一个带三角的直线指向父类。
  • 实际操作:
    • Dog类通过使用类和访问控制(private,public)隐藏了属性color,开放了接口setColor(),getColor(),bark()和toString。Dog类是一个模块,我们可以通过下面的代码使用它,测试代码与运行结果如下:

    • 打开UML建模软件umbrello,建立一个Cat类(模仿Dog类实现Cat类)和Test类,如图AnimalTest类依赖Dog类和Cat类:

    • 在构建过程中,发现教程给的方法不能在软件中实际操作,经过摸索后找到构建方法:

- 对应的测试代码和运行结果如下图所示:

- Dog类和Cat类都有Color属性和相应的setter和getter方法,明显违反了前面提到的DRY原则,我们可以通过继承解决这个问题,把Color属性和相应的setter和getter方法放到父类Animal中,如以下UML较图所示:

- 把Dog类中的bark()和Cat类中的meow()抽象成一个抽象方法shout(),Dog类和Cat类中覆盖这个方法,对应的代码和测试代码和运行结果如以下图所示:

  • 操作过程中出现的问题:
    • 自己摸索了Java代码生成UML图的方法:

(三)设计模式初步

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是OOD中最重要的一个原则,OCP的内容是:软件实体(类,模块,函数等)应该对扩充开放,对修改封闭。OCP可以用以下手段实现:(1)抽象和继承,(2)面向接口编程。
  • SRP的内容是:决不要有一个以上的理由修改一个类
  • LSP的内容是:子类必须可以被其基类所代,使用指向基类的指针或引用的函数,必须能够在不知道具体派生类对象类型的情况下使用它
  • LSP的核心思想是父类型对象可以被子类型对象所取代。不要滥用继承,行为功能(behavior)不是内在的、私有的,而是外在、公开的,是客户程序所依赖的接口。
  • ISP的内容是:客户不应该依赖他们并未使用的接口
  • DIP的内容是:高层模块不应该依赖于低层模块。二者都应该依赖于抽象,抽象不应该依赖于细节。细节应该依赖于抽象。DIP在应用中通过依赖注入的方式实现解耦,重用低级模块,重用实现,解除依赖。
  • 除SOLID原则外还有很多其它的面向对象原则。如:
    • "组合替代继承":这是说相对于继承,要更倾向于使用组合;
    • "笛米特法则":这是说"你的类对其它类知道的越少越好";
    • "共同封闭原则":这是说"相关类应该打包在一起";
    • "稳定抽象原则":这是说"类越稳定,越应该由抽象类组成";

模式与设计模式

  • 模式是某外在环境(Context) 下﹐对特定问题(Problem)的惯用解决之道(Solution)。
  • 计算机科学中有很多模式:
    • GRASP模式
    • 分析模式
    • 软件体系结构模式
    • 设计模式:创建型,结构型,行为型(最重要的是设计模式,在面向对象中设计模式的地位可以和面向过程编程中的数据结构的地位相当)
    • 管理模式: The Manager Pool 实现模式
    • 界面设计交互模式
    • ... ...

设计模式实示例

  • 设计模式(design pattern)提供一个用于细化软件系统的子系统或组件,或它们之间的关系图,它描述通信组件的公共再现结构,通信组件可以解决特定语境中的一个设计问题。
  • 设计模式背后是抽象和SOLID原则。
  • 设计模式有四个基本要素:
    • Pattern name:描述模式,便于交流,存档
    • Problem:描述何处应用该模式
    • Solution:描述一个设计的组成元素,不针对特例
    • Consequence:应用该模式的结果和权衡(trade-offs)
  • 操作过程中出现的问题:
    • 实验教程中“抽象工厂模式应用如下:”下面的图所对应的代码,在导入UML所呈图像少了几个子类,说明代码不完整(已补全)。

练习

使用TDD的方式设计关实现复数类Complex。

  • 伪代码
	/*
	 *	1)复数类ComplexNumber的属性
	*	m_dRealPart: 实部,代表复数的实数部分
	*	m_dImaginPart: 虚部,代表复数的虚数部分
	*	2)复数类ComplexNumber的方法
	*	ComplexNumber() 构造函数,将实部,虚部都置为0
	*	ComplexNumber(double r, double I) 构造函数,创
	*	建复数对象的同时完成复数的实部,虚部的初始化
	*	GetRealPart() 获取实部
	*	GetImaginaryPart() 获取虚部
	*	SetRealPart(double d) 设置实部
	*	SetImaginaryPart(double d) 设置虚部
	*	ComplexAdd(ComplexNumber c) 复数相加
	*	ComplexAdd(double c) 复数相加
	*	ComplexMinus(ComplexNumber c) 复数相减
	*	ComplexMinus(double c) 复数相减
	*	ComplexMulti(ComplexNumber c)  复数相乘
	*	ComplexMulti(double c)  复数相乘
	*	toString() 把当前复数对象的实部,虚部组合成a+bi的字符串形式
 */
  • 测试代码

    import static org.junit.Assert.*;

    import org.junit.*;

    public class ComplexNumberTest {
    	    ComplexNumber cn = null;
    	    @Before
    	    public void setUp() throws Exception {
		    cn = new ComplexNumber();
	    }
	    @After
	    public void tearDown() throws Exception {
		    cn = null;
	    }
	    @Test
	    public void CheckGetRealPart() {
		    cn.SetRealPart(11);
		    assertEquals(11, cn.GetRealPart(),0.00);
	    }
	    @Test
	    public void ChcekGetImaginaryPart() {
		    cn.SetImaginaryPart(20);
		    assertEquals(20, cn.GetImaginaryPart(),0.00);
	    }
	    @Test
	    public void CheckAdd() {
		    ComplexNumber complexNumber = new ComplexNumber(10,15);
		    ComplexNumber complexNumber2 = new ComplexNumber(16,20);
		    ComplexNumber complexNumber3 = new ComplexNumber(26,35);
		    assertEquals(complexNumber3.toString(),complexNumber.ComplexAdd(complexNumber2).toString());
	    }
	    @Test
	    public void CheckMinus() {
		    ComplexNumber complexNumber = new ComplexNumber(16,25);
		    ComplexNumber complexNumber2 = new ComplexNumber(10,20);
		    ComplexNumber complexNumber3 = new ComplexNumber(6,5);
		    assertEquals(complexNumber3.toString(),complexNumber.ComplexMinus(complexNumber2).toString());
	    }
	    @Test
	    public void CheckMulti() {
		    ComplexNumber complexNumber = new ComplexNumber(16,25);
		    ComplexNumber complexNumber2 = new ComplexNumber(2,2);
		    ComplexNumber complexNumber3 = new ComplexNumber(-18,82);
		    assertEquals(complexNumber3.toString(),complexNumber.ComplexMulti(complexNumber2).toString());
	    }
    }
  • 产品代码

    public class ComplexNumber {
	    double m_dRealPart;
        double m_dImaginPart;
        public  ComplexNumber()
        {
            m_dRealPart = 0;
            m_dImaginPart = 0;
        }
        public ComplexNumber(double r,double I)
        {
            m_dRealPart = r;
            m_dImaginPart = I;
        }
        public double GetRealPart()
        {
            return m_dRealPart;
        }
        public double GetImaginaryPart()
        {
            return m_dImaginPart;
        }
        public void SetRealPart(double r)
        {
            m_dRealPart = r;
        }
        public void SetImaginaryPart(double I)
        {
            m_dImaginPart = I;
        }
        public ComplexNumber ComplexAdd(ComplexNumber c)
        {
            ComplexNumber cn = new ComplexNumber();
            cn.SetImaginaryPart(m_dImaginPart + c.GetImaginaryPart());
            cn.SetRealPart(m_dRealPart + c.GetRealPart());
            return cn;
        }
        public ComplexNumber ComplexAdd(double c)
        {
            ComplexNumber cn = new ComplexNumber();
            cn.SetImaginaryPart(m_dImaginPart);
            cn.SetRealPart(m_dRealPart + c);
            return cn;
        }
        public ComplexNumber ComplexMinus(ComplexNumber c)
        {
            ComplexNumber cn = new ComplexNumber();
            cn.SetImaginaryPart(m_dImaginPart - c.GetImaginaryPart());
            cn.SetRealPart(m_dRealPart - c.GetRealPart());
            return cn;
        }
        public ComplexNumber ComplexMinus(double c)
        {
            ComplexNumber cn = new ComplexNumber();
            cn.SetImaginaryPart(m_dImaginPart);
            cn.SetRealPart(m_dRealPart - c);
            return cn;
        }
        public ComplexNumber ComplexMulti(ComplexNumber c)
        {
            ComplexNumber cn = new ComplexNumber();
            cn.SetImaginaryPart(c.GetImaginaryPart() * m_dRealPart + m_dImaginPart * c.GetRealPart());
            cn.SetRealPart(m_dRealPart * c.GetImaginaryPart() - m_dImaginPart * c.GetRealPart());
            return cn;
        }
        public ComplexNumber ComplexMulti(double c)
        {
            ComplexNumber cn = new ComplexNumber();
            cn.SetImaginaryPart(c * m_dImaginPart);
            cn.SetRealPart(c * m_dRealPart);
            return cn;
        }
        public String toString()
        {
            String result;
            result = m_dRealPart + "+" + m_dImaginPart + "i";
            return result;
        }
    }
  • 测试:
    • 报错显示第37行,应该是6.0+5.0i,返回值却是0.0,检查后发现两个字符串比较不加误差判断...我在编写测试代码时忘了删除,然后发现后面都加了,于是纠正错误,通过测试。

  • 在编写测试代码时,通过了解“org.junit.Assert.*”我发现junit中一些很好用的注解:
    • @Before 注解:与junit3.x中的setUp()方法功能一样,在每个测试方法之前执行;
    • @After 注解:与junit3.x中的tearDown()方法功能一样,在每个测试方法之后执行;
    • @BeforeClass 注解:在所有方法执行之前执行;
    • @AfterClass 注解:在所有方法执行之后执行;
    • @Test(timeout = xxx) 注解:设置当前测试方法在一定时间内运行完,否则返回错误;
    • @Test(expected = Exception.class) 注解:设置被测试的方法是否有异常抛出。抛出异常类型为:Exception.class;
    • @Ignore 注解:注释掉一个测试方法或一个类,被注释的方法或类,不会被执行。
  • 在编写测试代码时我采用了@Before 和@After。

体会

这次实验信息量比上一次大好多,实验课三个小时我都没有做完,不过收获颇多。详细了解到TDD的测试方法。对封装、继承与多态有了更加深刻理解。以及对UML的初步认识。了解了S.O.L.I.D原则。在自己动手设计程序过程中不断积累经验和串联知识。遇到一些新知识点可以自己动手查一查了解一下除了老师教的其他内容,可以收获更多。

PSP

步骤 耗时 百分比
需求分析 10分钟 10%
设计 10分钟 10%
代码实现 50分钟 50%
测试 20分钟 20%
分析总结 10分钟 10%
posted @ 2016-04-14 23:58  20145129  阅读(320)  评论(1编辑  收藏  举报