软件项目基础知识

文章目录

耦合

耦合度就是某模块(类)与其它模块(类)之间的关联、感知和依赖的程度,是衡量代码独立性的一个指标,也是软件工程设计及编码质量评价的一个标准。耦合的强度依赖于以下几个因素:

  1. 一个模块对另一个模块的调用;
  2. 一个模块向另一个模块传递的数据量;
  3. 一个模块施加到另一个模块的控制的多少;
  4. 模块之间接口的复杂程度。

耦合按从强到弱的顺序可分为以下几种类型:

  1. 内容耦合。当一个模块直接修改或操作另一个模块的数据,或者直接转入另一个模块时,就发生了内容耦合。此时,被修改的模块完全依赖于修改它的模块。类与类之间直接调用或继承关系都是属于这种耦合。
  2. 公共耦合。两个及两个以上的模块共同引用一个全局数据项就称为公共耦合。
  3. 控制耦合。一个模块在界面上传递一个信号(如开关值、标志量等)控制另一个模块,接收信号的模块的动作根据信号值进行调整,称为控制耦合。
  4. 标记耦合。模块间通过参数传递复杂的内部数据结构,称为标记耦合。此数据结构的变化将使相关的模块发生变化。
  5. 数据耦合。模块间通过参数传递基本类型的数据,称为数据耦合。
  6. 非直接耦合。模块间没有信息传递时,属于非直接耦合。

例子

下面是一个减少耦合度的例子,使用Java语言实现了一个简单的学生管理系统:

public class Student {
    private String name;
    private int age;
    private String id;

