Objective C - Best practice for Unit Testing with multiple test inputs - ios

I'm writing unit test codes for an existing project. The project is in Objective-C and I have to test few functions with a number of inputs to the test cases. For example, I have a test case to test a function calculator where two parameters are inputted. Currently I create array to store the set of input values to run the test. The code used are as follows:
- (void)setUp {
[super setUp];
self.vcToTest = [[BodyMassIndexVC alloc] init];
input1 = [[NSMutableArray alloc] initWithObjects:#"193", #"192", #"192", #"165", #"155", #"154", nil];
input2 = [[NSMutableArray alloc] initWithObjects:#"37", #"37", #"36", #"80",#"120", #"120", nil];
}
- (void)testCalculatorSuccess {
for (int i=0; i<input1.count; i++) {
NSArray *expectedResult = [[NSArray alloc] initWithObjects: #"9.93", #"10.04", #"9.77", #"29.38", #"49.95", #"50.60", nil];
NSString *actualResult = [self.vcToTest calculateResult:input1[i] andInput2:input2[i]];
XCTAssertEqualObjects(actualResult, expectedResult[i]);
}
}
I searched for best practices online but was not able to find any. Can someone help me with this? Am I running the test in the right way? What is the best practice to be followed in such cases? Should I create a test case for every set of input?

In my experience, the key consideration should be how easy it will be to maintain the test suite over time. Your current approach will cause problems down the road in two ways:
If you want to use different numbers for you BMI calculation you need to use a different test class (because you're locking in the values in the setup method).
If you ever decide to use different math or multiple BMI equations you'll have to update the arrays anywhere you're checking those values.
What I would suggest is to instead create a CSV or plain text file that has the height, weight, and expected BMI values in it. That keeps the test data together. Then in your test methods, load the file and check your actual BMI against the expected BMI.
You have the flexibility here to mix and match test data, or use different test files for different BMI equations. Personally, I also like the fact that you can keep old data files around as you change things, in case you ever want to rollback or add legacy algorithm support.
A quick and dirty version would look something like this:
- (NSArray *)dataFromFileNamed:(NSString *)filename {
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:filename ofType:nil];
// load the data however its formatted
// return the data as an array
return loadedData;
}
- (void)testBMIFormulaInCmAndKgSuccess {
NSArray *testData = [self dataFromFileNamed:#"BMI_data_01.txt"];
for (int i=0; i < testData.count; i++) {
NSArray *dataSet = testData[i];
CGFloat height = dataSet[0];
CGFloat weight = dataSet[1];
CGFloat expectedBMI = dataSet[2];
NSString *actualResult = [self.vcToTest calculateBMIforHeight:height andWeight:weight];
XCTAssertEqual(actualResult, expectedBMI);
}
}

Your test class should be targeting one specific thing to test which is the sut (system under test). In your case the sut variable should be your vcToTest. The way I would go about testing is by dependency injection rather than storing all the arrays you are testing as instance variables. So you would create a test method that takes in the parameters that you want to test. This way you don't need to keep creating instance variables, you only create local variables that are relevant to the test method.
- (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
self.sut = [[BodyMassIndexVC alloc] init];
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
self.sut = nil;
[super tearDown];
}
- (void)testBMIFormulaInCmAndKgSuccess {
// local variables that are only seen in this method
NSArray *heightsInMetres = [[NSMutableArray alloc] initWithObjects:#"193", #"192", #"192", #"165", #"155", #"154", nil];
NSArray *weightsInKg = [[NSMutableArray alloc] initWithObjects:#"37", #"37", #"36", #"80",#"120", #"120", nil];
NSArray *expectedResults = [[NSArray alloc] initWithObjects: #"9.93", #"10.04", #"9.77", #"29.38", #"49.95", #"50.60", nil];
for (int i=0; i<heightsInMetres.count; i++) {
[self xTestHeightInMeters:heightsInMetres[i] weightInKg:weightsInKg[i] expecting:expectedResults[i]];
}
}
// the variables that are used to test are injected into this method
- (void)xTestHeightInMeters:(NSString *)height weightInKg:(NSString *)weight expecting:(NSString *)expectedResult {
NSString *result = [self.sut calculateBMIforHeight:height andWeight:weight];
XCTAssertEqual(result, expectedResult);
}
If I was you I wouldn't create arrays to run the tests. Arrays are messy and become hard to understand what is going on, and easy to make mistakes. I would create specific test methods that test one thing to make sure the sut is working properly. For example in TDD you create a test method that will fail, then modify your sut to fix this failure in the most simple way. Usually this means your fix will just return exactly what you are expecting. Then you make another test that tests the exact same thing with a different value, it should now fail because your sut is simply returning what they previous test was looking for. Now you modify your sut again to make both tests pass. After this in most situations you won't need any addition tests since it has proven to work in two unique ways.
I know you said you are testing software that was written already, but I strongly recommend you check out Test Driven Development. Even if you don't actually apply TDD, you will learn how to create meaningful tests. This helped me learn tdd

It's usually best to avoid for-loops in unit test code. This rule usually leads to separate assertions.
But in your case, you want to exercise a function with various inputs. Your approach is not bad at all. We can simplify the arrays by using literals:
NSArray<NSString *> *heightsInMetres = #[ #"193", #"192", #"192", #"165", #"155", #"154"];
NSArray<NSString *> *weightsInKg = #[ #"37", #"37", #"36", #"80", #"120", #"120"];
NSArray<NSString *> *expectedResults = #[#"9.93", #"10.04", #"9.77", #"29.38", #"49.95", #"50.60"];
I also normally avoid funny formatting. But aligning the columns helps us see the values in a table-like format.
Finally, I wouldn't put these values in setUp unless they're used across multiple tests.
If you need many tests like this, it may be worth exploring a test format that uses actual tables, like Fitnesse. Table data could be in spreadsheets or in wiki format, driving tests.

Related

Reset property within lazy getter / after each access

I feel like there is a more regulation way to do what I am doing in, either by some iOS specific thing, or pattern I'm aware of. I'm trying to create an NSMutableArray variable, that essentially acts as temporary storage for a logger class. Each time the array is accessed, I want to either lazily instantiate it, or set it to nil. The way I am thinking of doing it seems a little hacky and I'm looking for some input?
- (NSMutableArray)myArray {
if (!_myArray) {
_myArray = [[NSMutableArray alloc] init];
} else {
_myArray = nil;
}
return _myArray;
}
The effect I'm hoping to achieve is using a logger that is logging details about network requests - http codes, URLs, repsonse times, etc. I want the logger to amalgamate all this output in this storage array. Later on, when I'm hitting an API, I want to take the contents of this array, and send it up to the API, and I also want the array to reset (so the array is essentially a log of network request data since the last time the app hits the API, versus a log of what has happened since the app launched.
I realise that I could do this manually by niling the array when I access it, but I'm trying to do this in a more of a plug and play way, where it you don't need to worry if someone forgets to nil the array etc
The effect that you are trying to achieve is perfectly legitimate, but you shouldn't try to achieve it with a getter alone: the very fact that a simple getter could reset something back to nil would be counter-intuitive to your readers.
Instead, you should make two methods - one to prepare the array, and another one to harvest it, and replace with a fresh nil:
- (NSMutableArray*)myArray {
if (!_myArray) {
_myArray = [[NSMutableArray alloc] init];
}
return _myArray;
}
- (NSMutableArray*)resetArray{
NSMutableArray *res = _myArray;
_myArray = nil;
return res;
}
Now the sequence of operations becomes intuitively clear: you get myArray as many times as you wish, add as many items as you need, and then when you are done call resetArray. This would get you a complete array with all the data, and reset the object to be ready for the next call:
for (int col = 0 ; col != 10 ; col++) {
[log.myArray addObject:[self getDataForIndex:col]];
}
NSMutableArray* logArray = [log resetArray];
What you're doing doesn't make any sense to me.
Creating it empty if it doesn't exist makes sense.
Setting it to nil if it does exist does not make sense.
The usual pattern for lazy loading is to use a property with a getter:
#property (nonatomic, retain) NSMutableArray * myArray;
and then the implementation:
//Custom getter
-(NSMutableArray*) myArray;
{
if (!_myArray)
_myArray = [[NSMutableArray alloc] init];
return _myArray;
}
Then you ALWAYS refer to the array using the getter. If it hasn't yet been created, the getter creates and returns the empty array. If it does exist, it returns the existing one.

Handling PVR textures for OpenGL ES 2 (the real questioning, though, is about Objective-C ARC and class methods)

According to the OpenGL ES Programming guide's section on texturetool,
Your app must parse the data header to obtain the actual texture data. See the
PVRTextureLoader sample for an example of working with texture data in the PVR format.
This is all well and good, but 3 years later, and PVRTextureLoader's PVRTexture.m doesn't compile because it needs to be converted in order to work in an ARC project. I reckon I could flag these two files as non-ARC but I wanted to at least learn a little about Objective-C this time around.
Here's a bit of code that's giving me trouble at this point:
+ (id)pvrTextureWithContentsOfFile:(NSString *)path
{
return [self initWithContentsOfFile:path];
}
This was manually converted from:
+ (id)pvrTextureWithContentsOfFile:(NSString *)path
{
return [[[self alloc] initWithContentsOfFile:path] autorelease];
}
Maybe someone could be so kind as to walk through what this actually does (as it makes no sense to me to refer to self from what is clearly declared as a class method and not an instance method), but the actual error seen is
<...>/PVRTexture.m:256:15: error: no known class method for selector 'initWithContentsOfFile:'
return [self initWithContentsOfFile:path];
^~~~~~~~~~~~~~~~~~~~~~
1 error generated.
As for why I am manually converting this file to ARC, the Edit->Refactor->Convert to Objective-C ARC... menu option basically says "fix these 8 errors before I can continue" and these 8 errors are of course ARC-related errors. Which I was hoping the conversion would be able to resolve. Circular dependencies are only fun the first time around.
The curious bit is that -initWithContentsOfFile:path is right there in the file too:
- (id)initWithContentsOfFile:(NSString *)path
{
if (self = [super init])
{
NSData *data = [NSData dataWithContentsOfFile:path];
_imageData = [[NSMutableArray alloc] initWithCapacity:10];
_name = 0;
_width = _height = 0;
_internalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
_hasAlpha = FALSE;
if (!data || ![self unpackPVRData:data] || ![self createGLTexture])
{
self = nil;
}
}
return self;
}
Note: This code compiles if I change the + to a - on the pvrTextureWithContentsOfFile declaration. I am positive the original code had a + there, so please, somebody help explain this to me.
alloc is a class method. You usually see alloc used from another class, with things like:
NSView *myView = [[NSView alloc] initWithFrame: someRect];
In a class method, self is the class, not an instance of that class. So [self alloc] allocates an instance of the class. It would also be valid to use the name of the class explicitly, so in the case of your PVRTexture class,
return [[self alloc] initWithContentsOfFile:path];
Could be replaced with
return [[PVRTexture alloc] initWithContentsOfFile:path];
Both are perfectly valid. I would probably use the second form, just because, like you, I find the first form a little odd-looking.
I got it, I think. Here's another example where writing out the question leads to the answer.
It's this:
return [[self alloc] initWithContentsOfFile:path];
I'm sure Google will be happy enough to (let SO) show me what alloc actually does. I guess calling [self alloc] from a class method is essentially what makes that method into a factory method that can generate instances of that class. This is nothing other than the Obj-C way to new something.
Me, I made the mistake of assuming alloc was something that ARC abolished.
Also, a side note: Reading carefully helps. I also just found out about GLKTextureLoader so I didn't need to convert PVRTextureLoader at all.

UITableView Setup

I have an unsorted NSArray of names. I know how to build a static table, but I am not sure how to make a dynamic table that will have a section with the first letter of the name and then the names for that letter in put into each section. I know this sounds like a beginner level programming question, and well it is. Any help would be great. New to iOS programming so I am just trying to get a feel for everything.
You can do this easily with TLIndexPathTools using the block-based data model initializer:
NSArray *items = ...; // and array containing your unorganized data (items of type NSString assumed here)
TLIndexPathDataModel *dataModel = [[TLIndexPathDataModel alloc] initWithItems:items sectionNameBlock:^NSString *(id item) {
return [[((NSString *)item) substringToIndex:1] upperCaseString];
} identifierBlock:nil];
The sectionNameBlock argument is a block that returns the first letter of the given item, which the initializer uses to organize the data into sections. Then you'd use dataModel (instead of an array) in your data source and delegate methods using the various APIs like [dataModel numberRowsInSection:], [dataModel itemAtIndexPath:], [dataModel indexPathForItem:], etc.
Try running the "Blocks" sample project for a working example.

Where to put several hundred array declarations for best performance in iOS?

I have the need for a 2-dimensional look-up table for my app. I wanted to do this as simply as possible, so I just made a series of arrays, and then will just call the appropriate array and position within that array whenever needed. However, this takes about 500 arrays... here is a code snippet:
NSArray *key_1 = [[NSArray alloc] initWithObjects:#"1",#"0.5038",#"0.8054",#"4.51",nil];
NSArray *key_2 = [[NSArray alloc] initWithObjects:#"1",#"0.4869",#"0.8009",#"4.7",nil];
NSArray *key_3 = [[NSArray alloc] initWithObjects:#"1",#"0.4708",#"0.7967",#"4.9",nil];
NSArray *key_4 = [[NSArray alloc] initWithObjects:#"1",#"0.4554",#"0.7926",#"5.09",nil];
NSArray *key_5 = [[NSArray alloc] initWithObjects:#"1",#"0.4407",#"0.7889",#"5.27",nil];
NSArray *key_6 = [[NSArray alloc] initWithObjects:#"1",#"0.426",#"0.7853",#"5.46",nil];
NSArray *key_7 = [[NSArray alloc] initWithObjects:#"1",#"0.4133",#"0.7819",#"5.65",nil];
However, I have two concerns. First, if these 500 array declarations cause a momentary hang in the app, where should I put this code so that it executes while still on the app's splash screen when first starting up? Would that be in viewWillAppear?
Second (and this is probably more of a newb question), if I place these array declarations in viewWillAppear or some other method in my FirstViewController.m, how can I make them accessible to methods in my SecondViewController.m?
Thanks!
So you have two options
First: You can load these arrays on different thread (not on main thread). You can use GCD to create a new thread and pass a these array as blocks to work on. By doing this you can solve your first problem "momentary hang".
Second: As Mrunal mentioned "Try using plist files for this. Write your data in that in dictionary format (Key-Value). And fetch only required data using specific key."
I like to option number two but if you have dynamic array please use option number one.

OCMock. Why does the second of these test pass?

I am getting up to speed of mock-ing with iOS using OCMock. I wrote a simple mock-ing unit test that passes when - based on the expectation I set - it should fail.
Here is the code:
// Scientific Calculator class
#implementation ScientificCalculator
- (NSNumber *)sqrt:(NSNumber *)input {
return [NSNumber numberWithDouble:sqrt([input doubleValue])];
}
#end
// unit test
-(void) testScientificCalculatorSqrt {
NSNumber *input = [NSNumber numberWithDouble:4];
ScientificCalculator *scientificCalculator = [[ScientificCalculator alloc] init];
NSNumber *expected = [NSNumber numberWithDouble:2];
NSNumber *result = [scientificCalculator sqrt:input];
STAssertEquals([result doubleValue], [expected doubleValue], nil);
}
// unit test
-(void)testScientificCalculatorMocking {
// setup
NSNumber *input = [NSNumber numberWithDouble:44];
id scientificCalculator = [OCMockObject mockForClass:[ScientificCalculator class]];
// expect
NSNumber *expected = [NSNumber numberWithDouble:2];
[[[scientificCalculator expect] andReturn:expected] sqrt:input];
// test
NSNumber *result = [scientificCalculator sqrt:input];
// verify
STAssertEquals([result doubleValue], [expected doubleValue], nil);
[scientificCalculator verify];
}
The testScientificCalculatorMocking unit test uses 44 as input to sqrt. I set the expected values to 2. Both STAssertEquals and [scientificCalculator verify] pass successfully.
Can someone please explain what I am doing wrong here?
Thanks,
Doug
You've told the mock to return 2 (expected) when sqrt is called with 44 (input). The result variable will therefore contain the value 2. You're then comparing result (2) and expected (2), which will pass.
Your first test is the correct way to test something like this. There's no reason to mock your calculator, because its behavior is what you're testing.
Generally you mock things that are orthogonal to what you're testing and may have side effects if you execute them in your test. For example, say you had a method that calculated the square root of Apple's stock price. You might mock the web service call to retrieve the stock price, so your test doesn't make network calls and the price is static in your test. Then you'd pass the pre-defined price to your calculator and verify that the square root value it gives you is correct.
As to stub vs. expect, expect means this method must be called once. stub means it may or may not be called, any number of times. In both cases, the andReturn part just defines what will be returned when the method is invoked on your mock. Remember, a mock is dumb, so you need to tell it what to return.

Resources