2

在编写单元测试时我还是个新手,我经常遇到这样的情况,我不知道什么是正确的做事方式。为计划中的设计编写测试我遇到了其中一个引起头皮屑的实例。我的设计:

一个 ViewController 根据用户的输入将消息发送到 dataFetcherClass。(以下代码已更改以保护无辜者)。

-(void) userPushedLocalBusinessButtons{
    [_businessDataFetcher fetchLocalData];
}

-(void) userPushedWorldwideBusinessButtons{
    [_businessDataFetcher fetchWorldwideData];
}

这些操作的数据格式相同,它是 dataFetcher 应该从更改中收集数据的位置。所以,在 BusinessDataFetcherClass 我有这些方法:

-(void) fetchLocalData{
    _dataAddress = @"localData.json";
    [self fetchData]; 
}

-(void) fetchWorldwideData{
    _dataAddress = @"worldwideData.json";
    [self fetchData]; 
}

fetchData 方法异步获取数据,并在完成后发送包含收集到的数据的通知。现在,我想编写单元测试,检查在执行 fetchLocalData 或 fetchWorldwideData 时 ivar _dataAddress 是否已更改。

如果不更改代码,这显然是不可能的。有人会说,这可以通过将 _dataAddress 设置为公共属性来轻松解决,这是一种解决方案。另一种方法是创建一个返回 _dataAddress ivar 值的方法。我对这两种选择都不完全满意,因为它们在两种情况下都迫使我只为测试更改代码,而不是提高实际代码库本身的整体质量。

我选择了第二种选择,并包含了一个方法 -(NSString *) dataAddress; 我的问题(如标题中所述)是这样是否可以?我的设计有问题吗?显然,TDD 的首要目标是避免回归,但我相信提高整体代码质量也是一个重要目标。是否会添加偶尔的绒毛?

4

3 回答 3

1

我想编写单元测试,检查执行 fetchLocalData 或 fetchWorldwideData 时 ivar _dataAddress 是否已更改。

当你写一个单元测试时,它应该测试一个类的外部行为。这是该类的实现细节。如果您想更改获取数据的工作方式,则该类可能在单元测试失败时仍然有效。这使您的单元测试很烦人,没有帮助。

异步获取数据并在完成后发送包含收集到的数据的通知。

听起来这是该类中这些方法的外部行为。这是您应该编写测试来检查的内容。我不知道objective-c,所以这是一个伪代码示例:

setup expected local data (preferably with a mock)
call fetchLocalData on BusinessDataFetcherClass 
wait a little bit
check that local data is populated on ViewController

我的设计有问题吗?

您在这里的设计确实使编写测试变得更加困难,尽管这不是一个大问题。特别是需要在测试中发生的“等待”。您的测试指出的设计问题是您的类至少有两个职责:获取数据和管理异步。如果您将这些职责分开,它们将更容易测试。

显然,TDD 的首要目标是避免回归,但我相信提高整体代码质量也是一个重要目标。是否会添加偶尔的绒毛?

在这种情况下,我认为您可能不需要更多绒毛,但有时确实会在单元测试中发生。当您编写带有测试的代码时,您最终会得到两个代码客户端:测试代码和生产代码。在不同的上下文中满足两个客户的需要迫使一些这种“绒毛”进入,或者迫使一些设计改变。好消息是,当您的设计可以轻松满足两个客户时,如果需要,您可能会相当容易地满足第三个和第四个客户。对我来说,这种效果是 TDD 最重要的好处之一。

于 2013-01-21T14:48:05.657 回答
1

你不想测试你的类的内部状态——这是没有意义的。你唯一关心的是你的班级在与外部世界的互动中做了什么(信息是向内还是向外或双向)。

换句话说:一旦你为你的类编写了一个测试,重写你的类的实现(内部)同时保持它的可见行为不应该破坏你的测试。如果是这样,那么您的测试就被 IMO 破坏了。

测试类行为的一个好方法是使用 Mock 对象——例如,请参阅OCMock for iOS。

模拟对象允许您测试目标类的行为。为此,您需要以某种方式编写目标类:在您的示例中,您需要能够传入网络提供程序类,而不是让您的类关闭并使用某个硬编码的提供程序(可重用的组件永远不应该配置自己,而应该被配置)。一旦您以这种方式进行设置,您的单元测试类就可以传入一个模拟网络服务提供程序,该服务提供程序检查是否命中了正确的 URL。

模拟对象乍一看似乎令人费解,但您正在测试正确的东西——目标类的行为——而没有使用任何特殊的测试方法等污染它。

另请注意,使您的代码易于测试也使其更易于重用:您的测试用例成为代码的第二个“用户”。

于 2013-01-21T13:37:20.960 回答
0

我也不是 ObjectiveC 开发人员,但我认为您发布此内容的原因是因为您正在听您的代码,而您的代码告诉您有些事情不太对劲。

我想问一下你对fetchData通话结果做了什么?我怀疑你在某处渲染数据。如果 iOS 正在渲染它,那么您可能在某处可以断言而不是断言实例变量的回调。如果您从类中更新 UI,如果您引入 Observer 以将您的 UI 和获取数据的代码解耦,那么测试将变得更容易。然后,您可以将您的测试寄存器作为接收器并在那里断言您的状态更改。

希望有帮助!

布兰登

于 2013-01-22T03:08:04.033 回答