20175315 實驗二《Java面向對象程序設計》實驗報告
一、實驗內容及步驟
1.初步掌握單元測試和TDD
- 單元測試
任務一:三種代碼
用程序解決問題時,要學會寫以下三種代碼:
- 偽代碼
- 產品代碼
- 測試代碼
TDD(測試驅動開發):
- 偽代碼(思路)
- 測試代碼(產品預期功能)
- 產品代碼(實現預期功能)
TDD的一般步驟如下:
- 明確當前要完成的功能,記錄成一個測試列表
- 快速完成編寫針對此功能的測試用例
- 測試代碼編譯不通過(沒產品代碼呢)
- 編寫產品代碼
- 測試通過
- 對代碼進行重構,並保證測試通過(重構下次實驗練習)
- 循環完成所有功能的開發
任務二: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)); } }
截圖如下:
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)是封裝的准確描述手段。
任務三:對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 = 20175315; } 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(); } }
運行結果截圖:
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()); } }
任務四:使用StarUML對實驗二中的代碼進行建模
最終結果截圖:
二、實驗過程中遇到的問題及解決
問題:在一開始的時候並不會使用Junit3進行測試
解決方法:通過百度以及室友提醒學會了如何使用
三、實驗體會與總結
我通過本次實驗學會了如何編寫測試代碼、如何繪畫UML圖以及在TDD模式下編寫代碼。
在自己上手實踐操作過程中,加深了對平時不清楚的知識點的理解,也掌握了的Junit的用法。在使用測試代碼的時候,既可以測試到代碼是否正確,又規范了編程習慣,單元測試提供了一種高效快速的測試代碼正確性的方法。