今天:

  1. 老师讲项目,剩余时间写项目

明天:

  1. 上午时间把黑马云盘项目消化结束
  2. 下午 装MySQL相关软件

    案例前置课

    前一天在进行文件上传案例的时候,客户端和服务端只能上传指定文件类型,比如客户端上传的是caocao.jpg,服务端也得跟着固定写出jpg文件。

    需求说明:

    1. 要实现在客户端上传任意文件,服务端都能够识别文件的名称和后缀,进行保存
    2. 上传的是caocao.jpg ,服务端保存的就是caocao.jpg
    3. 上传的是caocao.mp3 ,服务端保存的就是caocao.mp3
    分析:需要有协议思想,在TCP协议之上建立自己的一个通讯协议
    自定义协议:
    客户端和服务端两边约定,客户 端先传文件名,再传文件内容,服务端先收文件名再收文件内容,并用换行来分离文件名和文件内容。
    010101001001001(文件名.xxx)
    010010101001010010010100100010010101010010101010010010100100101001000100101010100101010100100101001001010010001001010101001010101001001010010010100100010010101010010101010010010100100101001000100101010100101010100100101001001010010001001010101001010101001001010010010100100010010101010010101010010010100100101001000100101010100101010100100101001001010010001001010101001010101001001010010010100100010010101010010101010001001010010001001010101001010101001...(实际的文件内容)
    

代码示例:

客户端

package com.itheima;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
/*
需求 : 文件上传和下载
客户端
//1. 创建Socket对象,指定服务端的地址和端口
//2. 获取一个输出流用来发送文件数据
//3. 创建一个本地文件输入流,用来读取本地文件
//4. 先用网络输出流把文件名发送(主要好换行)
//5. 边读,编写 [传文件数据]
//6. 告诉服务端,数据写出完毕
//7. 获取一个网络的输入流,用来读取服务端响应的数据
//8. 释放资源
*/
public class Client {
    public static void main(String[] args) throws IOException {
        //1. 创建Socket对象,指定服务端的地址和端口
        Socket socket = new Socket("127.0.0.1", 9999);
        //2. 获取一个输出流用来发送文件数据
        OutputStream netOut = socket.getOutputStream();
        //3. 创建一个本地文件输入流,用来读取本地文件
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入您要上传的文件路径:");
        String filePath = sc.nextLine();
        //为了方便获取文件名,可以把文件路径构建为文件对象
        File file = new File(filePath);
        //文件名
        String fileName = file.getName() + "\r\n";//名字+换行
        //文件的输入流
        FileInputStream localIn = new FileInputStream(file);
        //4. 先用网络输出流把文件名发送(要换行)
        netOut.write(fileName.getBytes());//将字符串采用平台默认UTF-8编码进行编码为字节数组
        //让客户端缓一下,使服务端来得及处理数据
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //5. 边读,编写 [传文件数据]
        byte[] buf = new byte[1024];
        int len;
        while ((len = localIn.read(buf)) != -1) {
            netOut.write(buf, 0, len);
        }
        //6. 告诉服务端,数据写出完毕
        socket.shutdownOutput();
        //7. 获取一个网络的输入流,用来读取服务端响应的数据
        InputStream netIn = socket.getInputStream();
        len = netIn.read(buf);
        System.out.println(new String(buf, 0, len));
        //8. 释放资源
        localIn.close();
        socket.close();
    }
}

服务端

package com.itheima;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/*
    需求 : 文件上传和下载
    服务端
    //1. 创建服务端对象,指定端口
    //2. 等待接受客户端的连接 accept
    //3. 获取网络的输入流,读取客户端发送的字节信息
    //4. 先按行读取数据,转换为字符串【读取文件名】
    //5. 使用读到的文件名,创建一个本地输出流,用来保存文件
    //6. 边读,编写 【读取文件数据,保存文件内容】
    //7. 获取一个网络输出流,告知客户端上传成功
    //8. 释放资源
 */
