实验二
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实验二相关的知识,希望对你有一定的参考价值。
一、单元测试和TDD
用程序解决问题时,要学会写以下三种代码:
- 伪代码
- 产品代码
- 测试代码
正确的顺序应为:伪代码(思路)→ 测试代码(产品预期功能)→ 产品代码(实现预期功能),这种开发方法叫“测试驱动开发”(TDD)。TDD的一般步骤如下:
- 明确当前要完成的功能,记录成一个测试列表
- 快速完成编写针对此功能的测试用例
- 测试代码编译不通过(没产品代码呢)
- 编写产品代码
- 测试通过
- 对代码进行重构,并保证测试通过(重构下次实验练习)
- 循环完成所有功能的开发
基于TDD,可以有效避免过度开发的现象,因为我们只需要让测试通过即可。
任务一:实现百分制成绩转成“优、良、中、及格、不及格”五级制成绩的功能
以这个任务为例,我们来对TDD方法进行一次小小的实践。
首先要明白自己的程序需要进行哪些操作?要实现什么目标?伪代码可以帮我们理清思路。
百分制转五分制:
如果成绩小于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 "错误";
}
}
产品代码是为用户提供的,为了保证正确性,我们需要对自己的程序进行测试,考虑所有可能的情况,来判断结果是否合乎要求。这是我们就需要写测试代码。
根据我们现在的理解,测试代码不就是不断调用System.out.println(),来判断输出是否合乎预期嘛?所以可能会写成下面这种代码:
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!");
}
}
如果输出test passed!,那就代表通过测试咯~
可是...如果输出test failed!呢?那事情就很糟糕了。我们需要把每一个测试用例的结果都打印出来,看看到底是哪里出现了该死的“test failed”。本题中的测试用例很少,那如果做大的项目时有成千上万个用例呢?
不用担心!Java中有单元测试工具JUnit来辅助进行TDD,我们用TDD的方式把前面百分制转五分制的例子重写一次,体会一下有测试工具支持的开发的好处。
鼠标放在需要测试的类上单击,出现一个黄色灯泡,点击选择“Create 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));
}
}
如果出现问题,IDEA就会提示我们具体是哪一步出错了。比如下面这个实例(我把95分的断言值改成了“良好”,我们知道测试肯定不会通过):
可以看出,IDEA提示我们Expected:良好; Actual:优秀,最后一行还有at test2.MyUtilTest.testNormal(MyUtilTest.java:11),这下我们明白了,是testNormal()方法中一个为“良好”的断言错了。这样一来,是不是比上面那种方法方便很多了呢?
当然了,测试代码不能随便一写草草了事,作为开发者的我们必须要考虑得足够周全,尤其是各种边界情况以及非法输入等等。比如本题我们需要添加testExceptions()、testBoundary()等等各种方法进行测试。这些老师的教程里写得很详细,我在此就不赘述了。
任务二:以TDD的方式研究学习StringBuffer
这个任务主要锻炼我们自己写JUnit测试用例的能力。老师在教程里给出的程序如下:
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()、capacity()、length()、indexOf。在产品代码里,我们需要为这四个方法加上返回值,并与我们的断言进行比较。产品代码如下:
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);
}
}
接下来我们需要对调用各种方法的返回值进行猜测。查询API文档可知,对charAt(int i)的解释为:“返回此序列中指定索引处的 char 值。第一个 char 值在索引 0 处,第二个在索引 1 处,依此类推,这类似于数组索引。”indexOf(String s)则返回输入的子字符串的第一个字母在母字符串的位置。这两个看起来都比较好理解,那capacity()和length()呢?我在学习StringBuffer的时候也遇到了这样的困惑,对于这两者之间的区别问题,可以参考我博客的“问题解决”模块。
基于以上的思考和学习,我们可以对各个方法的返回值有一个精确地分析,接下来只需要比较产品代码中的方法与我们的断言值是否相等即可。
出于对老师这篇博客中问题的思考,我设置了长度不同的三个字符串进行测试,代码如下:
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() throws Exception{
assertEquals(‘S‘,a.charAt(0));
assertEquals(‘g‘,a.charAt(5));
assertEquals(‘r‘,a.charAt(11));
}
@Test
public void testcapacity() throws Exception{
assertEquals(28,a.capacity());
assertEquals(40,b.capacity());
assertEquals(52,c.capacity());
}
@Test
public void testlength() throws Exception{
assertEquals(12,a.length());
assertEquals(24,b.length());
assertEquals(36,c.length());
}
@Test
public void testindexOf() throws Exception{
assertEquals(0,a.indexOf("Str"));
assertEquals(5,a.indexOf("gBu"));
assertEquals(10,a.indexOf("er"));
}
}
可以看到,出现了“green bar”,说明测试通过了,我们对StringBuffer也有了更加深刻的认识。
二、面向对象三要素:封装、继承、多态
面向对象(Object-Oriented)的三要素包括:封装、继承、多态。面向对象的思想涉及到软件开发的各个方面,如面向对象分析(OOA)、面向对象设计(OOD)、面向对象编程实现(OOP)。OOA根据抽象关键的问题域来分解系统,关注是什么(what)。OOD是一种提供符号设计系统的面向对象的实现过程,用非常接近问题域术语的方法把系统构造成“现实世界”的对象,关注怎么做(how),通过模型来实现功能规范。OOP则在设计的基础上用编程语言(如Java)编码。贯穿OOA、OOD和OOP的主线正是抽象。
任务三:使用StarUML对实验二中的代码进行建模
UML是一种通用的建模语言,可以非常直观地表现出各个结构之间的关系。
以上是关于实验二的主要内容,如果未能解决你的问题,请参考以下文章