    public Student(String name, int age, String id) {
        this.name = name;
        this.age = age;
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

public interface StudentDAO {
    void add(Student student);
    void remove(String id);
    void update(Student student);
    Student get(String id);
}
public class StudentDAOImpl implements StudentDAO {
    private List<Student> studentList = new ArrayList<>();

    @Override
    public void add(Student student) {
        studentList.add(student);
    }

    @Override
    public void remove(String id) {
        Student student = get(id);
        studentList.remove(student);
    }

    @Override
    public void update(Student student) {
        Student oldStudent = get(student.getId());
        oldStudent.setName(student.getName());
        oldStudent.setAge(student.getAge());
    }

    @Override
    public Student get(String id) {
        for (Student student : studentList) {
            if (student.getId().equals(id)) {
                return student;
            }
        }
        return null;
    }
}
public interface StudentService {
    void add(Student student);
    void remove(String id);
    void update(Student student);
    Student get(String id);
}
public class StudentServiceImpl implements StudentService {
    private StudentDAO studentDAO;

    public StudentServiceImpl(StudentDAO studentDAO) {
        this.studentDAO = studentDAO;
    }

    @Override
    public void add(Student student) {
        studentDAO.add(student);
    }

    @Override
    public void remove(String id) {
        studentDAO.remove(id);
    }

    @Override
    public void update(Student student) {
        studentDAO.update(student);
    }

    @Override
    public Student get(String id) {
        return studentDAO.get(id);
    }
}

在上面的代码中,我们使用了接口和实现类的方式,将业务逻辑和数据访问逻辑分离开来,达到了减少耦合度的效果。这样,如果我们需要更改数据访问逻辑,只需要修改实现类即可,业务逻辑代码无需修改;而如果需要更改业务逻辑,只需要修改业务逻辑实现类即可,数据访问代码无需修改,这样可以方便维护和扩展。下面是一个减少耦合度的例子,使用Java语言实现了一个简单的学生管理系统:

public class Student {
    private String name;
    private int age;
    private String id;

    public Student(String name, int age, String id) {
        this.name = name;
        this.age = age;
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

public interface StudentDAO {
    void add(Student student);
    void remove(String id);
    void update(Student student);
    Student get(String id);
}
public class StudentDAOImpl implements StudentDAO {
    private List<Student> studentList = new ArrayList<>();

    @Override
    public void add(Student student) {
        studentList.add(student);
    }

    @Override
    public void remove(String id) {
        Student student = get(id);
        studentList.remove(student);
    }

    @Override
    public void update(Student student) {
        Student oldStudent = get(student.getId());
        oldStudent.setName(student.getName());
        oldStudent.setAge(student.getAge());
    }

    @Override
    public Student get(String id) {
        for (Student student : studentList) {
            if (student.getId().equals(id)) {
                return student;
            }
        }
        return null;
    }
}
public interface StudentService {
    void add(Student student);
    void remove(String id);
    void update(Student student);
    Student get(String id);
}
public class StudentServiceImpl implements StudentService {
    private StudentDAO studentDAO;

    public StudentServiceImpl(StudentDAO studentDAO) {
        this.studentDAO = studentDAO;
    }

    @Override
    public void add(Student student) {
        studentDAO.add(student);
    }

    @Override
    public void remove(String id) {
        studentDAO.remove(id);
    }

    @Override
    public void update(Student student) {
        studentDAO.update(student);
    }

    @Override
    public Student get(String id) {
        return studentDAO.get(id);
    }
}

在上面的代码中,我们使用了接口和实现类的方式,将业务逻辑和数据访问逻辑分离开来,达到了减少耦合度的效果。这样,如果我们需要更改数据访问逻辑,只需要修改实现类即可,业务逻辑代码无需修改;而如果需要更改业务逻辑,只需要修改业务逻辑实现类即可,数据访问代码无需修改,这样可以方便维护和扩展。

内聚

内聚:一个模块内部各个元素彼此结合的紧密程度。

它是衡量一个模块内部组成部分间整体统一性的度量。
内聚程度最高的是功能内聚,最差的是偶然内聚(或称巧合内聚)
常见的内聚有七类。

  1. 功能内聚Functional Cohesion
    如果一个模块内所有处理元素完成一个,而且仅完成一个功能,则称为功能内聚。
    功能内聚是最高程度的内聚。但在软件结构中,并不是每个模块都能设计成一个功能内聚模块。

  2. 顺序内聚Sequential Cohesion
    如果一个模块内处理元素和同一个功能密切相关,而且这些处理元素必须顺序执行,则称为顺序内聚。

  3. 通信内聚Communicational Cohesion
    如果一个模块中所有处理元素都使用同一个输入数据和(或)产生同一个输出数据,称为通信内聚。

  4. 过程内聚Procedural Cohesion
    如果一个模块内的处理元素是相关的,而且必须以特定的次序执行,称为过程内聚。
    过程内聚与顺序内聚的区别是: 顺序内聚中是数据流从一个处理单元流到另一个处理单元,而过程内聚是控制流从一个动作流向另一个动作。

  5. 时间内聚Temporal Cohesion
    如果一个模块包含的任务必须在同一段时间内执行,称为时间内聚。也称为瞬时内聚。

  6. 逻辑内聚Logical Cohesion
    如果模块完成的任务在逻辑上属于相同或相似的一类,称为逻辑内聚。

  7. 偶然内聚Coincidental Cohesion

例子

如果一个模块由完成若干毫无关系的功能处理元素偶然组合在一起的,就叫偶然内聚。
这里是一个简单的例子,用Java语言实现了一个购物车类:

public class ShoppingCart {
    private List<Item> items;
    private double totalPrice;

    public ShoppingCart() {
        items = new ArrayList<>();
        totalPrice = 0;
    }

    public void addItem(Item item) {
        items.add(item);
        totalPrice += item.getPrice();
    }

    public void removeItem(Item item) {
        items.remove(item);
        totalPrice -= item.getPrice();
    }

    public void clear() {
        items.clear();
        totalPrice = 0;
    }

    public double getTotalPrice() {
        return totalPrice;
    }
}

这个购物车类实现了添加、删除、清空购物车和获取购物车总价四个功能,但是我们可以看到addItem、removeItem、clear这三个方法都会修改totalPrice,这会导致代码出现紧密耦合。为了减少内聚,我们可以将计算总价的逻辑分离出来,单独写一个方法,使得这个方法只负责计算总价:

public class ShoppingCart {
    private List<Item> items;

    public ShoppingCart() {
        items = new ArrayList<>();
    }

    public void addItem(Item item) {
        items.add(item);
    }

    public void removeItem(Item item) {
        items.remove(item);
    }

    public void clear() {
        items.clear();
    }

    public double getTotalPrice() {
        double totalPrice = 0;
        for (Item item : items) {
            totalPrice += item.getPrice();
        }
        return totalPrice;
    }
}

现在,addItem、removeItem、clear方法只负责添加、删除、清空购物车的物品,而getTotalPrice方法则只负责计算购物车的总价,这样就降低了类的内聚性。

系统结构设计原则

  1. 分解——协调原则
  2. 自顶向下的原则
  3. 信息隐蔽,抽象的原则
  4. 一致性原则
  5. 明确性原则。每个模块必须功能明确,接口明确,消除多重功能和无用接口。
  6. 模块之间的耦合尽可能小,模块的内聚度尽可能高
  7. 模块的扇入系数和扇出系数要合理
  8. 模块的规模适当

系统文档

  1. 用户与系统分析人员,在系统规划,系统分析阶段通过;可行性研究报告、总体规划报告、系统开发合同和系统方案说明书。
  2. 系统开发人员与项目管理人员,用系统开发计划(包括工作任务分解表、PERT图、甘特图和预算分配表等)系统开发月报、系统开发总结报告。
  3. 系统测试人员与系统开发人员,用系统方案说明书、系统开发合同、系统设计说明书、测试计划。
  4. 系统开发人员与用户,文档用:用户手册和操作指南。
  5. 系统开发人员与系统维护人员,文档用:系统说明书和系统开发总结报告。
  6. 用户与系统维护人员,文档用:系统运行报告和维护修改建议。

数据字典

数据字典就是为数据流图中的每个数据流,文件,加工,以及组成数据或文件的数据项做出说明。

  1. 数据字典的内容
    数据字典有4类条目:数据流、数据项、数据存储和基本加工。数据项是组成数成数据流和数据存储的最小元素。原点、终点不在系统之内,故一般不在字典说明。
  2. 加工逻辑的描述
    加工逻辑也称为“小说明”.常用的加工逻辑描述方法有结构化语言,判定表和判定树3种

软件维护

软件维护一般包括四个方面:

● 正确性维护是改正在系统开发阶段已经发生而在系统测试阶段尚未发生的错误
● 适应性维护是指使用软件适应信息技术变化和管理需求变化而进行的修改。
● 完善性维护为扩充功能和改善性能而进行的修改。
● 预防性维护是为了改进应用软件的可靠性和可维护性,为了适应未来的软硬环境的变化主动增加预防性的新功能,以使应用系统适应各类变化而不被淘汰。

阶段式模型

● 初始的:过程不可预测且缺乏控制
● 已管理的:过程为项目服务
● 已定义的:过程为组织服务
● 定量管理的:过程已度量和控制
● 优化的:集中于过程改进。

CMM软件过程5个成熟度级别

  • 初始级
    软件过程的特点杂乱无章,有时很混乱。几乎没有明确的定义的步骤,项目的成功完全依赖努力和英雄式核心人物的作用。
  • 可重复级
    建立了基本的项目管理过程和实践来跟踪项目费用、进度和功能特性。
  • 已定义级
    管理和工程两个方面的软件过程已经文档化、标准化
  • 已管理级
    制定了软件过程和产品质量的详细度量标准。
  • 优化级
    加强了定量分析,通过来自过程质量反馈和新观念,新技术的反馈过程不断持续的改进。

过程模型

原型模型

  1. 适用项目类型
    适合预先不能确切定义需求的软件系统的开发
    客户能提出一般性的目标,但不能标出详细的输入处理及输出需求﹔或开发者不能确定算法的有效性﹑操作系统的适应性、及人机交互的形式
    用户定义了一组一般性目标,但不能标识出详细的输入﹑处理及输出需求
    开发者可能不能确定算法的有效性﹑操作系统的适应性或人机交互的形式

  2. 优势
    克服瀑布模型的缺点,减少由于软件需求不明确带来的开发风险。

  3. 劣势
    所选用的开发技术和工具不一定符合主流的发展;
    快速建立起来的系统结构加上连续的修改可能会导致产品质量低下
    使用这个模型的前提是要有一个展示性的产品原型,因此在一定程度上可能会限制开发人员的创新

增量模型

增量模型的最大特点就是将待开发的软件系统模块化和组件化。基于这个特点,增量模型具有以下优点。

  1. 将待开发的软件系统模块化,可以分批次地提交软件产品,使用户可以及时了解软件项目的进展。
  2. 以组件为单位进行开发降低了软件开发的风险。一个开发周期内的错误不会影响到整个软件系统。
  3. 开发顺序灵活。开发人员可以对组件的实现顺序进行优先级排序,先完成需求稳定的核心组件。当组件的优先级发生变化时,还能及时地对实现顺序进行调整。

增量模型的缺点是要求待开发的软件系统可以被模块化。如果待开发的软件系统很难被模块化,那么将会给增量开发带来很多麻烦。

增量模型适用于具有以下特征的软件开发项目:
  1. 软件产品可以分批次地进行交付。
  2. 待开发的软件系统能够被模块化。
  3. 软件开发人员对应用领域不熟悉,难以一次性地进行系统开发。
  4. 项目管理人员把握全局的水平较高。

增量模板作为瀑布模型的一个变体,具有瀑布模型的优点等

优点:

● 第一个可以交付版本所需要的成本和时间少。
● 开发由增量表示的小系统所承担的风险不大。
● 由于很快发布了第一个版本,因此可以减少用户需求的变更。
● 运行增量投资,即在项目开始时可以仅对一两个增量投资。

缺点:

● 没有变更规则,增量可造成增量不稳定。
● 早期思考不稳定和完整,则需要重新开发,重新发布。
● 管理发生的成本,进度和配置的复杂度可能超出组织的能力。

瀑布模型

优点:

容易理解,管理成本低

缺点:

客户必须能够完整,正确和清晰的表达需要的在瀑布模型中,需要或设计中的错误往往只有到了项目后期才能被发现,对于项目风险的控制能力较弱,从而导致项目常常延期完成开发费用超出预算。
在这里插入图片描述

螺旋模型

对于复杂的大型软件,开发一个原型往往达不到要求。螺旋模型将瀑布模型和演化模型结合起来,加入两种模型均忽略的分析分析,弥补了这两种模型的不足。

特点:

螺旋模型强调风险分析,该模型用于庞大,复杂并且有高风险的系统。
与瀑布模型相比,螺旋模型持用户需求的动态变化,用户参与软件开发的所有关键决策提供了方便,有助于提高软件的适应能力。
在这里插入图片描述

喷泉模型

喷泉模型是一种以用户需求为动力,以对象作为驱动的模型,适合于面向对象的开发方法。它克服了瀑布模型不支持软件重用和多项开发活动集成的局限性。喷泉模型使开发过程具有迭代性和无间性。
在这里插入图片描述

统一过程模型

统一过程模型一种“用例和风险驱动,以架构为中心,迭代并且增量”的开发过程。

正常软件项目的所有元素:计划,分析和设计,构造,集成,和测试。

  • 起始阶段
    起始阶段专注于项目的初始活动。
  • 精化阶段
    精化阶段在理解了最初的领域范围之后进行需求分析和架构演进。
  • 构建阶段
    构建阶段关注系统的构建,产生实现模型,产生的主要工作产品有设计模型等。
  • 移交阶段

移交阶段关注软件提交方面的工作,产生软件增量,在每个迭代中有5个核心工作流:

● 捕获系统因该做什么的需求工作流。
● 精化和结构化需求的设计工作流。
● 在系统结构内实现需求的事件工作流。
● 构造软件的实现工作流。
● 验证实现是否期望那样工作测试工作流。

4个 技术上阶段由主要里程碑所终止:

● 初始阶段:生命周期目标
● 精化阶段:生命周期架构
● 构建阶段:初始运作功能
● 移交阶段:产品发布

敏捷方法

敏捷方法的总体目标是通过“尽可能早地、持续地对有价值的软件的交付”使客户满意。

极限编程(XP)

4大价值观:交通、简单性、反馈和勇气
5个原则:快速反馈、简单性假设、逐步修改、提倡更改和优质工作
● 12个最佳实践:

水晶法(Crystal)

水晶法认为每一个不同的项目都需要不同的一套策略、约定和方法论。

并列争求法(Scrum)

  • 并列争求法使用迭代的方法,其中,把每30天一次的迭代称为一个“冲刺”,并按照的优先级别来实现产品。 自适应软件开发(ASD)了解

敏捷统一过程

采用“大型上连续”以及在“在小型上迭代”的原理来构建软件系统。采用经典的UP阶段性活动(初始、精化、构建、转换)在每个活动里,一个团队迭代使用敏捷,并将有意义的软件增量可能快的交付给最终用户。

每个AUP迭代执行以下活动:
● 建模
● 实现
● 测试
● 部署
● 配置及项目管理
● 环境管理

软件需求

  • 功能需求
  • 性能需求
  • 用户或人的因素
  • 环境需求
  • 界面需求
  • 文档需求
  • 数据需求
  • 资源使用需求
  • 安全保密需求
  • 可靠性要求
  • 软件成本消耗与开发进度需求
  • 其他非功能性要求

系统设计

  • 设计软件系统总体结构
    其本任务是采用某种设计方法,将一个复杂的系统按功能划分模块;确定每个模块的功能;确定模块之间的调用关系;确定模块之间的接口,即模块之间传递的信息;评价模块结构的质量。
  • 数据结构及数据库设计*
    数据结构的设计
    数据库的设计
  • 编写概要设计文档
    文档主要有概要设计说明书,数据库设计说明书,用户手册以及修订测试计划。
  • 评审*

详细设计

  • 对每个模块进行详细的算法设计,用某种图形,表格和语言等工具将每个模块处理过程的详细算法描述出来。
  • 对模块内的数据结构进行设计
  • 对数据库进行物理设计,即确定数据库的物理结构
  • 其他设计
  • 编写详细设计说明书
  • 评审

系统测试

系统测试的意义、目的及原则:

● 意义:系统测试是为了发现错误而执行程序的过程,成功的测试是发现了至今尚未发现的错误的测试。
● 测试的目的,就是希望能以最少的人力和时间发现潜在的各种错误和期限。

在进行信息测试时应遵循以下基本原则:

  • 应尽早并不断地进行测试
  • 测试工作应该避免由原开发软件的人或小组承担。
  • 在设计测试方案时,不仅要确定输入数据,而且要根据系统功能确定预期输出结果。
  • 在设计测试用例时,不仅要设计有效,合理的输入条件,也要包含不合理、失效的输入条件。
  • 在测试程序时,不仅要检验程序是否做了该做的事,还是检验程序是否了不该做的事
  • 严格按照测试计划来进行,避免测试的随意性。
  • 妥善保存测试计划,测试用例,作为软件文档的组成部分,为维护提供方便。
  • 测试例子都是精心设计出来的,可以重新测试或最追加测试提供方便。当纠正错误,系统功能扩充后,都需要重新开始测试。

集成测试

自顶向下集成测试

  • (增量方法) 不用编写驱动模块 需要编写桩模块

自底向上集成测试

  • 编写驱动模块 不需要写桩模块

测试方法

软件测试方法分为静态测试和动态测试

  • 静态测试静态测试是被测试程序不在机器上运行,而是采用人工检测和计算机辅助静态分析的手段对程序进行检测。
  • 动态测试,动态测试是指通过运行程序发现错误,动态测试有黑盒测试法,白盒测试法。

例子

动态测试

以下是一个简单的Java动态测试示例,使用JUnit框架:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class DynamicTest {

    @TestFactory
    public Stream<DynamicNode> testMultiply() {
        List<Integer> inputs = Arrays.asList(1, 2, 3);
        List<Integer> expectedOutputs = Arrays.asList(10, 20, 30);

        // Creating dynamic tests for each input and expected output
        return inputs.stream().map(input -> {
            String testName = "multiplyByTen(" + input + ")";
            Executable executable = () -> assertEquals(expectedOutputs.get(input - 1), multiplyByTen(input));
            return DynamicTest.dynamicTest(testName, executable);
        });
    }

    private int multiplyByTen(int num) {
        return num * 10;
    }
}

在上述示例中,我们创建了一个名为testMultiply的动态测试工厂,该工厂生产多个动态测试。我们使用了inputsexpectedOutputs两个列表来存储输入和预期输出。

testMultiply方法中,我们使用Java流来遍历每个输入值,并为每个输入值创建一个名为testName的动态测试名称。然后,我们为每个测试创建一个执行体executable,该执行体会调用multiplyByTen方法并验证结果是否等于预期输出。最后,我们使用DynamicTest.dynamicTest静态方法来创建动态测试,并将其作为流返回。

这样做的好处是,我们可以轻松地添加、删除或修改测试用例,而不需要每次手动编写单独的测试用例方法。以下是一个简单的Java动态测试示例,使用JUnit框架:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class DynamicTest {

    @TestFactory
    public Stream<DynamicNode> testMultiply() {
        List<Integer> inputs = Arrays.asList(1, 2, 3);
        List<Integer> expectedOutputs = Arrays.asList(10, 20, 30);

        // Creating dynamic tests for each input and expected output
        return inputs.stream().map(input -> {
            String testName = "multiplyByTen(" + input + ")";
            Executable executable = () -> assertEquals(expectedOutputs.get(input - 1), multiplyByTen(input));
            return DynamicTest.dynamicTest(testName, executable);
        });
    }

    private int multiplyByTen(int num) {
        return num * 10;
    }
}

在上述示例中,我们创建了一个名为testMultiply的动态测试工厂,该工厂生产多个动态测试。我们使用了inputsexpectedOutputs两个列表来存储输入和预期输出。

testMultiply方法中,我们使用Java流来遍历每个输入值,并为每个输入值创建一个名为testName的动态测试名称。然后,我们为每个测试创建一个执行体executable,该执行体会调用multiplyByTen方法并验证结果是否等于预期输出。最后,我们使用DynamicTest.dynamicTest静态方法来创建动态测试,并将其作为流返回。

这样做的好处是,我们可以轻松地添加、删除或修改测试用例,而不需要每次手动编写单独的测试用例方法。

静态测试

下面是一个简单的静态测试的Java代码:

public class StaticTest {
    static int num1 = 5;
    static int num2 = 10;
    
    public static void main(String[] args) {
        System.out.println("num1 = " + num1);
        System.out.println("num2 = " + num2);
        
        int sum = addNumbers(num1, num2);
        System.out.println("The sum of num1 and num2 is " + sum);
    }
    
    public static int addNumbers(int a, int b) {
        return a + b;
    }
}

这个程序定义了一个带有两个静态变量的类和一个静态方法。在main方法中,我们打印出了静态变量num1和num2的值,然后调用静态方法addNumbers来计算它们的总和,并将结果打印到控制台上。这个程序演示了如何使用静态变量和静态方法来处理数据并返回结果。

白盒测试

白盒测试是基于代码的测试,测试人员可以访问源代码,了解程序的内部结构和工作方式。根据程序的逻辑和函数之间的关系,测试人员可以设计测试用例,并使用代码覆盖率工具来评估测试的完整性。

  • 白盒测试可以帮助测试人员发现代码错误、缺陷、死代码、性能问题和其他安全漏洞等问题,从而提高软件的质量和稳定性。

  • 语句覆盖(Statement Coverage):测试用例覆盖程序中的每一个语句,确保每一条语句都至少执行了一次。

  • 判定覆盖(Decision Coverage):测试用例覆盖程序中每一个判定的真假两种情况,即每个条件和循环至少执行一次和没有执行的情况。

  • 条件覆盖(Condition Coverage):测试用例覆盖每一个判定中的每一个条件,包括复合条件和逻辑关系,确保每个条件都至少执行一次。

  • 判定/条件组合覆盖(Decision/Condition Coverage):测试用例覆盖程序中每一个判定的真假两种情况以及每个判定中的每一个条件。

  • 路径覆盖(Path Coverage):测试用例覆盖程序中每一个可能的路径,确保每个路径都至少执行一次。

  • 分支/条件组合覆盖(Branch/Condition Combination Coverage):测试用例覆盖程序中每一个可能的分支路径,包括判定中的条件组合以及循环中的初始、循环、结束执行情况,确保每个分支路径都至少执行一次。

例子

下面是一个用Java语言编写的简单白盒测试:

假设我们要测试一个计算器类,在其中实现加、减、乘、除四种运算方法。

测试代码如下:

import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class CalculatorTest {

    @Test
    public void testAdd() {
        Calculator calculator = new Calculator();
        int result = calculator.add(5, 3);
        assertEquals(8, result);
    }

    @Test
    public void testSubtract() {
        Calculator calculator = new Calculator();
        int result = calculator.subtract(5, 3);
        assertEquals(2, result);
    }

    @Test
    public void testMultiply() {
        Calculator calculator = new Calculator();
        int result = calculator.multiply(5, 3);
        assertEquals(15, result);
    }

    @Test
    public void testDivide() {
        Calculator calculator = new Calculator();
        int result = calculator.divide(6, 3);
        assertEquals(2, result);
        // 测试除数为0的情况
        try {
            calculator.divide(6, 0);
        } catch (ArithmeticException e) {
            assertEquals("/ by zero", e.getMessage());
        }
    }
}

上面的测试代码使用JUnit框架进行测试。其中,每个测试方法都实例化了一个计算器类,并调用该类的相应方法进行运算,并且对结果进行判断,判断是否与期望结果相等。其中,最后一个测试方法还测试了除数为0的情况。在这个测试方法中,我们使用了try-catch语句来捕获异常,并且判断异常的信息是否符合预期。

这个测试代码并不完整,我们还可以通过增加更多的测试方法,来测试不同的情况,以确保程序的质量。下面是一个用Java语言编写的简单白盒测试:

假设我们要测试一个计算器类,在其中实现加、减、乘、除四种运算方法。

测试代码如下:

import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class CalculatorTest {

    @Test
    public void testAdd() {
        Calculator calculator = new Calculator();
        int result = calculator.add(5, 3);
        assertEquals(8, result);
    }

    @Test
    public void testSubtract() {
        Calculator calculator = new Calculator();
        int result = calculator.subtract(5, 3);
        assertEquals(2, result);
    }

    @Test
    public void testMultiply() {
        Calculator calculator = new Calculator();
        int result = calculator.multiply(5, 3);
        assertEquals(15, result);
    }

    @Test
    public void testDivide() {
        Calculator calculator = new Calculator();
        int result = calculator.divide(6, 3);
        assertEquals(2, result);
        // 测试除数为0的情况
        try {
            calculator.divide(6, 0);
        } catch (ArithmeticException e) {
            assertEquals("/ by zero", e.getMessage());
        }
    }
}

上面的测试代码使用JUnit框架进行测试。其中,每个测试方法都实例化了一个计算器类,并调用该类的相应方法进行运算,并且对结果进行判断,判断是否与期望结果相等。其中,最后一个测试方法还测试了除数为0的情况。在这个测试方法中,我们使用了try-catch语句来捕获异常,并且判断异常的信息是否符合预期。

这个测试代码并不完整,我们还可以通过增加更多的测试方法,来测试不同的情况,以确保程序的质量。

语句覆盖例子

假设有以下方法,目的是将一个数组元素全部设置为0:

public static void setZero(int[] array) {
    for (int i = 0; i < array.length; i++) {
        array[i] = 0;
    }
}

以下是一个简单的语句覆盖的测试例子:

@Test
public void testSetZero() {
    int[] array = {1, 2, 3, 4, 5};
    setZero(array);
    for (int i = 0; i < array.length; i++) {
        assertEquals(0, array[i]);
    }
}

这个测试方法使用了一个长度为5的数组作为测试数据,然后调用 setZero 方法将其所有元素设置为0。最后再遍历一遍数组,确保所有元素都已正确设置为0。这个测试覆盖了所有的代码语句。

判定覆盖例子

假设有一个方法需要测试,它的代码如下:

public static boolean checkAge(int age){
    if(age > 18){
        return true;
    }else{
        return false;
    }
}

为了进行判定覆盖的测试,我们首先需要找出该方法中的判定语句。在上面的代码中,判定语句为 if(age > 18)

接下来,我们需要设计测试用例来覆盖这个判定语句的所有分支。

  1. 测试用例1:age > 18
int age = 19;
assertTrue(checkAge(age));
  1. 测试用例2:age = 18
int age = 18;
assertFalse(checkAge(age));
  1. 测试用例3:age < 18
int age = 17;
assertFalse(checkAge(age));

这三个测试用例可以覆盖该方法中的所有分支,从而实现了判定覆盖。

条件覆盖例子

假设要测试一个名为Calculator的类中的add方法,我们可以编写以下条件覆盖的测试:

import org.junit.Test;
import static org.junit.Assert.*;

public class CalculatorTest {
    
    private Calculator calculator = new Calculator();
    
    @Test
    public void testAdd() {
        // Test case 1: add two positive numbers
        assertEquals(calculator.add(2, 3), 5);
        
        // Test case 2: add two negative numbers
        assertEquals(calculator.add(-2, -3), -5);
        
        // Test case 3: add a positive and a negative number
        assertEquals(calculator.add(2, -3), -1);
        
        // Test case 4: add zero to a number
        assertEquals(calculator.add(0, 5), 5);
        
        // Test case 5: add a number to itself
        int num = 2;
        assertEquals(calculator.add(num, num), 4);
    }
}

在这个例子中,我们考虑了不同的输入和预期输出,以测试add方法的各种条件。通过这些测试,我们可以保证add方法在不同的情况下都能按照预期工作。

黑盒测试

黑盒测试完全不考虑软件的内部结构和特性的情况下,测试软件的外部特性:

  • 等价类划分,等价类划分有两种不同的情况,有效等价类和无效等价类。
  • 边界值分析。边界值划分选择等价边界的测试用例。即注重输入条件边界,又适用于输出测试用例
  • 错误推测
  • 因果图。

例子

假设有一个简单的计算器程序,提供基本的加、减、乘、除运算。我们可以编写以下黑盒测试来测试程序是否按照预期工作。

import org.junit.Test;

import static org.junit.Assert.*;

public class CalculatorTest {

    @Test
    public void testAddition() {
        Calculator calculator = new Calculator();
        assertEquals(4, calculator.addition(2, 2));
        assertEquals(-4, calculator.addition(-2, -2));
        assertEquals(0, calculator.addition(0, 0));
    }

    @Test
    public void testSubtraction() {
        Calculator calculator = new Calculator();
        assertEquals(0, calculator.subtraction(2, 2));
        assertEquals(-2, calculator.subtraction(-2, 0));
        assertEquals(10, calculator.subtraction(20, 10));
    }

    @Test
    public void testMultiplication() {
        Calculator calculator = new Calculator();
        assertEquals(0, calculator.multiplication(0, 0));
        assertEquals(10, calculator.multiplication(5, 2));
        assertEquals(-6, calculator.multiplication(2, -3));
    }

    @Test
    public void testDivision() {
        Calculator calculator = new Calculator();
        assertEquals(2, calculator.division(4, 2));
        assertEquals(-2, calculator.division(4, -2));
        assertEquals(0, calculator.division(0, 10));
        assertEquals(Double.POSITIVE_INFINITY, calculator.division(10, 0), 0.0001);
    }
}

在这个测试中,我们分别测试了加、减、乘、除四种运算。每个测试用例包括输入两个参数并检查程序的输出是否与预期相符。例如,在testAddition()测试中,我们输入2和2进行加法运算,预期得到4作为结果。

如果每个测试用例都能够成功执行并返回预期结果,那么我们可以相对确定地说,程序在各种情况下都按照预期工作。假设有一个简单的计算器程序,提供基本的加、减、乘、除运算。我们可以编写以下黑盒测试来测试程序是否按照预期工作。

import org.junit.Test;

import static org.junit.Assert.*;

public class CalculatorTest {

    @Test
    public void testAddition() {
        Calculator calculator = new Calculator();
        assertEquals(4, calculator.addition(2, 2));
        assertEquals(-4, calculator.addition(-2, -2));
        assertEquals(0, calculator.addition(0, 0));
    }

    @Test
    public void testSubtraction() {
        Calculator calculator = new Calculator();
        assertEquals(0, calculator.subtraction(2, 2));
        assertEquals(-2, calculator.subtraction(-2, 0));
        assertEquals(10, calculator.subtraction(20, 10));
    }

    @Test
    public void testMultiplication() {
        Calculator calculator = new Calculator();
        assertEquals(0, calculator.multiplication(0, 0));
        assertEquals(10, calculator.multiplication(5, 2));
        assertEquals(-6, calculator.multiplication(2, -3));
    }

    @Test
    public void testDivision() {
        Calculator calculator = new Calculator();
        assertEquals(2, calculator.division(4, 2));
        assertEquals(-2, calculator.division(4, -2));
        assertEquals(0, calculator.division(0, 10));
        assertEquals(Double.POSITIVE_INFINITY, calculator.division(10, 0), 0.0001);
    }
}

在这个测试中,我们分别测试了加、减、乘、除四种运算。每个测试用例包括输入两个参数并检查程序的输出是否与预期相符。例如,在testAddition()测试中,我们输入2和2进行加法运算,预期得到4作为结果。

如果每个测试用例都能够成功执行并返回预期结果,那么我们可以相对确定地说,程序在各种情况下都按照预期工作。

McCabe度量法

McCabe概念:

McCabe度量法是一种基于程序控制流的复杂性度量方法。
McCabe复杂性度量又称环路度量,其计算公式为: V(g)=m-n+2,其中m和n分别代表图中的边数和顶点数。

系统可维护性

软件维护是软件生命周期中的最后的一个阶段,处于系统投入生产性运行后的时期中,因此不属于系统开发。

  • 系统维护性的评价指标
    ● 可理解性
    ● 可测试性
    ● 可修改性
  • 维护于软件文档
    ● 编写高质量文档可以提高软件开发的质量
    ● 文档也是软件产品的一部分,没有文档的软件就不能称之为软件
    ● 软件文档的编制的编制在软件开发工作中占有突出的地位和先当大的工作量高质量文档对于软件产品的效益有着重要的意义。
    ● 总的来说,软件文档只好不坏,选项中说软件文档不好的就是不正确的。

软件维护

软件维护几个方面:

  • 正确性维护:指正在系统开发阶段已发生而系统测试阶段尚未发现的错误
  • 适应性维护:指使应用软件信息技术变化和管理需求变化而进行的修改。
  • 完善性维护:这是扩充功能和改善性能而进行的修改,主要是指对已有的软件系统增加一些在系统分析和设计阶段中没有规定的功能与性能特征。
  • 预防性维护:为了改进应用软件的可靠性和可维护性,为了适应未来的软/硬环境的变化,应主动增加预防性的新的功能,以使应用系统适合各类的变化而不被淘汰。

软件可靠性,可用性,可维护性

  • 可靠性:MTTF/(1+MTTF)

  • 可用性:MTBF/(1+MTBF)

  • 可维护性:1/(1+MTTR)

标准化和知识产权

著作权

著作权,著作权(也称版权),是指作者对其创作的作品享有的人身权和财产权。人身权包括:发表权,署名权,修改权,和保护作品完整权等。
发表权有时间性,终身十年死后50年
著作权有地域权,只受申请专利的国家有这权力(受保护),没有申请专利的不受保护。

计算机软件著作权

计算机软件著作权的主体是指享有著作权的人,根据**《中国人民共和国著作权法》和《计算机软件保护条例》**,著作权法保护的计算机软件是指计算机程序以及其有关文档。
计算机程序:包括源程序和目标程序
计算机软件的文档:程序设计说明书,流程图,用户手册等。

著作权的权力

**《中华人民共和国著作权法》规定,软件作品享有两类权利,一类是软件著作权的人生权(精神权利);另一类是软件著作权的财产权(经济权利)。《计算机软件保护条例》**规定,软件著作权人享有发表权和开发者身份权(署名权)。
署名权:不随着时间软件开发者的消失而丧失,且无时间限制。

著作权的归属

职务软件作品是作为公民在单位任职期间执行单位工作任务开发的计算机软件作品。署名权:作者所有著作权公司所有。

非职务软件作品:

所开发的软件作品不是执行其本职工作的结果。
开发的软件作品与开发者在单位中从事的工作无直接联系。
开发的软件作品未使用单位的物质技术和技术条件。

委托开发的软件著作权归属

关于委托开发软件著作权的归属,《计算机软件保护条例》第十一条规定:“接受他人委托开发的软件,其著作的归属由委托者与受委托者签订书面合同规定;无书面或合同或者合同未明确规定的,其著作权由受委托人享有。

商业秘密权

定义:**《反不正当竞争》**中商业秘密定义为:“指不为公众所知悉的,能为权利人带来经济利益、具有实用性并经权利人采取保密措施的技术信息和经营信息”

专利的申请

两个或者两个以上人分别就同样的发明创造申请专利,专利授权给最先申请的人。
如果同时申请同样的专利,双方协商确定。

商标权

商标权的时间性。 我国商标权的保护期限自注册之日起10年内有效,但可以根据其所有人的需要无限地延长权力期限,在期限届满前6个月内申请续展注册,每次续展注册的有效期为10年,续展注册的次数不限。

商标注册

谁先注册归谁,若同一天注册看谁先使用归谁。
若同一天使用或没使用,双方协商确定。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值