public class Server {
    public static void main(String[] args) throws IOException {
        //1. 创建服务端对象,指定端口
        ServerSocket serverSocket = new ServerSocket(9999);
        //让服务端循环的服务
        while (true) {
            //2. 等待接受客户端的连接 accept
            System.out.println("等待客户端连接!");
            Socket socket = serverSocket.accept();
            System.out.println("客户端连接成功!" + socket);
            //3. 获取网络的输入流,读取客户端发送的字节信息
            InputStream netIn = socket.getInputStream();
            //4. 先按行读取数据,转换为字符串【读取文件名】
            //InputStream--> InputStreamReader --> BufferedReader
            InputStreamReader isr = new InputStreamReader(netIn);//将字节流转成字符流
            BufferedReader br = new BufferedReader(isr);// 将字符流变成缓冲流
            //缓冲流读行
            String fileName = br.readLine();//客户端发送过来的第一行字符串,文件名字
            //5. 使用读到的文件名,创建一个本地输出流,用来保存文件
            FileOutputStream localOut = new FileOutputStream("day13_demo/server/" + fileName);
            //6. 边读,编写 【读取文件数据,保存文件内容】
            int len;
            byte[] buf = new byte[1024];
            while ((len = netIn.read(buf)) != -1) { //继续使用netIn读取客户端发送过来的数据
                localOut.write(buf, 0, len);//将读取的文件数据,保存到服务端文件
            }
            System.out.println("服务端文件接收完毕!");
            //7. 获取一个网络输出流,告知客户端上传成功
            OutputStream netOut = socket.getOutputStream();
            netOut.write("恭喜!文件上传成功!".getBytes());
            //8. 释放资源
            localOut.close();
            socket.close();
        }
    }
}

黑马云盘综合案例

学习目标

  • 理解需求,通过文档能够独立完成代码的开发

    一 需求说明

    day13 黑马云盘 - 图1
    实现一个乞丐版云盘。
    云盘项目包含客户端和服务端,通过客户端可以查看网盘内容,可以下载网盘中的文件,上传文件到网盘中

    二 概要设计

    2.1 服务端实现

    day13 黑马云盘 - 图2

    2.2 客户端实现

    day13 黑马云盘 - 图3

    三 详细设计

    3.1 技术选型

  1. 使用TCP编程技术实现客户端和服务端的开发,完成文件上下传的功能
  2. 自定义客户端和服务端之间的通讯协议
  3. 服务端多线程技术,实现高并发访问
  4. 自定义异常维护业务安全
  5. 使用JDK现有的API完成相关业务
    1. Socket,ServerSocket网络编程
    2. File文件类
    3. IO流相关类
    4. ResourceBundle配置文件读取

day13 黑马云盘 - 图4

3.2 协议定义

协议介绍

协议就是客户端和服务端通讯双方共同遵守的规定。
TCP协议是区分客户端服务端的一个比较底层的协议,传输的数据是字节码数据,如下
day13 黑马云盘 - 图5
当客户端连接服务端后,若要上传一个文件到服务端。直接将文件数据传给服务端,那么服务端该如何识别这个数据呢。对于服务端来讲收到的都是字节数据,服务端该如何识别客户端的操作意图,如果是上传文件,那么文件的类型是什么,文件的名字是什么等等信息。
怎样让双方在沟通时理解对方的信息呢?
我们可以把要发送给对方的,我们可以称为头信息。这个头信息包含了我要干什么,我的数据有哪些属性等信息,发头信息后再把具体的数据发送给对方。这样对方先把头信息获取,知道了我要做什么操作,发送过来的数据是是什么有什么数据,再接收具体的数据,就搞定了。
day13 黑马云盘 - 图6
加了头信息的数据如下:
day13 黑马云盘 - 图7

自定义协议

我们约定双方发送数据前要先发送一个头信息(协议信息),如下

type=操作类型,fileName=文件名,status=操作状态,message=说明信息\r\n

