首页 > 其他分享 >代码评审,揭示黑盒背后的真相

代码评审,揭示黑盒背后的真相

时间:2023-08-03 21:33:18浏览次数:33  
标签:黑盒 String 真相 代码 System 评审 println public out

一、引言

黑盒测试犹如案发现场,只能根据表象推断事件经过。

代码评审即深入调查,挖掘蛛丝马迹的线索,揭示背后的真相。

"They think I am hiding in the shadows, but I am the shadows."

二、黑盒测试与白盒测试的区别

黑盒测试存在一些局限性:

  • 可能无法发现与系统实现相关的问题

  • 可能无法覆盖所有的测试场景

  • 测试效率较低,比如准备物料、模拟场景

  • 强依赖需求文档,如果文档不全,测试会漏

对于测试人员来说,可以在代码评审阶段,通过白盒测试改进测试的质量和效率。

三、代码评审的定义和意义

代码评审,Code Review(CR),是一种通过检查代码来提高代码质量的过程。

对于测试人员来说,参与代码评审,可以尽量提前发现问题,减少修复代价,提高效能。

四、代码评审的形式

多人讨论

组织会议,研发牵头讲解代码,架构和测试参与,讨论交流。这是最普遍的一种形式。

Code Diff

查看Code Diff,可以借助Gitlab或IDEA,比较分支差异或版本差异。

对比时机:

  • 提测前和测试中,自行走查代码

  • 发现缺陷,定位代码原因

  • 修复缺陷后,评估影响范围

  • 上线前,是否夹带代码

精准测试

评估测试用例的代码覆盖率,查漏补缺,Jacoco的on-the-fly模式支持动态收集代码覆盖率数据。

五、代码评审的方法

面向业务,面向业务,面向业务。重要的事情说三遍。

刚开始做代码评审,很容易把注意力集中在找代码规范问题上面,比如命名不规范、注释不清楚、代码实现冗长等。这些问题不是测试人员关注的重点,需要由研发团队或代码扫描工具来解决。

在做Code Diff时,也没必要把每个文件、每行代码的意思搞懂,比如研发对代码结构做了调整,在diff时要梳理清楚的话,ROI会非常低,因为既消耗时间,又发现不了问题。

那该怎么做代码评审呢? 关注业务:

  1. 跟需求文档比较,哪些需求是遗漏的,哪些代码是补充的,哪些代码是夹带的

  2. 关注核心业务代码逻辑,使用条件覆盖、路径覆盖等方法设计测试用例

  3. 优化测试用例,针对代码实现考虑异常、边界、幂等、并发等场景

代码评审要求测试人员具备代码能力,理解编程语言,掌握软件设计,熟悉代码结构和架构,多与开发同学交流,共同优化代码质量。

六、代码评审的实际案例

1、空指针异常

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    private final SqlSessionTemplate sqlSessionTemplate;
    
    @Autowired
    public UserService(SqlSessionTemplate sqlSessionTemplate) {
        this.sqlSessionTemplate = sqlSessionTemplate;
    }
    
    public void getUserById(String userId) {
        User user = sqlSessionTemplate.selectOne("com.example.mapper.UserMapper.getUserById", userId);
        System.out.println(user.getName()); // 可能导致空指针异常
    }
}

如果取到的 user 对象为空,就会导致空指针异常。

2、String类型判空用StringUtils.isBlank(),Collection类判空用CollectionUtils.isEmpty()

import org.apache.commons.lang3.StringUtils;

public class ExampleStringUtils {

    public static void main(String[] args) {
        String str1 = "";
        String str2 = null;
        String str3 = "   ";

        if (StringUtils.isBlank(str1)) {
            System.out.println("str1 is blank or null");
        }

        if (StringUtils.isBlank(str2)) {
            System.out.println("str2 is blank or null");
        }

        if (StringUtils.isBlank(str3)) {
            System.out.println("str3 is blank or null");
        }
    }
}
import org.apache.commons.collections4.CollectionUtils;

import java.util.ArrayList;
import java.util.List;

