编写额外的代码来执行涉及ivars的单元测试好还是应该不惜一切代价避免?

时间:2013-01-21 08:57:44

标签: objective-c unit-testing tdd code-design

在编写单元测试时,我仍然是一个新手,我经常遇到这样的情况,即我正在摸索着做正确的事情的方法。为计划的设计编写测试我遇到了这些导致头皮屑的实例之一。我的设计:

一个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的首要目标是避免回归,但我认为提高整体代码质量也是一个重要目标。是否会增加偶尔出现的绒毛?

3 个答案:

答案 0 :(得分:1)

您不想测试班级的内部状态 - 这没有任何意义。你唯一关心的是你的班级在与外界的互动中所做的事情(信息是向内还是向外或两种方式)。

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

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

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

嘲笑对象乍一看似乎很复杂,但你正在测试正确的东西 - 你的目标类的行为 - 而不用任何特殊的测试方法等来污染它。

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

答案 1 :(得分:1)

  

我想编写单元测试来检查ivar _dataAddress   已经改变了fetchLocalData或fetchWorldwideData   执行。

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

  

异步获取数据并发送通知   完成后收集数据。

听起来这是该类中这些方法的外部行为。这是您应该编写要检查的测试。我不知道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最重要的好处之一。

答案 2 :(得分:0)

我也不是ObjectiveC的开发者,但我认为你发布这个的原因是因为你正在听你的代码,而你的代码告诉你某些东西不太正确。

我会问你对fetchData电话的结果做了什么?我怀疑你是在某处渲染数据。如果iOS正在呈现它,那么可能存在一个可以断言而不是声明实例变量的回调。如果您正在从类中更新UI,则可以更轻松地测试是否引入了Observer来分离UI和获取数据的代码。然后,您可以将测试寄存器作为接收器并在那里断言状态更改。

希望有所帮助!

布兰登