开发人员谈测试:如何写出简洁又规范的单元测试

2023-11-05

文章转载链接http://www.51testing.com/html/83/n-4480883.html

我们之前谈到过要让开发人员认识到测试的重要性并了解测试,本文的主要重点是针对日常开发阶段工程师可以做的事情,也就是单元测试而展开。
  编写功能、业务代码的时候一般会遵循kiss原则 ,所以类、方法、函数往往不会太大,分层设计越好、职责越单一、耦合度越低的代码越适合做单元测试,单元测试也倒逼开发过程中代码分层、解耦。
  可能某个功能的实现代码有30行,测试代码有50行。单元测试的代码如何编写才更合理、整洁、规范呢?
  编码分模块展开
  先贴一段代码:

-  (void)testInsertDataInOneSpecifiedTable
{
    XCTestExpectation *exception = [self expectationWithDescription:@"测试数据库插入功能"];
    // given
    [dbInstance removeAllLogsInTableType:HCTLogTableTypeMeta];
    NSMutableArray *insertModels = [NSMutableArray array];
    for (NSInteger index = 1; index <= 10000; index++) {
        HCTLogMetaModel *model = [[HCTLogMetaModel alloc] init];
        model.log_id = index;
        // ...
        [insertModels addObject:model];
    }
    // when
    [dbInstance add:insertModels inTableType:HCTLogTableTypeMeta];
     // then 
    [dbInstance recordsCountInTableType:HCTLogTableTypeMeta completion:^(NSInteger count) {
        XCTAssert(count == insertModels.count, @"「数据增加」功能:异常");
        [exception fulfill];
    }];
    [self waitForExpectationsWithCommonTimeout];
}

可以看到这个方法的名称为 testInsertDataInOneSpecifiedTable,这段代码做的事情通过函数名可以看出来:测试插入数据到某个特定的表。
  这个测试用例分为3部分:
  测试环境所需的先决条件准备;
  调用所要测试的某个方法、函数;
  验证输出和行为是否符合预期。
  其实,每个测试用例的编写也要按照该种方式去组织代码。步骤分为3个阶段:Given->When->Then。
  所以单元测试的代码规范也就出来了。此外单元测试代码规范统一后,每个人的测试代码都按照这个标准展开,那其他人的阅读起来就更加容易、方便。
  按照这3个步骤去阅读、理解测试代码,就可以清晰明了的知道在做什么。
  一个测试用例只测试一个分支
  我们写的代码有很多语句组成,有各种逻辑判断、分支(if…else、swicth)等等,因此一个程序从一个单一入口进去,过程可能产生n个不同的分支,但是程序的出口总是一个。
  所以由于这样的特性,我们的测试也需要针对这样的现状走完尽可能多的分支。相应的指标叫做「分支覆盖率」。
  假如某个方法内部有 if…else…,我们在测试的时候尽量将每种情况写成一个单独的测试用例,单独的输入、输出,判断是否符合预期。这样每个case都单一的测试某个分支,可读性也很高。
  比如对下面的函数做单元测试,测试用例设计如下:

- (void)shouldIEatSomething
{
   BOOL shouldEat = [self getAteWeight] < self.dailyFoodSupport;
   if (shouldEat) {
     [self eatSomemuchFood];
   } else {
     [self doSomeExercise];
   }
}
- (void)testShouldIEatSomethingWhenHungry
{
   // ....
}
- (void)testShouldIEatSomethingWhenFull
{
  // ...
}

明确标识被测试类
  这条主要站在团队合作和代码可读性角度出发来说明。
  写过单元测试的人都知道,可能某个函数本来就10行代码,可是为了测试它,测试代码写了30行。
  一个方法这样写问题不大,多看看就看明白是在测试哪个类的哪个方法。
  可是当这个类本身就很大,测试代码很大的情况下,不管是作者自身还是多年后负责维护的其他同事,看这个代码阅读成本会很大,需要先看测试文件名“代码类名+Test”才知道是测试的是哪个类,看测试方法名“test+ 方法名”才知道是测试的是哪个方法。
  这样的代码可读性很差,所以应该为当前的测试对象特殊标记,这样测试代码可读性越强、阅读成本越低。
  比如定义局部变量 _sut 用来标记当前被测试类(sut——System under Test,软件测试领域有个词叫做被测系统,用来表示正在被测试的系统)。