public class ExampleCollectionUtils {

    public static void main(String[] args) {
        List<String> list1 = new ArrayList<>();
        List<String> list2 = null;

        if (CollectionUtils.isEmpty(list1)) {
            System.out.println("list1 is empty or null");
        }

        if (CollectionUtils.isEmpty(list2)) {
            System.out.println("list2 is empty or null");
        }
    }
}

3、写操作的事务一致性

@Service
public class UserService {

    private final UserMapper userMapper;
    private final AccountMapper accountMapper;

    // 省略构造方法

    public void addUserAndDeductBalance(User user, double amount) {
        try {
            userMapper.insertUser(user); // 插入用户信息
            accountMapper.deductBalance(user.getAccountId(), amount); // 扣除账户余额

            // 其他写操作...
        } catch (Exception e) {
            // 处理异常
        }
    }
}

没有使用 @Transactional,不会进行事务管理和回滚,如果执行accountMapper.deductBalance()时异常,那么已经执行的 userMapper.insertUser() 操作无法回滚,用户信息被插入但账户余额未扣除,导致数据的不一致性。

4、根据判断条件补充用例

public class ECommerceSystem {
    public static void main(String[] args) {
        String productCategory = "electronics";
        float productPrice = 999.99f;
        int userPoints = 100;

        if ("electronics".equals(productCategory)) {
            if (productPrice > 1000) {
                if (userPoints > 50) {
                    applyDiscount(0.2f);
                } else {
                    applyDiscount(0.1f);
                }
            } else {
                if (userPoints > 100) {
                    applyDiscount(0.15f);
                } else {
                    applyDiscount(0.05f);
                }
            }
        } else if ("clothing".equals(productCategory)) {
            if (productPrice > 500) {
                if (userPoints > 100) {
                    applyDiscount(0.3f);
                } else {
                    applyDiscount(0.2f);
                }
            } else {
                if (userPoints > 50) {
                    applyDiscount(0.1f);
                } else {
                    applyDiscount(0.05f);
                }
            }
        } else {
            if (productPrice > 100) {
                if (userPoints > 10) {
                    applyDiscount(0.1f);
                } else {
                    applyDiscount(0.05f);
                }
            }
        }
    }

    private static void applyDiscount(float discount) {
        System.out.println("Applying discount of " + discount * 100 + "%");
        // 执行折扣逻辑
    }
}

复杂的判断条件,文档很可能描述不全所有场景,需要针对代码实现,补充测试用例。

5、代码放在不同位置,影响范围变小

public class ShoppingCart {
    private List<Product> products = new ArrayList<>();

    public void addToCart(Product product) {
        products.add(product);
        updateCartTotal();
    }

    public void removeFromCart(Product product) {
        products.remove(product);
        updateCartTotal();
    }

    private void updateCartTotal() {
        float total = 0;
        for (Product product : products) {
            total += product.getPrice();
        }
        System.out.println("Cart Total: " + total);
    }
}

public class Product {
    private String name;
    private float price;

    // constructor, getters and setters

    public float getPrice() {
        return price;
    }
}

如果需要更改计算总金额的逻辑,只需修改 updateCartTotal() 方法即可,而不需要修改调用该方法的其他部分代码,测试点更少,影响面更小。

6、for循环性能优化

public class PerformanceOptimization {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        
        // 原始的 for 循环
        long startTime = System.nanoTime();
        for (int i = 0; i < numbers.size(); i++) {
            int number = numbers.get(i);
            System.out.println(number);
        }
        long endTime = System.nanoTime();
        long elapsedTime = endTime - startTime;
        System.out.println("原始 for 循环耗时: " + elapsedTime + " 纳秒");

        // 使用增强 for 循环
        startTime = System.nanoTime();
        for (int number : numbers) {
            System.out.println(number);
        }
        endTime = System.nanoTime();
        elapsedTime = endTime - startTime;
        System.out.println("增强 for 循环耗时: " + elapsedTime + " 纳秒");
    }
}

如果for循环里面接口调用或计算量大,可能会导致性能问题。

