What's new?
In this article I want to talk about the use of technology BDD in the development of applications for iOS.
It was interesting to try in practice one of the methodologies: TDD or BDD. The choice fell on BDD. Why precisely he? Very interesting about him told at DevCamp'e in the Kharkov office Ciklum. Why Kiwi? He was also discussed on this notorious DevCamp. Therefore, I wanted to try everything myself in practice. So, who cares about the examples with BDD, is a bit more complicated than testing the flip of a line or calculator, please under the cat.
Formulation of the problem
What task is most often solved by a business application developer for iOS? In my opinion, this is receiving data from the server and displaying it to the user. For example, an application was written that solved the problem of receiving questions on the tag “iphone” from the site “StackOverflow” and then displaying them in a table. I will not delve into the subtleties of architecture and implementation, but rather I’d like to dwell more on interesting, in my opinion, things.
How to test a query?
Probably, everyone had situations when working in parallel with the web-team, you could not reach the server. The reasons for this could be different, whether the url was changed, or the service broke down. In general, it does not matter. And the important thing is that it would be good to check the unit tests when starting up, and does our server respond? Class “StackOverflowRequest” is responsible for communicating with the server. We need to ensure that the receivedJSON: method is called when the data is successfully received from the server, and if it fails, the fetchFailedWithError: method was called. As a test data, select a valid and invalid url.
it(@"should recieve receivedJSON", ^ { NSString *questionsUrlString = @"http://api.stackoverflow.com/1.1/search?tagged=iphone&pagesize=20"; IFStackOverflowRequest *request = [[IFStackOverflowRequest alloc] initWithDelegate:controller urlString:questionsUrlString]; [[request fetchQestions] start]; [[[controller shouldEventuallyBeforeTimingOutAfter(3)] receive] receivedJSON:any()]; }); it(@"should recieve fetchFailedWithError", ^ { NSString *fakeUrl = @"asda"; IFStackOverflowRequest *request = [[IFStackOverflowRequest alloc] initWithDelegate:controller urlString:fakeUrl]; [[request fetchQestions] start]; [[[controller shouldEventuallyBeforeTimingOutAfter(1)] receive] fetchFailedWithError:any()]; });
Unfortunately, in this test one of the test conditions is violated: the tests must pass quickly. And since we are waiting for a response from the server, this is not good. Therefore, I concluded these 2 tests in the “ifdef” block.
')
How to test data waiting from the server?
As you know, if there is some kind of data loading / processing, then we have to show this to the user so that there is no feeling that the application is stuck. Most often this occurs when downloading images from the server. In my case, I just show the spiner. The algorithm, by which I determine whether the pictures loaded, is implemented using KVO.
it(@"spiner should be visible during avatar loading", ^ { IFQuestionCell *cell = (IFQuestionCell *)[tableDelegate tableView:[UITableView new] cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]; UIImageView *askerAvatar = (UIImageView *)[cell objectForPropertyName:@"askerAvatar"]; [askerAvatar.image shouldBeNil]; UIActivityIndicatorView *spiner = (UIActivityIndicatorView *)[cell objectForPropertyName:@"spiner"]; [spiner shouldNotBeNil]; [[theValue(spiner.hidden) should] equal:theValue(NO)]; [[theValue(spiner.isAnimating) should] equal:theValue(YES)]; }); it(@"spiner should be hidden when avatar is loaded", ^ { IFQuestionCell *cell = (IFQuestionCell *)[tableDelegate tableView:[UITableView new] cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]; UIImageView *askerAvatar = (UIImageView *)[cell objectForPropertyName:@"askerAvatar"]; askerAvatar.image = [UIImage new]; UIActivityIndicatorView *spiner = (UIActivityIndicatorView *)[cell objectForPropertyName:@"spiner"]; [spiner shouldNotBeNil]; [[theValue(spiner.hidden) should] equal:theValue(YES)]; });
How to test the correctness of filling the table with data?
For these purposes, I usually use an object that supports the protocols "UITableViewDataSource, UITableViewDelegate". Perhaps, I will not give an example in the article of testing the correctness of sorting and the uniqueness of objects in the table, I mean, in view of their banal implementation. This can be seen in the example. I will show how to test the loading of new questions. As you know, the server cannot give us any amount of data we request from it. Instead, we get the data in chunks. In my case, these are 20 questions at a time. That is, I can display 20 questions, and after the user has crushed to the last question, I will have to make a request to the server to receive the next batch of questions. In order for the user to see that a request has been sent to the server for the next batch of questions, instead of a cell with questions, I display a cell with spinners.
it(@"spiner cell should be last cell, if last cell is not visible on table", ^ { IFQuestion *q1 = [IFQuestion new]; IFQuestion *q2 = [IFQuestion new]; IFQuestion *q3 = [IFQuestion new]; IFQuestion *q4 = [IFQuestion new]; IFQuestion *q5 = [IFQuestion new]; IFQuestion *q6 = [IFQuestion new]; IFQuestion *q7 = [IFQuestion new]; IFQuestion *q8 = [IFQuestion new]; q1.questionID = 1; q2.questionID = 2; q3.questionID = 3; q4.questionID = 4; q5.questionID = 5; q6.questionID = 6; q7.questionID = 7; q8.questionID = 8; [tableDelegate addQuestions:@[q1, q2, q3, q4, q5, q6, q7, q8]]; IFSpinerCell *cell = (IFSpinerCell *)[tableDelegate tableView:tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:9 inSection:0]]; NSString *cellClassName = NSStringFromClass ([cell class]); [[cellClassName should] equal:NSStringFromClass ([IFSpinerCell class])]; for(NSInteger i = 0; i < 9; i++) { cell = (IFSpinerCell *)[tableDelegate tableView:tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]]; cellClassName = NSStringFromClass ([cell class]); [[cellClassName should] equal:NSStringFromClass ([IFQuestionCell class])]; } });
PS
A full example can be found by following
this link. I hope the article was helpful. I will be glad to answer your questions, as well as hear comments and suggestions. Thank.