#import <XCTest/XCTest.h>
#import "HCTLogPayloadModel.h"
@interface HCTLogPayloadModelTest : HCTTestCase
{
    HCTLogPayloadModel *_sut;
}
@end
@implementation HCTLogPayloadModelTest
- (void)setUp
{
    [super setUp];
    HCTLogPayloadModel *model = [[HCTLogPayloadModel alloc] init];
    model.log_id = 1;
    // ...
    _sut = model;
}
- (void)tearDown
{
    _sut = nil;
    [super tearDown];
}
- (void)testGetDictionary
{
    NSDictionary *payloadDictionary = [_sut getDictionary];
    XCTAssert([(NSString *)payloadDictionary[@"report_id"] isEqualToString:@"001"] &&
              [payloadDictionary[@"size"] integerValue] == 102 &&
              [(NSString *)payloadDictionary[@"meta"] containsString:@"meiying"],
              @"HCTLogPayloadModel 的 「getDictionary」功能异常");
}
@end

使用分类来暴露私有方法、私有变量
  某些场景下写的测试方法内部可能需要调用被测对象的私有方法,也可能需要访问被测对象的某个私有属性。
  但是测试类里面是访问不到被测类的私有属性和私有方法的,借助于Category可以实现这样的需求。
  为测试类添加一个分类,后缀名为UnitTest,如下所示。
  HermesClient类有私有属性@property (nonatomic, strong) NSString *name;,私有方法 - (void)hello。
  为了在测试用例中访问私有属性和私有方法,写了如下分类:

// HermesClientTest.m
@interface HermesClient (UnitTest)
- (NSString *)name;
- (void)hello;
@end
  
@implementation HermesClientTest
- (void)testPrivatePropertyAndMethod
{
    NSLog(@"%@",[HermesClient sharedInstance].name);
    [[HermesClient sharedInstance] hello];
}
@end
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

