40、iOS开发中的单元测试、调试与断点使用

iOS开发中的单元测试、调试与断点使用

单元测试

在开发过程中,单元测试是确保代码质量的重要手段。以 helloWorld 方法的测试为例,我们编写了如下测试代码:

- (void)testDebugMeHelloWorld
{
    NSString *result = [self.debugMe helloWorld];
    STAssertEquals(result, @"Hello, World!",
                   @"expected DebugMe helloWorld to be 'Hello, World!', got '%@'", result);
}

然而,运行这个测试时却失败了。查看问题导航器,失败信息显示:

error: testDebugMeHelloWorld (DebugMeTests) failed: '<7c7d0000>' should be equal to '<f8b23d07>': 
expected DebugMe helloWorld to be 'Hello, World!', got 'Hello, World!'

这是因为 STAssertEquals 期望的是两个字符串对象相等,而非它们的值相等。我们需要使用 STAssertEqualObjects 来解决这个问题。修改后再次运行测试,就会成功,这意味着我们编写的第一个单元测试用例完成了。

一般来说,我们会为应用程序中的每个类编写一个测试类。有一种叫做测试驱动开发(TDD)的方法,它建议先编写测试用例,再编写应用程序代码。TDD 的一个好处是,在开始编码之前,我们就知道应用程序应该如何运行。

另外,在编写测试时,还有一个很有用的概念是模拟(mocking)。当我们测试的代码依赖于另一个对象时,可以定义一个模拟对象来模拟这个依赖对象,这样有助于保持每个单元测试的独立性。一个不错的模拟框架是 OCMock。

调试配置

在 Xcode 中创建项目时,项目默认处于调试配置模式。通常,应用程序有两种配置:调试(debug)和发布(release)。调试配置和发布配置有很多不同之处,关键区别在于调试配置会在应用程序中构建调试符号。这些调试符号就像编译后的应用程序中的小书签,能让我们将应用程序中执行的任何命令与项目中的特定源代码片段对应起来。Xcode 包含一个名为调试器的软件,它利用这些调试符号将机器代码字节转换为生成该机器代码的源代码中的特定函数和方法。

需要注意的是,如果在发布或分发配置下使用调试器,会得到非常奇怪的结果,因为这些配置不包含调试符号。

断点

断点是调试过程中最重要的工具之一。它是向调试器发出的指令,让应用程序在代码中的特定位置暂停执行并等待。通过暂停而不是停止程序的执行,应用程序仍在运行,我们可以查看变量的值,逐行执行代码。断点有两种类型,一种是暂停程序执行,另一种是执行命令或脚本后继续执行程序,不过我们通常使用前者更多。

在 Xcode 中最常见的断点类型是行号断点。设置行号断点的步骤如下:
1. 单击 MasterViewController.m 文件。
2. 找到 viewDidLoad 方法,它通常是文件中较早的方法之一。
3. 在编辑面板左侧,会看到一个显示行号的列,这就是所谓的边栏(gutter)。如果看不到行号或边栏,可以打开 Xcode 的偏好设置,进入文本编辑面板,选择编辑选项卡,勾选“显示:行号”。
4. 找到 viewDidLoad 方法中的第一行代码(通常是调用 super 的那一行),在该行左侧的边栏中单击,会出现一个小箭头指向该行代码,这样就在 MasterViewController.m 文件的特定行号处设置了一个断点。

我们可以通过将断点从边栏拖走来移除断点,通过拖动断点到边栏的新位置来移动断点。还可以通过单击断点将其暂时禁用,再次单击则重新启用。

在工具栏顶部的调试和项目窗口中有一个标有“Breakpoints”的图标,点击它可以切换断点的开启和关闭状态,这样可以在不丢失断点的情况下启用或禁用所有断点。

调试导航器和调试区域

当 Xcode 进入调试模式时,左侧的导航面板会激活调试导航器。这个视图会显示应用程序的堆栈跟踪信息,即导致当前状态的方法和函数调用。例如,它会突出显示 MasterViewController 中对 viewDidLoad 的调用。灰色的行表示我们在源代码中无法访问的类和方法。