说明:第一行头信息要和数据分开,用行分隔符分开即可,每个信息使用 key=value键值对表示,么个信息使用逗号分隔

  1. type:操作类型,对应的值可以是如下:

    scan:表示浏览目录操作
    upload:表示上传操作
    download:表示下载操作
    下载操作示例:
    type=download
    
  2. fileName:要浏览操作文件的文件名

    下载文件“美女.jpg”示例:
    type=download,fileName=美女.jpg
    
  3. status:操作状态

    表示服务端收到客户端请求后回复操作状态,ok表示成功,failed表示失败
    type=download,fileName=美女.jpg,status=ok
    
  4. message:说明信息

    其他附加说明信息
    type=download,fileName=美女.jpg,status=ok,message=下载成功
    

    下载文件示例:

    客户端和服务端之间交互是基于协议进行的。
    1 客户端发送请求:
    客户端先发送协议

    type=download,fileName=root/美女.jpg,status=null,message=null,\r\n
    

    服务端响应请求:
    服务端收到协议,判断文件是否存在,并做回复协议。

  • 成功
    文件是存在的

    type=download,fileName=root/美女.jpg,status=ok,message=文件存在\r\n
    01010101010010美女数据010100101001010
    
  • 失败
    文件不存在的

    type=download,fileName=root/美女.jpg,status=failed,message=文件不存在!,\r\n
    

    非常重要:文件要体现在服务端的位置。客户端看到的文件都是基于服务端某一个文件夹而存在的,我们把这个文件夹叫做root。
    服务端开发时,需要指定一个合法的文件夹当做这个root。提供给客户端使用

    协议的封装

    为了方便协议的定义和解析,我们可以使用面向对象的思想进行封装成一个类,Protocol

    public class Protocol {
      //协议数据
      private String type;//操作类型
      private String fileName;//操作文件
      private String status;//操作状态
      private String message;//说明信息
      /**
       * 操作类型
       */
      public static class Type {
         public static final String  SCAN="scan";//浏览
         public static final String  UPLOAD="upload";//浏览
         public static final String  DOWNLOAD="download";//浏览
      }
      /**
       * 操作状态
       */
      public static class Status {
         public static final String  OK="ok";//成功
         public static final String  FAILED="failed";//失败
      }
    
    //省略其他构造器及getter/setter
    
    //工具,快速获取协议的工具
    }
    

    3.3 功能接口的定义

    这里的接口,指的是服务端暴露出来的功能。比如文件浏览功能,只要按照该接口指定的方式传输数据,就能完成功能了。

    文件浏览

  • 客户端与服务端的交互流程
    day13 黑马云盘 - 图8

  • 数据交互1 请求:客户端 —-> 服务端

    type=scan,fileName=需要浏览的目录,status=null,message=null\r\n
    
  • 2 响应: 服务端 —->客户端

    1. 成功:存在目录

      type=scan,fileName=需要浏览的目录,status=ok,message=null,
      xxx目录或者文件名称xxxxxx
      xxx目录或者文件名称xxxxxx
      xxx目录或者文件名称xxxxxx
      
    2. 失败:不存在目录

      type=scan,fileName=null,status=failed,message=目录不存在,只能浏览当前子目录,
      #没有后续数据
      
    3. 失败的原因,就是服务端没有对应的目录,无法遍历。

      文件上传

  • 文件上传交换流程
    day13 黑马云盘 - 图9

  • 数据交互
    1 请求:客户端 —-> 服务端

    type=upload,fileName=要上传的文件,status=null,message=null,
    
  • 2 响应: 服务端 —->客户端
    成功
    服务端:如果服务端不存在该文件,告诉客户端,文件可以上传的

    type=upload,fileName=要上传的文件,status=ok,message=null,
    
  • 客户端:收到信息后,继续上传文件信息,文件接收完毕后继续响应

    type=upload,fileName=要上传的文件,status=ok,message=文件上传成功,
    
  • 失败
    如果发现服务端已经存在该文件,提示客户端不要上传

    type=upload,fileName=要上传的文件,status=failed,message=文件已存在
    

    文件下载

  • 文件下载流程
    day13 黑马云盘 - 图10

  • 数据交互1 请求:客户端 —-> 服务端

    type=download,fileName=下载的文件名,status=null,message=null,
    
  • 2 响应: 服务端 —->客户端

    1. 成功
      服务端存在该文件,先响应成功协议,再发数据
      type=download,fileName=下载的文件名,status=ok,message=134448,
      
  • 010101010010下载的文件字节数据010101010010100101001010010010010101…. ``` 需要将下载的文件字节数据保存到文件中

  1. 失败 ```
  • type=download,fileName=下载的文件名,status=failed,message=文件不存在
    只有协议数据,没有实际下载的文件数据
    

    3.4 基础架构

    服务端架构

    day13 黑马云盘 - 图11
    代码结构:
    day13 黑马云盘 - 图12
    基础代码请看看今天资料

    客户端架构

    day13 黑马云盘 - 图13
    项目结构:
    day13 黑马云盘 - 图14
    基础代码请看看今天资料

    四 开发实战

    基于资料中提供的基础框架代码,完成客户端和服务端的开发.
    资料中有代码
    半成品项目.zip
    完整项目.zip

    五 知识扩展

    1 项目管理流程

    day13 黑马云盘 - 图15

    2 项目开发流程

    完整的IT项目开发流程

    一般情况下,企业开发软件时会按照基线和定制两块并行方式执行项目开发工作。无论什么公司,都需要遵从一套成熟的产品研发过程体系,才能做出质量较好的产品。因此,如果出现项目较多的情况,应该合理地安排基线和定制之前的里程碑,让基线产品能够尽量多地收集用户的通用型需求,为定制项目进度实现技术支撑,减少定制项目中大量更改代码、需要新增模块情况发生。此外,产品研发过程体系也需要按照业务实际时间要求变化,不要拘泥于一定要按照瀑布方式,或是敏捷方式进行管理,凡事都需要找到契合自己的方式。
    【这里以一个基线产品开发过程作为流程解释基础,需要注意的是,以下说描述的各个阶段,在项目执行前要明确各个阶段的目标、指定计划、及时沟通,并确保各个时期所有成员对项目理解一致】

    项目启动会

    项目启动会的目标是明确该产品开发项目的目标。目标不是孤立存在的,目标与计划相辅相成,目标指导计划,计划的有效性影响着目标的达成。所以在执行目标的时候,考虑清楚自己的行动计划,怎么做才能更有效地完成目标,是每个人都要详情清楚的问题,否则,目标越是不清晰或是过高,都会影响项目的实际结果。
    项目启动会需要说明项目目标、阶段划分、组织结构、管理流程等关键事项,并将这些内容写入 PPT(最好是有固定格式和范文,让团队内部或者公司内部共同遵守规范),需要大家达成一致。对于关键角色任命,事前也需要听取相关领导和项目主要干系人的意见。

    用户需求

    软件开始开发前需要确定代价和所获得价值的对比,也就是 ROI(Return On investment),一旦确定需要创建,就需要安排一系列的资源来支撑这个软件的生存。这是需求的最原始描述。
    为什么既要有用户需求,也要有产品需求?因为两者是有差异的,用户需求由用户提出,对技术一般不描述,只描述产品目标。产品需求是根据用户需求转化而来的技术实现需求,需要针对用户提出的产品目标进行细分,总结出具体的每一个功能点,再针对每一个功能点细分为各种不同的操作流程,对每一个操作流程进行技术化定义。
    用户需求和产品需求容易发生不一样,这是因为虽然大家都在谈需求,但是出发点可能不同,造成了双方关注点和思维方式不同。用户需求关注的是系统如何支持业务流程,背后的需求是“实现业务目标”。技术人员关注的是合理技术方案,背后的需求是“工作量”、“实现难度”和“系统性能”。

    产品需求

    我们需要弄清楚产品经理或项目需求提出者为什么要做这个项目?这是最本质的业务需求。需求分析确定的业务需求,都是从业务需求推导出来的,都必须为业务需求服务。
    产品需求一般包括产品需求规格说明书和产品需求矩阵。产品需求矩阵一般按照子系统、功能集、执行单元的结构列出所有的功能需求,每列则对应每项功能的工作步骤以及每个步骤的工作量。
    产品需求写完后,需要进行评审。在需求评审会上,产品、技术详细评审需求是否完整,产品功能的正常场景是什么?是否形成闭环?异常场景是什么?是否考虑周全?
    需求评审后,开发和测试负责人,分别编写技术方案和测试用例。技术方案评审,开发负责人拉上涉及到其他系统的负责人一起讨论,技术方案中必须要有业务流程图和时序图,业务流程图是为了梳理开发对业务的理解,是否和需求一致。时序图是了梳理本次需求涉及的系统交互。技术方案评审通过后,确认工作量和交付时间,反馈给产品。

    总体设计

    设计阶段的目标主要是对待开发系统的构架进行分析和设计,并建立系统构架的基线,以便为之后的实施工作提供一个稳定的基础。
    设计阶段包括了系统架构的输出,一个好的系统架构设计可以帮助人类梳理业务逻辑且抓住核心需求,设计稳定可扩展的业务系统,评估业务开发周期和开发成本,有效的规避风险。例如盖房子的时候得有建筑图纸,有了图纸,才能核算施工周期。
    总体设计是整个系统的框架型设计,意义及其重大,一般情况下不能省略(只有维护项目可以省略总体设计,因为基准项目已经设计完毕),所有的产品开发项目均需要首先进行总体设计,它是设计首要步骤,决不允许本末倒置,不能出现先编码后设计的情况,这是软件开发的第二大痛点(第一大是需求不明确、任意变更需求)。
    总体设计分为三个阶段:
    第一阶段:初始设计。在对给定的数据流图进行复审和精化的基础上,将其转化为初始的模块结构图。
    第二阶段:精化设计。依据模块“高内聚低耦合”的原则,精化初始的模块结构图,并设计其中的全局数据结构和每一模块的接口。
    第三阶段:设计复审阶段。对前两个阶段得到的高层软件结构进行复审,必要时还可能需要对软件结构做一些精化工作。

    概要设计

    概要设计的目的是描述系统的每个模块的内部设计,对总体设计和详细设计承担承上启下的作用。
    概要设计按照结构化设计方法进行设计。结构化设计方法的基本思路是:按照问题域,将软件逐级细化,分解为不必再分解的的模块,每个模块完成一定的功能,为一个或多个父模块服务(即接受调用),也接受一个或多个子模块的服务(即调用子模块)。模块的概念,和编程语言中的子程序或函数是对应的。
    概要设计阶段把软件按照一定的原则分解为模块层次,赋予每个模块一定的任务,并确定模块间调用关系和接口。
    在这个阶段,设计者会大致考虑并照顾模块的内部实现,但不过多纠缠于此。主要集中于划分模块、分配任务、定义调用关系。模块间的接口与传参在这个阶段要制定得十分细致明确,需要编写严谨的数据字典,避免后续设计产生不解或误解。概要设计一般不是一次就能做到位,而是反复地进行结构调整。典型的调整是合并功能重复的模块,或者进一步分解出可以复用的模块。在概要设计阶段,应最大限度地提取可以重用的模块,建立合理的结构体系,节省后续环节的工作量。
    概要设计文档最重要的部分是分层数据流图、结构图、数据字典以及相应的文字说明等。以概要设计文档为依据,各个模块的详细设计就可以并行展开了。

    详细设计

    详细设计阶段就是依据概要设计阶段的分解,设计每个模块内的算法、流程,为每个模块完成的功能进行具体的描述,要把功能描述转变为精确的、结构化的过程描述。
    详细设计这个阶段,各个模块可以分给不同的人去并行设计。设计者的工作对象是一个模块,根据概要设计赋予的局部任务和对外接口,设计并表达出模块的算法、流程、状态转换等内容。这里要注意,如果发现有结构调整(如分解出子模块等)的必要,必须返回到概要设计阶段,将调整反应到概要设计文档中,而不 能就地解决,不打招呼。详细设计文档最重要的部分是模块的流程图、状态图、局部变量及相应的文字说明等。一个模块对应一篇详细设计文档。
    概要设计阶段通常得到软件结构图,详细设计阶段常用的描述方式有:流程图、N-S 图、PAD 图、伪代码等。而详细设计的目的是描述某一个模块内部的处理流程、开发方法和编码技巧。一般来说,详细设计由项目简介、模块说明(具体说明每一个模块内部的流程、功能、逻辑、消耗以及未解决问题)、接口设计(包括内部接口和外部接口)、数据结构设计(包括物理结构和逻辑结构)、特殊处理等几个部分构成
    软件的详细设计,最终是将软件系统的各个部分的具体设计方法、逻辑、功能采用文字方式进行表述。这样在实现过程中,编码人员原则上严格按此进行代码实现即可。

    编写代码

    编写代码可以遵循以下几点原则:
    先做核心模块的压测:很多程序员,习惯把东西做完,然后等着快上线的时候才做性能测试,那么如果前面设计出了问题,这个就很头大了。当然,后期快上线的时候也要做性能测试,但前期的我认为还是很重要的。当然,做好这一点,需要懂一些业务,你要知道业务压力在哪里,业务请求的重心在哪里,很多时候,产品经理不讲,你也要问清楚。
    确保过程可控:代码执行时一定要保持中间的输出,比如说,每处理 10 万条日志,写一条状态日志,记录处理的日志条目数和当前的执行时间。
    多打日志:很多时候,代码写的自己也不是很满意,比如某个处理效率不够优化,某个处理的方法不够简洁,或者扩展性比较差,代码写的很弱智,但可能短时间没有办法想清楚最合理的解决方案,考虑到上线初期这里并不是重心所在,所以也不会特意去优化它,但这种情况下我往往会留下注释,并说明下一步优化的可能思路是什么,或者想到的可行方案是什么。
    简单易懂的逻辑:千万不要把自己绕进去了,时间一长,谁都看不明白你的逻辑。如果逻辑真的很难在一个函数内完成,尝试切分。
    不要沉迷于框架:框架最大的问题是什么?是过于繁冗的嵌套。为什么我一直很烦框架?因为经常遇到需要一秒钟几千次请求的处理场景,那么调优的时候,要从数不清的框架中寻找数据处理的逻辑,寻找性能卡点,可能改动代码只有两行,但是找问题需要两天。程序员记住,你的技术能力绝对不能被框架约束住。
    使用熟悉、成熟的技术:很多人根本没搞明白自己的障碍和问题在哪里,根本不知道相关技术产品的优势和劣势在哪里,看一堆第三方的数据测评,脑子一热,去学新技术,然后,掉进坑里出不来,如果是创业公司,可能项目就死在里面了。使用新技术前,建议全面了解该技术的特征,适用范围,以及不适用的范围。

    代码审核

    众所周知,在团队中进行代码审查(Code Review)可以提升代码质量,分享项目知识、明确责任,最终达到构建更好的软件、更好的团队。
    代码审核及其重要,一般来说每周都要做一次代码审核。首先,代码审核有利于你跟踪项目进展情况,我们能真实地看到手下的人进展如何,并且更早发现他们是否误入歧途。有时候,手下人会说“完成得差不多了!”,你去看代码时发现什么都没有或者只是一堆垃圾,诸如此类,总之离完成还很遥远。在管理中,这种情况是最让人讨厌的,所以我认为代码审查是避免这种麻烦的最佳途径。

    单元测试

    要认识单元测试,首先要明白什么是“单元(Unit)”。所谓“单元”指的是代码调用的最小单位,实际上指的是一个功能块(Function)或者方法(Method)。所以单元测试指的就是对这些代码调用单元的测试。
    单元测试是一种白盒测试,就是必须要对单元的代码细节很清楚才能做的测试。所以,单元测试的编写和执行都是由软件工程师来做的。相对于单元测试,还有集成测试。集成测试基本都是黑盒测试,主要是由测试人员根据软件的功能手册来进行测试,需要有专门的测试环境配合。集成测试又分功能测试、回归测试等。
    需要单元测试的代码实际上是开发人员自己写的逻辑,测试逻辑所依赖的环境是否正常不是单元测试的目的。在环境访问代码中引入逻辑,只会让逻辑更难测试,导致逻辑代码无法进行单元测试。因此,可单元测试的代码,才能够采用单元测试。判断可测试的代码还有一个方法,就是看这个方法能否用一个 main 函数直接运行,如果可以的话就是可单元测试的代码。可测试的代码还有另一个特征,就是该方法单元的参数,开发人员可以自由模拟,不需要依赖外部环境。

    集成测试

    集成测试,也叫组装测试或联合测试。在单元测试的基础上,将所有模块按照设计要求组装成为子系统或系统,进行集成测试。实践表明,一些模块虽然能够单独地工作,但并不能保证连接起来也能正常的工作。一些局部反映不出来的问题,在全局上很可能暴露出来。
    集成测试是在软件系统集成过程中所进行的测试,其主要目的是检查软件单位之间的借口是否正确。它根据集成测试计划 ,一边将模块或其他模块组合成越来越大的系统,一边运行该系统,以分析所组成的系统是否正确,各个组成部分是否合拍。集成测试的策略主要有自顶向下和自底向上两种。也可以理解为在软件设计单元、功能模块组装、集成为系统时,对应用系统的各个部件(软件单元、功能模块接口、链接等)进行的联合测试,以决定他们能否在一起共同工作,部件可以是代码块、独立的应用、网络上的客户端或服务器端程序。

    系统测试

    系统测试阶段包括系统测试方案及用例编写、功能性测试、性能测试、稳定性测试。
    为了验证需求分析确定的功能是否齐全并被正确实现,同时还要对安装、部署、适应性、安全性、界面等非功能性需求进行测试。系统测试也有测试人员负责,应该在需求分析完成后进行设计,在集成测试完成后进行实施。
    功能性测试一般由独立测试小组采用黑盒方式来测试,主要测试系统是否符合“需求规格说明书”。在经过以上各阶段测试确认之后,把系统完整地模拟客户环境来进行的测试。系统测试是将已经确认的软件、计算机硬件、外设、网络等其他元素结合在一起,进行信息系统的各种组装测试和确认测试,其目的是通过与系统的需求相比较,发现所开发的系统与用户需求不符或矛盾的地方,从而提出更加完善的方案。
    性能测试验证系统的稳定性和效率,检查系统是否满足规定的性能要求。性能测试通常选择一些典型的功能,检验这些功能在大量用户同时使用系统时系统是否稳定。性能测试由测试人员负责,可以在系统测试完成后进行,也可以对重要模块先进行性能测试,可以贯穿整个测试周期,目的是尽早发现系统的性能瓶颈并提早解决。
    稳定性测试和性能测试都必须等到系统基本没问题、趋于稳定时再进行才有效果,否则很难顺利测下去,出现异常也不能定位究竟是系统架构的问题,还是功能上的缺陷。
    稳定性测试(亦可称可靠性测试)通过给系统加载一定的业务压力,让系统持续运行一段时间(一般为 7x24 小时),检测系统是否能够稳定运行。

    产品发布

    产品发布是系统测试结束后的最后一步,通常在软件产品开发过程中不需要产品试制环节,可以直接上线,只需要系统测试员输出系统测试报告并批准产品发布(上线)就可以了。
    产品发布前需要通过产品发布说明会形式,对整个产品开发过程从立项开始回溯过程,指出整个过程中的不足点,总结经验,为下一个项目提供经验案例。这一会议可以通过正式会议形式召开,需要召集产品经理、主要开发人员、测试人员、上级领导等参与,准备充分,尽最大可能说清楚这个产品发布之后的效果、效益,为上线后的价值评估做准备。这一环节不可缺少,即便在互联网公司,迭代速度很快的情况下,这一环节也需要满足。

    开发过程复盘

    其实开发过程体系里并没有这一过程,但是我个人认为它非常重要。
    所有的总结,只有带着问题去思考才会有收获,这就是复盘。不论我说多少,如果没有过类似的经验,就很难有很强的共鸣。我觉得看清一个问题最好的方式,就是你曾经处在一个问题的两个不同的角色中。
    总结项目经验教训的目的,在于总结问题、分析原因,避免以后犯同样的错误,而不是追究谁的责任。
    假设一个需求理解的缺陷,如果在需求阶段发现,修改一下可能只要一个小时,但是如果到了设计完成时发现这个缺陷,因为涉及的人员、文档增多,估计要一天时间,而如果等到代码都编写完成时才发现这个缺陷,可能需要十天八天了。如果缺陷没被发现,而是直接到了生产系统中呢?这就不是工作量的问题了,估计损失就难以估计了。在质量管理的理论中,缺陷每延迟一个阶段被发现,修复的代价就要乘上十倍。