开发人员谈测试:如何写出简洁又规范的单元测试 的相关文章

  • 如何在运行时更改 slf4j 级别?

    我使用 SLF4j 作为我的日志框架 由 log4j 支持 我的问题是我正在寻找一种在运行时更改记录器的日志记录级别的方法 据我了解 slf4j 不允许直接通过其自己的 API 执行此操作 因此 我必须直接访问日志记录提供程序 我个人认为这
  • 捕获 log4j 输出

    我们正在使用log4j2广泛地存在于我们的系统中 并对其进行配置log4j2 xml 现在我需要一个可以运行的新应用程序jobs 我想单独捕获时间 X 和 Y 之间产生的所有日志并将其放入数据库中 我们框架的正常日志记录应该照常发生 记录到
  • log4j 打印错误的字符

    有人报告我给他的使用 log4j 的程序无法正确打印字符 他告诉我 在文件中打印为 例如 Vid o 变成 Vid o 这可能是一些编码问题 但我喜欢重现问题以证明它已修复 我无法找到有关该主题的良好 且简短 文档 因此 是什么导致了这个问
  • Log4j 配置 - 不同的日志记录到不同的文件

    对于某些人来说 这可能是一个非常简单的问题 但就我个人而言 我发现 Log4j 配置非常困难 并且学习进行脑部手术可能不那么具有挑战性 我正在尝试让多个记录器登录到不同的文件中 这是我的 log4j properties 文件中的内容 Ro
  • 清理 Tomcat 访问日志条目

    在我们的日志中 我们看到信用卡号码是由于人们使用 CC 信息点击我们应用程序中的一些 ULR 我不知道他们为什么这样做 我们希望清理这些信息 出于 PCI 考虑 甚至不将其保存到磁盘 因此 我希望能够在日志条目到达日志文件之前对其进行清理
  • 使用log4j2,如何记录键值对

    我需要创建带有键值对的日志 如下所示 PatternLayout 是否支持使用 log4j2 xml 对线程中的静态字段 如 log level class name event id 等 执行此操作 日志样本 2014 06 18 11
  • 记录多线程应用程序中的活动

    我有一个 Java 分层应用程序 它有一个从不同点调用的多线程数据访问层 对该层的一次调用可能会产生多个线程来并行化对数据库的请求 我正在寻找的是一个日志记录工具 它允许我定义由各种线程组成的 活动 因此 数据访问层中的相同方法应根据其调用
  • maven install 仅在第二次尝试后抛出“打开 zip 文件时出错”

    我尝试在我的项目上运行 Maven install 并带有一些依赖项
  • 在 log4j.properties 文件 SMTP Appender 中隐藏或加密密码

    我正在使用自定义 gmail smtp 附加程序从我的 gmail 帐户发送错误日志 按照说明操作 http www tgerm com 2010 05 log4j smtpappender gmail custom html http w
  • 如何重新执行Log4j“默认初始化过程”?

    在运行时我经常创建 修改log4j记录器 附加器 级别 布局有时需要将所有内容重置回默认值 Log4j系统有明确的定义默认初始化过程 http logging apache org log4j 1 2 manual html default
  • logback 支持 log4j 附加程序吗?

    为 log4j 创建的自定义 Appender 扩展 AppenderSkeleton 可以与 new 一起使用吗logback框架 我知道logback带有自己的一组类似于 log4j 的附加程序 但这是否可以重用现有的附加程序 如何 以
  • 如何解决此 Log4J 导入错误(也与类路径相关)?

    当我运行以下简单的 log4J 示例时 出现错误 import org apache logging log4j core import java io import java sql SQLException import java ut
  • Log4j 显示包名称

    现在对于我的 ConversionPattern 我有 log4j appender A1 layout ConversionPattern d yyyy MMM dd HH mm ss SSS 5p t F L m n 我想做的还包括包含
  • Log4j2 ThreadContext 映射不适用于parallelStream()

    我有以下示例代码 public class Test static System setProperty isThreadContextMapInheritable true private static final Logger LOGG
  • 有没有一种简单的方法来为每个类创建一个记录器实例?

    我现在使用静态方法来记录 因为我发现在Android中登录非常容易 但是现在我需要为不同的类配置不同的appender 所以我对静态记录方法有一个问题 我读了Log4J 创建 Logger 实例的策略 https stackoverflow
  • logback的“谨慎模式”是如何实现的?

    The 审慎模式 http logback qos ch manual appenders html prudentlogback 中的序列化所有 JVM 之间的 IO 操作 写入同一文件 可能运行在不同的主机上 在其他日志记录框架中 如果
  • 在S3中捕获Kubernetes Spark驱动程序和执行程序日志并在历史服务器中查看

    我正在 Kubernetes 上使用 Spark submit cli 运行 Spark 3 0 0 和 Hadoop 2 7 如下所示 spark submit master k8s https api k8s my domain com
  • 如何在 Surefire 测试报告中显示 log4j 输出

    当 Maven 中的测试失败时 surefire 测试报告 xml 文件位于target surefire reports TEST
  • log4j.properties 在 Wildfly 上无法正常工作

    我的类路径中有一个 log4j properties 文件 它位于 APP XX jar log4j properties 位置 我注意到在ear文件中我还可以在lib文件夹中找到log4j 1 2 17 jar 但无论我在 log4j p
  • 如果未安装 Java,您是否可以免受 log4j CVE-2021-44228 的影响?

    我已经阅读了很多关于这个问题有多严重的内容 并了解了在我们公司正在生成的代码中找到它的可用选项 并更新了使用易受攻击版本的服务器 我无法找到的是特定服务器是否未安装 Java 即如果我以 root 身份登录并运行java version并得

随机推荐