编辑区域下方是调试区域,它由三个部分组成:
| 组件名称 | 功能描述 |
| ---- | ---- |
| 调试栏(Debug Bar) | 包含一系列控件和堆栈跟踪跳转栏,用于控制调试会话。从左到右,第一个按钮是折叠调试区域的按钮;接着是继续按钮,用于恢复程序执行;还有单步跳过(Step Over)和单步进入(Step Into)按钮,用于逐行执行代码;单步退出(Step Out)按钮用于完成当前方法的执行并返回到调用它的方法;最后一个是位置按钮,用于模拟使用 Core Location 的应用程序的位置。 |
| 变量列表(Variable List) | 默认显示局部变量,我们可以通过选择列表左上角的下拉菜单来更改显示内容,有“Auto”、“Local”和“All Variables, Registers, Globals and Statics”三个选项。还可以通过双击变量值来修改其值。 |
| 控制台面板(Console Pane) | 可以直接访问调试器命令行和输出。输出(如 NSLog() 语句)会显示在控制台面板中,所以在调试时查看这里的输出很有用。 |

嵌套调用

嵌套方法调用会在同一行代码中组合两个命令,例如:

[[NSArray alloc] initWithObject:@"Hello"];

如果嵌套多个方法,使用单步跳过按钮时会跳过多个实际命令,导致无法在不同的嵌套语句之间设置断点。因此,除了标准的 alloc init 方法嵌套外,我们通常不建议过多嵌套消息调用。

点符号(dot notation)也会带来类似的问题。例如:

[self.tableView reloadData];

在调用 reloadData 之前,会先调用访问器方法 tableView 。如果使用访问器方法有意义,我们可以在消息调用中直接使用点符号,但要注意,点符号实际上是方法调用,过多嵌套方法调用会使代码难以调试。

尝试调试控件

为了更清楚地了解调试控件的使用,我们进行以下操作:
1. 选择 MasterViewController.m 文件,在 @implementation 声明之后添加以下两个方法:

- (float)processBar:(float)inBar {
    float newBar = inBar * 2.0;
    return newBar;
}

- (NSInteger)processFoo:(NSInteger)inFoo {
    NSInteger newFoo = inFoo * 2;
    return newFoo;
}
  1. 在现有的 viewDidLoad 方法中插入以下代码:
- (void)viewDidLoad
{
    [super viewDidLoad];

    // Do any additional setup after loading the view, typically from a nib.
    NSInteger foo = 25;
    float bar = 374.3494;
    NSLog(@"foo: %d, bar: %f", foo, bar);

    foo = [self processFoo:foo];
    bar = [self processBar:bar];

    NSLog(@"foo: %d, bar: %f", foo, bar);

    self.navigationItem.leftBarButtonItem = self.editButtonItem;

    UIBarButtonItem *addButton =  
        [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd  
                                                     target:self  
                                                     action:@selector(insertNewObject:)];
    self.navigationItem.rightBarButtonItem = addButton;
}
  1. 将断点拖动到 NSInteger foo = 25; 这一行。
  2. 从项目菜单中选择“Run”来编译更改并再次启动程序。
  3. 前两行代码只是声明变量并赋值,此时单步跳过和单步进入按钮的功能相同。先点击单步跳过按钮执行下一行代码,再点击单步进入按钮执行第二行新代码。
  4. 查看变量列表,会看到刚刚声明的两个变量及其当前值,其中 bar 的值为蓝色,表示它是由最后执行的命令赋值或更改的。
  5. 还可以将鼠标悬停在编辑面板中的变量上,会弹出一个小框显示变量的当前值和类型。
  6. 下一行代码是日志语句,点击单步跳过按钮让其执行。
  7. 接下来的两行代码分别调用一个方法。点击单步进入按钮进入 processFoo 方法,此时堆栈跟踪中 viewDidLoad 不再是第一行, processFoo 取代了它。如果想回到 viewDidLoad 方法,点击单步退出按钮。
  8. 对于 processBar 方法,使用单步跳过按钮。使用单步跳过按钮时,不会在堆栈跟踪中看到 processBar 方法,调试器会运行整个方法,然后在方法返回后停止执行。通过查看 bar 的值,我们可以看到 processBar 方法的执行结果。
断点导航器和符号断点

