I am trying to write a unit test case for calculator in Xcode, currently I am writing test case for the add function.
This is my digit pressed function:
- (IBAction)digitPressed:(UIButton *)sender.
I have seen an example where if the method name is
- (IBAction)digitPressed:(id)sender,
you can invoke the function with help of view tag like
[calc_view_controller digitPressed:[calc_view viewWithTag:6];
The instance defined above in implementation section as
app_delegate = [[UIApplication sharedApplication] delegate];
calc_view_controller = app_delegate.viewController;
calc_view = calc_view_controller.view;
now since my button type is (UIButton *) I cant use view tag, is there any other alternative for UIButton type?
If so, Can you give me an example?
A button is a view, so -viewWithTag: will find it just fine. The only issue is your sender type not agreeing with the type returned by -viewWithTag:, but you can solve that with a cast if you're sure the view you'll get back is a button, or you can check first:
[calc_view digitPressed:(UIButton*)[calc_view viewWithTag:6]];
or:
UIButton *button = (UIButton*)[calc_view viewWithTag:6]];
if ([button isKindOfClass:[UIButton class]]) {
[calc_view digitPressed:button];
}
Either works in practice; the latter is safer and makes it easy to add an additional test: you could fail the test if the button isn't a UIButton.
You do it in the bad way, you shouldn't compare sender.titleLabel.text. Think about what happens when you'll change the label text, because e.g you must ship your app in another language.
But if you still want to handle button recognizing in that way, you have to create UIButton in your unit test and set it the proper label text value.
Rather than invoking the IBAction method, I would invoke the specific method which peforms the addition.
There are a number of things that you could test here, for example:
The UIButton object sends the correct value to the digitPressed method
The digitPressed method correctly extracts the value from the UIButton object and passes this value to the add method
The result of the add method is what you expect
How much you test is up to you, personally I wouldn't get too hung up on 1 and 2. If the logic is wrong in either of these it will become obvious, and they are unlikely to attract regressions.
To me it is more important to fully test the add method. Send in plenty of common cases, edge cases and boundary values, rather than worrying about the flow from the UIButton press.
Related
A quote from Apple docs: "The sender parameter usually identifies the control sending the action message (although it can be another object substituted by the actual sender)."
How is this accomplished? The "it can be another object" part.
In my case I have a number of buttons that all target the same method which produces a popover with a graphic of a tape measure with a pointer to the number. I have a lot of labels I want to use this on and will put a small button with an icon next to each of them so the user can see the fractional equivalent. I need a way to tell the method the number, which is held by a different label in each case. Making "sender" the label would make this a simple thing.
You can pass any sender you want if you invoke the method from somewhere else in your code, but if your action method is being invoked from an action on the control, then the sender will be the control (i.e. UIButton) that invoked the method. You will need to associate the sensor with the meaning in your code. The tag property may be of use.
The quote you added from Apple is merely indicating that the actual sender can supply a different object if it wants to - but it doesn't mean that all objects have the ability to specify a different sender. In the case of a UIButton there is no way of specifying a different sender.
I'm trying to write unit tests for my view controller. It is the first view controller in my app and has a 'Account' button on the top left hand corner. Pressing this will present an action sheet, which, for now, has two buttons:
Logout
Change Passcode
I want to write tests for this functionality:
Pressing the 'Account' button should present Action Sheet.
The Action Sheet should have two buttons: 'Logout' & 'Change Passcode'.
Pressing the 'logout' button should log the user out.
Pressing the 'change passcode' button should present the passcode view controller in change passcode mode.
The problem is, if I trigger the Account button in my test, it will try to present an action sheet, which fails because the view of the controller under test is not part of a window, and that means I can't write any of the other tests either.
There are proposed solutions on testing alert views and action sheets, but they require creating a PONSO with the same interface as UIActionSheet, and doing something like this in my view controller:
// in the .h file
#property (nonatomic, strong) Class actionSheetClass;
// in the .m file
// after button is pressed...
self.actionSheetClass actionSheet = [[self.actionSheetClass alloc] init...];
This is a very unnatural way of writing code - one of those times when you twist your code out of shape just to make it testable. I get better test coverage at the expense of readability. I'd rather have my cake, eat it, and then bake some more cake and eat that too.
Does anyone know how I can test my UIActionSheet-based behaviour without resorting to such shenanigans?
Update
After Michał Ciuba's comment, I started exploring how I can use OCMock to test all the things I want to test. The problem with the approach in that thread is that the number of buttons cannot be tested, neither can their actions. The culprit is the nil-terminated argument list. Even with all the acrobatics that the Objective-C runtime makes possible, it's actually impossible to test the buttons and their actions.
This is why:
You can't show an action sheet because your tests don't have a view that is being displayed. So if the view controller uses some code like
-(void) showActionSheet
{
UIActionSheet* actionSheet = [[UIActionSheet alloc] initWith...];
[actionSheet showInView:self.view];
}
That is to say, if it doesn't hold a reference to its action sheet, you also won't be able to get a reference to the action sheet in your tests without some mocking. No reference, no checking buttons.
You can't stub the initWithTitle:delegate:cancelButtonTitle:destructiveButtonTitle:otherButtonTitles: method and check its arguments because of the nil-terminated arguments.
I tried to use Peter Steinberger's Aspects library to add an "after hook" to the that method, but the nil-terminated arguments again cause problems here because Aspects uses NSInvocation to pass the message onto the original method, which means attempting to access anything past the first item in a variable argument list will cause an EXC_BAD_ACCESS.
Swizzling the init method? That might be an option but I haven't tried yet and won't have time to for a while.
Is that really worth the effort? Certainly not, but I think it should be testable and that it shouldn't take this much effort. It's definitely been educational.
What's the difference between declaring a UIButton in Xcode like this:
- (IBAction)testButton;
and declaring a button like this:
- (IBAction)testButton:(id)sender;
I understand that in the .m file you would then implement the buttons accordingly, as shown below:
- (IBAction)testButton
{
// insert code here..
}
and setting it up like this:
- (IBAction)testButton:(id)sender
{
// insert code here..
}
Is there any additional things you can do by declaring the button with :(id)sender, is there some additional stability, or is there no difference?
With :(id)sender you are able to access the button itself through the sender variable. This is handy in many situations. For example, you can have many buttons and give each a tag. Then use the [sender tag] method to find which button was tapped if many buttons are using this IBAction.
- (IBAction)someMethod:(id)sender {
// do stuff
}
Using (id)sender, you have a reference to who sent the method call. Please note, this doesn't have to be limited to a UIButton.
If you're created this method via control-dragging from the storyboard an only hooking up a single button, then sender is basically useless (it will always be the same), and should probably be marked as unused:
#pragma unused (sender)
(The compiler can better optimize your code if you do this.)
However, there's nothing wrong with hooking up several UI elements to the same IBAction method. You can then distinguish the sender via:
[sender tag]
...which returns an int that was either set via the storyboard or programmatically.
Moreover, you can call this method elsewhere in your class. You can either pass nil as the sender, or you can pass it a particular UI element in order to force it into the results you've coded for objects of that tag.
Nonetheless, if you plan to call the method with a nil argument, you can always throw:
if(!sender)
... into the method in order to handle special logic for when the method has been invoked programmatically as opposed to via user interaction.
It allows you to know which button you are working with. I have posted a simple example for a card game below
- (IBAction)flipCard:(id)sender {
[self.game flipCardAtIndex:[self.cardButtons indexOfObject:sender]];
self.flipCount++;
[self updateUI];
}
This method is used for a card flipping game. There are multiple buttons on the screen representing different cards. When you hit the button, a card in the model must be flipped. We know which one by finding the index of the variable sender
I'm still quite the beginner at iOS and so I've been doing lots of tutorials, lately.
Let's say I was making an app such as a calculator, with let's say 24 buttons. I've seen example code where the button's label gets used to figure out what button it is, but that seems really kludgey, especially when trying to translate the app.
So is it better to just bite the bullet and have one IBOutlet for each and every button instead? why or why not?
If not, what would be the most elegant way to go about doing this, while staying in the MVC paradigm?
Ok I just was looking back at my code and now i feel more like a noob than before... I really was talking about IBActions, not so much IBOutlets... Should I have a whole bunch of IBActions for the different buttons? here's what it looks like right now in the viewController.h file:
- (IBAction)digitPressed:(UIButton *)sender;
- (IBAction)operationPressed:(UIButton *)sender;
- (IBAction)dotPressed:(UIButton *)sender;
- (IBAction)button_mClear_Pressed:(UIButton *) sender;
- (IBAction)button_mPlus_Pressed:(UIButton *) sender;
- (IBAction)button_mMinus_Pressed:(UIButton *) sender;
- (IBAction)button_mRecall_Pressed:(UIButton *) sender;
- (IBAction)button_AC_Pressed:(UIButton *) sender;
- (IBAction)button_PlusMinus_Pressed:(UIButton *) sender;
why does that just feel repetitive and inelegant to me?
Typically, you'd have similar buttons all trigger the same action, the idea being that similar button actions should have some common code between them. You then use the tag property to identify which button was clicked. E.g., number buttons trigger a specific action, operator buttons trigger another action, and so on.
- (void)didClickOperatorButton:(id)button
{
switch ([button tag])
{
case kAdditionOperation:
// Do the addition operation ...
// etc..
You can set the tag property on any control in Interface Builder.
If you use IBOutlets and wire them up in Interface Builder/Xcode 4 is more a matter of taste, than a programming decision. And doing so or not does not necessarily affect the mvc paradigm.
It is your choice, if you keep 24 IBOutlets in your viewcontroller and load the buttons from a nib, as it is maybe easier to arrage them in your interface, or to have an array full of buttons, and add them to your view programmatically and set them up with the right actions.
You can also have the buttons in different nibs for different viewcontroller — lets say for the number pad, the simple commands and the higher commands and functions. each of the viewcontrollers would have a delegate of a certain protocol, which all would be implemented by on 'BrainController'.This setup might be a bit overkill for a simple calculator, but would allow you to use nibs, without a viewcontoller overcrowded with IBOutlets. And you could re-use oarts of it in other project, i.e. the numberpad in an app with a remote control interface.
If you use XIB's and a lot of objects, then yes. If you plan on making the object do something special like disable the button during some method call later in code, then YES, hook up an IBOutlet. If you are only connecting the buttons to IBActions, then NO, just connect any button (without IBOutlet) to your IBAction, this will save you on connecting a bunch of objects.
Say for instance I have an IBAction that is hooked up to a UIButton in interface builder.
- (IBAction)functionToBeCalled:(id)sender
{
// do something here
}
With-in my code, say for instance in another method, what is the best way to call that IBAction?
If I try to call it like this, I receive an error:
[self functionToBeCalled:];
But, if I try to call it like this (cheating a bit, I think), it works fine:
[self functionToBeCalled:0];
What is the proper way to call it properly?
The proper way is either:
- [self functionToBeCalled:nil]
To pass a nil sender, indicating that it wasn't called through the usual framework.
OR
- [self functionToBeCalled:self]
To pass yourself as the sender, which is also correct.
Which one to chose depends on what exactly the function does, and what it expects the sender to be.
Semantically speaking, calling an IBAction should be triggered by UI events only (e.g. a button tap). If you need to run the same code from multiple places, then you can extract that code from the IBAction into a dedicated method, and call that method from both places:
- (IBAction)onButtonTap:(id)sender {
[self doSomething];
}
This allows you to do extra logic based on the sender (perhaps you might assign the same action to multiple buttons and decide what to do based on the sender parameter). And also reduces the amount of code that you need to write in the IBAction (which keeps your controller implementation clean).