测试驱动开发(Test-Driven Development)是敏捷开发中的一项核心实践和技术,也是一种设计方法论。TDD的原理是在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。TDD虽是敏捷方法的核心实践,但不只适用于XP(Extreme Programming),同样可以适用于其他开发方法和过程。
TDD的基本思路就是通过测试来推动整个开发的进行,但测试驱动开发并不只是单纯的测试工作,而是把需求分析,设计,质量控制量化的过程。
TDD的重要目的不仅仅是测试软件,测试工作保证代码质量仅仅是其中一部分,而且是在开发过程中帮助客户和程序员去除模棱两可的需求。TDD首先考虑使用需求(对象、功能、过程、接口等),主要是编写测试用例框架对功能的过程和接口进行设计,而测试框架可以持续进行验证。
项目概要
多线程下载要求的技术点比较多,其中主要包括以下几个方面:
- 1.连接网络资源
- 2.获取网络资源
- 3.输出网络资源
4.多线程下载
根据上面几个大的方向,我们可以把多线程下载的项目抽象成各个问题的解决方法
接下来,我们运用TDD的思想,具体解决我们所需要实现的方法类。链接网络资源模块
首先要考虑链接网络,我们必须先要考虑我们设计的方法能发实现,因此首先写了一段关于测试url的测试代码其中,我们测试的就是我们的connectionImpl类是否能满足需求:
//测试连接url的功能
public void testContentLength() throws Exception{
//new一个接口,然后实现这个接口
ConnectionManager connMan = new ConnectionManagerImpl();
Connection conn = connMan.open("http://www.hinews.cn/pic/0/13/91/26/13912621_821796.jpg");
Assert.assertEquals(35470, conn.getContentLength());
});
}
接下来,
这个测试用例所需要实现的方法有一个open和getContentLength方法,我们集中编写connectionImpl类,在编写connectionImpl类的时候,我们先写了可以保证安全性的一个管理类,如下
public class ConnectionManagerImpl implements ConnectionManager {
public Connection open(String url) throws ConnectionException {
return new ConnectionImpl(url);
}
我们期望通过这个类把url传进ConnectionImpl类中,这样就不用暴露,保证代码的安全性,在编写这个类之前,我们先定义了一个接口
package com.coderising.download.api;
public interface ConnectionManager {
/**
* 给定一个url , 打开一个连接
* @param url
* @return
*/
public Connection open(String url) throws ConnectionException;
}
这样,我们可以把ConnectionImpl的构造函数写成如下方式
URL url;
static final int BUFFER_SIZE = 1024;
ConnectionImpl(String _url) throws ConnectionException{
try {
url = new URL(_url);
} catch (MalformedURLException e) {
throw new ConnectionException(e);
}
}
接下来就可以实现我们第一个测试的所需要的方法open方法了,而测试所需的第二个方法getContentLength需要的接口我们都写出来:
package com.coderising.download.api;
import java.io.IOException;
public interface Connection {
/**
* 给定开始和结束位置, 读取数据, 返回值是字节数组
* @param startPos 开始位置, 从0开始
* @param endPos 结束位置
* @return
*/
public byte[] read(int startPos,int endPos) throws IOException;
/**
* 得到数据内容的长度
* @return
*/
public int getContentLength();
/**
* 关闭连接
*/
public void close();
}
把这个接口接到类中,写出的方法为
public int getContentLength() {
URLConnection con;
try {
con = url.openConnection();
return con.getContentLength();
} catch (IOException e) {
e.printStackTrace();
}
return -1;
}
这样的话,我们的第一个测试用例就已经测试完毕。
紧接着,我们实现第二个测试用例:
//测试读入,确保设计的接口ok
public void testRead() throws Exception{
ConnectionManager connMan = new ConnectionManagerImpl();
Connection conn = connMan.open("http://www.hinews.cn/pic/0/13/91/26/13912621_821796.jpg");
byte[] data = conn.read(0, 35469);
Assert.assertEquals(35470, data.length);
data = conn.read(0, 1023);
Assert.assertEquals(1024, data.length);
data = conn.read(1024, 2023);
Assert.assertEquals(1000, data.length);
// 测试不充分,没有断言内容是否正确
}
这个测试用例主要是判断我们从网络端读取的文件大小是否与我们期望的一致,所以主要的方法是实现read方法。
方法实现如下:
public byte[] read(int startPos, int endPos) throws IOException {
HttpURLConnection httpConn = (HttpURLConnection)url.openConnection();
httpConn.setRequestProperty("Range", "bytes=" + startPos + "-"
+ endPos);
InputStream is = httpConn.getInputStream();
//is.skip(startPos);
byte[] buff = new byte[BUFFER_SIZE];
//读出应该下载的字节数
int totalLen = endPos - startPos + 1;
//内存中输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while(baos.size() < totalLen){
//关键:把数据放到数组中
int len = is.read(buff);
//检查长度,如果是-1必须跳出循环,即代表已经读完
if (len < 0) {
break;
}
//把读到的数据全部写到输出流
baos.write(buff,0, len);
}
//判断写到的输出流,如果写多则抛弃多余的,为什么会超出呢?因为
//每一次输入流读写的时候会都一段一段的写入buffer,假设去1034字节,第一次都去了1024数据
//下一次在读1024字节,因此会超出
if(baos.size() > totalLen){
byte[] data = baos.toByteArray();
return Arrays.copyOf(data, totalLen);
}
return baos.toByteArray();
}
所以在测试用例的作用下,我们就把我们需要的类写好了
总体如下:
package com.coderising.download.impl;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
import com.coderising.download.api.Connection;
import com.coderising.download.api.ConnectionException;
class ConnectionImpl implements Connection {
URL url;
static final int BUFFER_SIZE = 1024;
ConnectionImpl(String _url) throws ConnectionException{
try {
url = new URL(_url);
} catch (MalformedURLException e) {
throw new ConnectionException();
}
}
public byte[] read(int startPos, int endPos) throws IOException {
HttpURLConnection httpConn = (HttpURLConnection)url.openConnection();
httpConn.setRequestProperty("Range", "bytes=" + startPos + "-"
+ endPos);
InputStream is = httpConn.getInputStream();
//is.skip(startPos);
byte[] buff = new byte[BUFFER_SIZE];
//读出应该下载的字节数
int totalLen = endPos - startPos + 1;
//内存中输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while(baos.size() < totalLen){
//关键:把数据放到数组中
int len = is.read(buff);
//检查长度,如果是-1必须跳出循环,即代表已经读完
if (len < 0) {
break;
}
//把读到的数据全部写到输出流
baos.write(buff,0, len);
}
//判断写到的输出流,如果写多则抛弃多余的,为什么会超出呢?因为
//每一次输入流读写的时候会都一段一段的写入buffer,假设去1034字节,第一次都去了1024数据
//下一次在读1024字节,因此会超出
if(baos.size() > totalLen){
byte[] data = baos.toByteArray();
return Arrays.copyOf(data, totalLen);
}
return baos.toByteArray();
}
public int getContentLength() {
URLConnection con;
try {
con = url.openConnection();
return con.getContentLength();
} catch (IOException e) {
e.printStackTrace();
}
return -1;
}
public void close() {
}
}