在 Xcode 导航面板中,选择导航栏上的“Breakpoints”选项卡,这个面板会显示项目中当前设置的所有断点。我们可以在这里选择断点并按删除键来删除它们,还可以添加另一种类型的断点——符号断点。符号断点不是在特定源代码文件的特定行上中断,而是告诉调试器在使用调试配置时,每当到达应用程序中内置的某个调试符号时中断。调试符号是从方法和函数名称派生的可读名称。

iOS开发中的单元测试、调试与断点使用(续)

调试过程中的注意事项

在调试过程中,除了前面提到的内容,还有一些细节需要我们注意。

避免过度嵌套消息调用

嵌套方法调用虽然可以让代码看起来更简洁,但在调试时会带来很大的困扰。如前面提到的 [[NSArray alloc] initWithObject:@"Hello"]; 这样的嵌套调用,使用单步跳过按钮会跳过多个实际命令,使得难以在不同的嵌套语句之间设置断点。除了常见的 alloc init 方法嵌套,我们应尽量避免过度嵌套消息调用。

注意点符号的使用

点符号是方法调用的简写形式,例如 [self.tableView reloadData]; 这行代码实际上包含了两个命令,在调用 reloadData 之前会先调用访问器方法 tableView 。在使用点符号时,要清楚它会引发方法调用,避免因过度嵌套方法调用而导致代码难以调试。

调试流程总结

为了更清晰地展示调试的整个流程,下面是一个 mermaid 格式的流程图:

graph TD;
    A[开始调试] --> B[设置断点];
    B --> C[运行程序];
    C --> D{是否到达断点};
    D -- 是 --> E[暂停程序,查看变量和堆栈信息];
    E --> F{选择调试操作};
    F -- 单步跳过 --> G[执行下一行代码];
    F -- 单步进入 --> H[进入调用的方法];
    F -- 单步退出 --> I[完成当前方法并返回调用处];
    G --> D;
    H --> D;
    I --> D;
    D -- 否 --> J[程序正常运行结束];
调试技巧拓展

除了上述基本的调试方法和工具,还有一些调试技巧可以帮助我们更高效地解决问题。

条件断点

在某些情况下,我们可能只希望在满足特定条件时才触发断点。例如,我们只关心某个变量的值达到特定值时的程序状态。在 Xcode 中设置条件断点的步骤如下:
1. 先设置一个普通的行号断点。
2. 右键点击断点,选择“Edit Breakpoint”。
3. 在弹出的窗口中,找到“Condition”字段,输入你想要的条件,例如 variable == 10
4. 点击“Done”保存设置。这样,只有当条件满足时,断点才会触发。

日志调试

虽然使用调试器可以方便地查看变量的值,但在某些情况下,日志调试也是一种简单有效的方法。通过在代码中添加 NSLog() 语句,我们可以在控制台输出关键信息,帮助我们了解程序的执行流程和变量的值。例如:

NSInteger value = 10;
NSLog(@"The value of variable is: %d", value);
总结

通过本文,我们详细介绍了 iOS 开发中的单元测试、调试配置、断点使用等重要内容。单元测试是确保代码质量的重要手段,通过编写测试用例可以提前发现代码中的问题。调试配置中的调试符号为我们使用调试器提供了便利,让我们能够将机器代码与源代码对应起来。断点是调试过程中的核心工具,通过行号断点和符号断点,我们可以在代码的关键位置暂停程序,查看变量的值和程序的执行流程。调试导航器和调试区域为我们提供了丰富的信息,帮助我们更好地理解程序的运行状态。同时,我们还介绍了调试过程中的注意事项和一些调试技巧,希望这些内容能够帮助你在 iOS 开发中更高效地进行调试工作。

在实际开发中,我们应该养成良好的调试习惯,合理运用各种调试工具和技巧,不断提高自己的调试能力,从而更快地解决代码中的问题,提高开发效率。

调试工具/技巧 描述
行号断点 在特定源代码文件的特定行上中断程序执行
符号断点 在到达应用程序中内置的某个调试符号时中断程序执行
条件断点 满足特定条件时触发断点
日志调试 通过 NSLog() 语句输出关键信息

希望本文对你在 iOS 开发中的调试工作有所帮助,如果你在调试过程中遇到任何问题,欢迎留言讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值