7、finally块的return覆盖try-catch块中的return

public class ReturnInFinally {
    public static void main(String[] args) {
        System.out.println(testMethod());
    }
    
    public static int testMethod() {
        try {
            System.out.println("Inside try block");
            return 1;
        } catch (Exception ex) {
            System.out.println("Inside catch block");
            return 2;
        } finally {
            System.out.println("Inside finally block");
            return 3;
        }
    }
}

如果确实需要在 finally 块中执行一些清理或资源释放操作,并希望保留 try-catch 块中的返回结果,可以将返回值存储在一个变量中,在 finally 块之后再进行返回。

8、多表同时更新,使用分布式事务

try {
    // 开启分布式事务
    beginDistributedTransaction();
    
    // 执行事务操作1
    updateTable1();
    
    // 执行事务操作2
    updateTable2();
    
    // 执行事务操作3
    updateTable3();
    
    // 提交分布式事务
    commitDistributedTransaction();
} catch (Exception e) {
    // 回滚分布式事务
    rollbackDistributedTransaction();
    // 处理异常
    handleException(e);
}

假设有两个服务,一个是订单服务,负责处理用户下单和创建订单;另一个是库存服务,负责管理商品的库存数量。当用户下单时,订单服务需要创建订单并扣减对应商品的库存。可能会出现数据不一致:在订单服务创建订单之后,库存服务还未扣减库存的情况下发生了故障,导致订单已经创建但库存没有被正确扣减。这会导致订单和库存之间的数据不一致。如果只是简单地依次执行两个操作,无法保证它们的原子性。

9、幂等

public class OrderService {
    public String createOrder(OrderData orderData) {
        // 生成订单号
        String orderId = generateOrderId();

        // 检查订单是否已经存在
        if (!isOrderExist(orderId)) {
            // 创建订单
            saveOrder(orderId, orderData);

            // 扣减库存
            decreaseInventory(orderData);

            return "订单创建成功";
        } else {
            return "订单已存在";
        }
    }

    private String generateOrderId() {
        // 省略具体实现
        return "123456789";
    }

    private boolean isOrderExist(String orderId) {
        // 省略具体实现
        return false;
    }

    private void saveOrder(String orderId, OrderData orderData) {
        // 省略具体实现
    }

    private void decreaseInventory(OrderData orderData) {
        // 省略具体实现
    }
}

如果发起重复请求,上个请求还未处理完,可能会重复创建相同订单。考虑使用分布式锁来保证接口的幂等性。

10、执行频率高的代码日志,增加级别判断

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyClass {
    private static final Logger logger = LoggerFactory.getLogger(MyClass.class);

    public void myMethod() {
        // 判断日志级别是否为 INFO
        if (logger.isInfoEnabled()) {
            String message = "This is an info message.";
            logger.info(message);
        }
    }
}

11、枚举类

public enum InvoiceStatus {
    PENDING("待处理"),
    APPROVED("已批准"),
    REJECTED("已拒绝"),
    CANCELLED("已取消"),
    PAID("已支付");
    // 省略定义
}

如果筛选几个枚举作为状态判断,可能不准确。

12、更多业务类案例:

  • 代码未找到需求相关实现,参考需求文档

  • 上下游接口字段未对齐,参考接口文档

  • 修改了公共方法,回归范围扩大

  • 修改了方法A1,未修改方法A2,A1和A2是不同入口,都需要修改

七、总结

从业务需求角度出发,剖析代码逻辑,运用测试经验,以更高的效率,发现更多的缺陷,这就是代码评审带来的烧脑体验。

标签:黑盒,String,真相,代码,System,评审,println,public,out
From: https://www.cnblogs.com/df888/p/17602102.html

相关文章

  • 《软件测试的艺术》原书第三版 - 第三章 - 代码检查、走查与评审
    第三章代码检查、走查与评审发现了一句有趣的话:从内部产生的压力似乎会急剧增长,并产生一个趋势,要“尽可能快地修正这个缺陷”。由于这些压力的存在,程序员在改正某个由基于计算机测试发现的错误时所犯的失误,要比改正早期发现的问题时所犯的失误更多一些。太紧张了?代码检查......
  • 【软件测试】黑盒及白盒的测试方法
    黑盒测试方法等价类划分法、边界值分析法、因果图法、场景法、正交实验设计法、判定表驱动分析法、错误推测法、功能图分析法、状态迁移等价类划分方法使用有代表性的数据来测试程序;着重考虑输入条件把所有可能的输入数据,即程序的输入域划分成若干部分(子集),然后从每......
  • 【质量保证】测试的托底保障环节:发布评审
    众所周知~~~测试的核心工作是:质量保证BUT,都说一个巴掌拍不响,一个人也顶不了天所以为了把大家(我们所可恶的项目、产品、研发)拖下水,测试的生命周期中一个重要的环节诞生了,那就是:发布评审最近看了一本书,《清单革命》,贼赞,有点迷,所以咱以清单的形式来阐述下发布评审中,都能干点啥???不......
  • 《产品发展的路标是客户需求导向 企业管理的目标是流程化的组织建设》-- 任正非在PERB
    《产品发展的路标是客户需求导向企业管理的目标是流程化的组织建设》--任正非在PERB产品路标规划评审会议上的讲话2003年5月26日【导读】流程的核心是要反映业务的本质。流程承载业务,业务在流程上跑,沿着流程进行业务管理,由此,组织也必须与业务和流程进行......
  • 超强阵容!HarmonyOS极客马拉松2023专家评审团来袭!
     数十位重量级专家现身决赛现场,为参赛者提供多角度专业点评。12支队伍,46位选手,齐聚东莞·松山湖,围绕HarmonyOS技术特性,共同挑战36小时极限编程,谁将问鼎决赛之巅,8.3日-5日,我们拭目以待! ......
  • 30天自制操作系统与操作系统真相还原书籍+源代码整理免费下载
    文件列表1.操作系统真相还原.7zhttps://lphco.lanzouj.com/icDqH12wwxsj密钥:a2dy2.30天自制操作系统.7zhttps://lphco.lanzouj.com/iLfDq12wx0od密钥:7jy93.30天自制操作系统压缩包自解压程序.exe防止没有7ziphttps://lphco.lanzouj.com/ilBju12wx2ub密钥:7sap4.操......
  • 36白盒测试和黑盒测试
    白盒测试和黑盒测试都是动态测试,都要运行程序。白盒测试是结构测试主要用于单元测试。黑盒测试是功能测试用于集成测试、确认测试和系统测试阶段。白盒测试有:控制流测试:语句覆盖、路径覆盖(路径覆盖最强、语句覆盖最弱)数据流测试程序变异测试黑盒测试有:等价类划分,又分有效......
  • DevOps | 产研协同效能提升之评审、审批流、质量卡点
    研发过程中有各种需求的评审、审批流和质量卡点,有的是为了质量把关,有的是为了彰显权力,还有一些是为了信息告知。本文主要讨论在软件开发过程中涉及的评审、审批和质量卡点三种情况,同时探讨对研发流程的影响,在这过程中如何去提效。 同团队内部评审同团队之间的评审包括产品团......
  • 视觉检测系统不丢帧背后的真相——10G高速图像采集卡
    中国机器视觉起步于80年代的技术引进,一直到2011年,市场开始高速增长,随着人工成本的增加和制造业的升级需求,加上计算机视觉技术的快速发展,越来越多机器视觉方案应用于各领域。参差不齐的视觉系统导致机器视觉设备的持续稳定性以及传输数据的准确性也无法得到保障。今天联瑞给大家讲......
  • Prometheus-2:blackbox_exporter黑盒监控
    黑盒监控blackbox_exporter前边介绍有很多exporter可以直接将metrics暴露给Prometheus进行监控,这些称为“白盒监控”,那些exporter无法监控到的指标呢?或者未暴露Metrics给Prometheus的一些服务怎么办?这时就要用到blackbox_exporte“黑盒监控”。blackbox_exporte支持用户通过:HT......