Can anyone give me an example about unit testing in iOS Swift? - ios

I am new in iOS development and want to learn unit testing. I want to add two numbers using text fields and show the result in a label when clicking on the action. Can anyone suggest me how to make a unit test for this?

This is a short example, but you should always try multple different variations as if it would be text instead of numbers and negative numbers, numbers that is higher than int etc...
class AddingViewController: UIViewController {
var sum = 0
let textField1 = UITextField()
let textField2 = UITextField()
let button = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
button.addTarget(self, action: #selector(addNumbers), for: .touchUpInside)
}
#objc func addNumbers() {
guard let number1 = Int(textField1.text!), let number2 = Int(textField2.text!) else {
return
}
sum = number1 + number2
}
}
class TestAddingViewController {
let vc = AddingViewController()
func testAddNumbers() {
let number1 = 1
let number2 = 2
let sum = number1 + number2
vc.textField1.text = "\(number1)"
vc.textField2.text = "\(number2)"
vc.button.sendActions(for: .touchUpInside)
XCTAssertEqual(sum, vc.sum)
}
}
If viewController is from storyboard you call it as you normally call storyboard viewControllers
let storyboard = UIStoryboard(name: "MyStoryboardName", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "someViewController")
And the textfields + button will be #outlets and the button action will be an #IBAction and not #objc func

Here is my view controller class:
import UIKit
class ViewController: UIViewController {
// MARK: ▼▼▼ IBOutlets ▼▼▼
#IBOutlet weak var lblAnswer: UILabel!
// MARK: ▼▼▼ Lifecycle methods ▼▼▼
override func viewDidLoad() {
super.viewDidLoad()
lblAnswer.text = "\(calculateSum(num1: 5, num2: 10))"
}
// MARK: ▼▼▼ Methods ▼▼▼
func calculateSum(num1: Int, num2: Int) -> Int {
return (num1 + num2)
}
}
And for the unit test class I have:
import XCTest
#testable import Test
class TestTests: XCTestCase {
// MARK: ▼▼▼ Variables ▼▼▼
var num1: Int?
var num2: Int?
var testClass: ViewController?
// This is used to initialise your code:
override func setUp() {
testClass = ViewController()
num1 = 10
num2 = 10
}
// This runs when test is complete:
override func tearDown() {
testClass = nil
num1 = nil
num2 = nil
}
// Should begin with test so that it is recognised:
func testSumFunction() {
let sum = testClass!.calculateSum(num1: num1!, num2: num2!)
// The sum should be 20 for this to pass.
XCTAssertTrue(sum == 20)
}
}
If the logic of the function is changed and answer is not equal to 20 then this test will fail.

Related

how can I add a addition feature into my Xcode project

I have a textfield in my firstViewContoller and thats where the user types in some numbers. Then in my secondViewController i have a label and in that label I want the number entered by the user to be shown. I have done till this but I dont know how I can take the value the user enters in the textfield every time and add it all up and show the result in the label. For example the user enters 5 so now the label is showing 5 then the user types in 20 and now i want the label to add that up and show 25 and keep on adding what the user enters.
// ViewController.swift
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var amountSpent: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
amountSpent.delegate = self
amountSpent.keyboardType = .numberPad }
private func amountSpent(_ amountSpent: UITextField,
shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let invalidCharacters = CharacterSet(charactersIn: "0123456789").inverted
return string.rangeOfCharacter(from: invalidCharacters) == nil }
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let resultViewController = segue.destination as! ResultViewController
resultViewController.recievedInt = amountSpent.text!
}
#IBAction func buttonPressed(_ sender: Any) {
self.performSegue(withIdentifier: "goToResult", sender: nil) } }
// ResultViewController.swift
import UIKit
class ResultViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var totalAmount: UILabel!
var recievedInt = ""
override func viewDidLoad() {
super.viewDidLoad()
totalAmount.text = recievedInt
}
}
Convert the new and the existing values from string to Int and add them together before setting the label to the new total
class ResultViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var totalAmount: UILabel!
var recievedInt = ""
override func viewDidLoad() {
super.viewDidLoad()
if let newValue = Int(recievedInt), let total = Int(totalAmount.text!) {
let newTotal = newValue + total
totalAmount.text = "\(newTotal)"
}
}
An alternative way to do it could be
override func viewDidLoad() {
super.viewDidLoad()
let newValue = Int(recievedInt) ?? 0
let total = Int(totalAmount.text!) ?? 0
let newTotal = newValue + total
totalAmount.text = "\(newTotal)"
}
Create a string variable to store the total value
var totalValue = "0"
Add the below method to your project. This will add all your entered values and will return the total value as a string.
private func getTotalCount(string: String) -> String {
let strTotal = "\(totalCount) + \(string)"
let arithmeticExpression = NSExpression(format: strTotal)
let additionResult = arithmeticExpression.expressionValue(with: nil, context: nil) as! Int
return String(additionResult)
}
You will click the done/return button after you entered the count in the text field, the delegate method will be triggered. Call the getTotalCount() method inside the delegate method as below:
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
totalCount = self.getTotalCount(string: textField.text ?? "0")
textField.resignFirstResponder()
return true
}
Thus adding new counts will be automatically handled inside the UITextField delegate method. You just need to pass the totalValue to the next View Controller like this:
let resultViewController = segue.destination as! ResultViewController
resultViewController.recievedInt = totalCount

Is it possible to toss data to another view controller?

I just Make timer that can use in life. just like image that I push in here, if I go back to main ViewController then I wanna the number that I input in set view controller are tossed to viewController so when I go back to main ViewController and press restart then that number gonna be in text of CountTimeLabel.. but I really don't know how to toss data that I input in another view controller to root viewController... pleas help me.. if I write code like ViewController().variableName = 30 in setViewController, that dose not make things well..(I already know about prepare function but that is not what I am finding..because this is happen when I go back to ViewController(RootViewController)) I will put my code in below..
is it possible to toss data to another view controller from other view controller?
import UIKit
class ViewController: UIViewController{
#IBOutlet var AllTileLabel: UILabel!
#IBOutlet var SumTimeLabel: UILabel!
#IBOutlet var CountTimeLabel: UILabel!
#IBOutlet var StartButton: UIButton!
#IBOutlet var StopButton: UIButton!
#IBOutlet var ResetButton: UIButton!
var timeTrigger = true
var realTime = Timer()
var second : Int = 3000
var sum : Int = 14400
var allTime : Int = 14400
var IntSecond : Int = 0
var ifReset = false
override func viewDidLoad() {
StartButton.layer.cornerRadius = 10
StopButton.layer.cornerRadius = 10
ResetButton.layer.cornerRadius = 10
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func StartButtonAction(_ sender: UIButton) {
if timeTrigger { checkTimeTrigger() }
print("Start")
}
#IBAction func StopButtonAction(_ sender: UIButton) {
endGame()
}
#IBAction func ResetButtonAction(_ sender: UIButton) {
print(second)
getTimeData()
//second = 3000
//CountTimeLabel.text = "0:50:00"
CountTimeLabel.text = printTime(temp: second)
ifReset = true
}
#IBAction func Reset(_ sender: UIButton) {
endGame()
timeTrigger = true
realTime = Timer()
second = 3000
sum = 14400
allTime = 14400
IntSecond = 0
ifReset = false
AllTileLabel.text = "8:00:00"
SumTimeLabel.text = "0:0:0"
CountTimeLabel.text = "0:50:00"
}
#objc func updateCounter(){
// if String(format: "%.2f",second) == "0.00"{
if second < 1 {
endGame()
CountTimeLabel.text = "종료"
} else {
second = second - 1
sum = sum + 1
allTime = allTime - 1
AllTileLabel.text = printTime(temp: allTime)
SumTimeLabel.text = printTime(temp: sum)
CountTimeLabel.text = printTime(temp: second)
print("update")
}
}
func checkTimeTrigger() {
realTime = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateCounter), userInfo: nil, repeats: true)
timeTrigger = false
}
func endGame() {
realTime.invalidate()
timeTrigger = true
}
func printTime(temp : Int) -> String
{
let S = temp%60
let H = temp/3600
let M = temp/60 - H*60
let returnString = String(H) + ":" + String(M) + ":" + String(S)
return returnString
}
func getTimeData() {
second = 20
sum = SetViewController().real.sum
allTime = SetViewController().real.allTime
print(second)
}
}
import UIKit
class SetViewController: UIViewController {
#IBOutlet var View1: UIView!
#IBOutlet var View2: UIView!
#IBOutlet var InputView1: UIView!
#IBOutlet var InputView2: UIView!
#IBOutlet var SetButton: UIButton!
#IBOutlet var H1TextField: UITextField!
#IBOutlet var M1TextField: UITextField!
#IBOutlet var H2TextField: UITextField!
#IBOutlet var M2TextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
H1TextField.keyboardType = .numberPad
M1TextField.keyboardType = .numberPad
H2TextField.keyboardType = .numberPad
M2TextField.keyboardType = .numberPad
View1.layer.cornerRadius = 14
View2.layer.cornerRadius = 14
InputView1.layer.cornerRadius = 10
InputView2.layer.cornerRadius = 10
SetButton.layer.cornerRadius = 10
// Do any additional setup after loading the view.
}
#IBAction func SetButton(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
enter image description here
If you're a hobbyist programmer and you just want to "get it done", simply use a static.
Let's say Bottom: UIViewController is the "main", root, view controller at the absolute "base" of your app. no matter what happens "Bottom" is always there.
Say Timer: UIViewController is (any) other view controller you put on top for some reason.
In Bottom, do this
class Bottom: UIViewController, etc ... {
static weak var current: Bottom? = nil
override func viewDidLoad() {
super.viewDidLoad()
Bottom.current = self
}
func testing() {
print("it works, WTH")
}
Note that in ViewDidLoad, you simply set it.
Next, say you are in Timer, try this:
class Timer: UIViewController, etc ... {
func someFunction() {
Bottom.current.testing() // it's that easy
}
It's that easy.
Note there is a huge amount of confusion about using statics, singletons, and similar approaches in iPhone programming.
(Just for example, many engineers will say "avoid singletons!" This is remarkably confused because in iOS engineering, almost everything is a singleton (notably the app itself (!!!!!), the screen, the GPS, etc etc.)
In any event, as a beginner hobbyist, learn how to use statics (it's simple .. Bottom.current. ... as above), and eventually you can learn about the pros and cons of such things.

Problems Updating View with Swift 5

I am having some trouble updating my secondViewController view in Xcode using Swift 5. I want my app to add two numbers together and show the result in the second ViewController. Although it works the first time, if I return to my previous view and change the numbers, the view does not update.
I tried using viewWillAppear, viewWillDisappear, amongst others, including NSNotificationCenter addObserve, but I have had no luck whatsoever.
Do you have any recommendations? Am I missing something?
Please see below for the code and a screenshot of my ViewControllers:
//
// ViewController.swift
//
import UIKit
var result = ""
var resultFinal = Float(result)
let finalResult = resultFinal!
class ViewController: UIViewController {
#IBOutlet weak var firstNumber: UITextField!
#IBOutlet weak var secondNumber: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func getResult()-> Float{
guard let fNumber = firstNumber.text else {
return 0
}
let firstFloat = Float(fNumber)
guard let sNumber = secondNumber.text else {
return 0
}
let secondFloat = Float(sNumber)
let sumNumber: Float = firstFloat! + secondFloat!
return sumNumber
}
#IBAction func submitSum(_ sender: Any) {
resultFinal = getResult()
print(resultFinal!)
}
}
//
// secondViewController.swift
//
import UIKit
class secondViewController: UIViewController {
#IBOutlet weak var test: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.test.text!=""
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
test.text = String(finalResult)
}
}
Screenshot:
Thanks.
Your problem is with the global variables. It seems from your code that you expect these three to reevaluate every time one of them changes:
var result = ""
var resultFinal = Float(result)
let finalResult = resultFinal!
For example, if you set resultFinal = 4, then finalResult will equal 4. However, those variables only evaluate once––the first time. You can simplify your use of these variables significantly. Replace these three with:
var result: Float?
Then, in ViewController:
class ViewController: UIViewController {
#IBOutlet weak var firstNumber: UITextField!
#IBOutlet weak var secondNumber: UITextField!
func getResult() -> Float {
guard let number1 = Float(firstNumber.text ?? "0") ?? 0
guard let number2 = Float(secondNumber.text ?? "0") ?? 0
return number1 + number2
}
#IBAction func submitSum(_ sender: Any) {
result = getResult()
}
}
Note: I simplified getResult and made it treat empty fields as 0.
In SecondViewController:
class SecondViewController: UIViewController {
#IBOutlet weak var test: UITextField!
override func viewWillAppear(_ animated: Bool) {
test.text = String(result ?? 0)
}
}
Note: self.test.text!="" doesn't really do anything, so I removed it.

Code crashing in during runtime

I am learning swift3 programming but after executing my calculator app its crashing in between. Please check the below code.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var aLabel: UILabel!
#IBOutlet weak var commmon_button: UIButton!
var a: Int?
var b: Int?
var sum: Int?
var val = ""
#IBOutlet weak var text_feild: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func clik_button(_ sender: UIButton) {
val=String(sender.tag)
text_feild.text = text_feild.text! + val
}
#IBAction func fn_addition(_ sender: UIButton) {
a = Int(text_feild.text!)
}
#IBAction func fn_answer(_ sender: UIButton) {
b = Int(text_feild.text!)
sum = a! + b!
a = 0
b = 0
text_feild.text = nil
text_feild.text = String(sum!)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
During run time i am getting crash at fn_addition by saying Thread 1 breakpoint 2.1
Initiliaze your variables as below
var a = 0
var b = 0
var sum = 0
Replace your methods with below methods.
#IBAction func clik_button(_ sender: UIButton) {
guard let value = String(sender.tag), let text = text_feild.text
else {
return
}
text_feild.text = text + value
}
#IBAction func fn_addition(_ sender: UIButton) {
guard let aValue = Int(text_feild.text)
else{
return
}
a = aValue
}
#IBAction func fn_answer(_ sender: UIButton) {
guard let bValue = Int(text_feild.text)
else{return}
b = bValue
sum = a + b
a = 0
b = 0
text_feild.text = ""
text_feild.text = String(sum)
}
Suggestion: You are using same text field for taking a , b values and for showing sum result. It is better to use two different text fields for taking a and b values separately. Take a label to show sum value.

Passing Data Through Segue & some errors

Hey guys I need some one to help me finish my app, I need to finish it before Dec 15. I'm making a Tip Calculator Project in Swift2 and It must have a settings view where I select the default tip rate. I have some issues with passing data, when I select a default tip percentage it doesn't change in the View Controller, also I want to make the app remember the default rate when I close the app and reopened. I will really appreciate that some one corrects my code and test it. Im new in this, below is the code of the two ViewControllers and a screenshot of the Main.Storyboard (Image 1) (ViewController Screenshot)
My apologies for my bad English, is not my native language
ViewController
import UIKit
class ViewController: UIViewController {
//Inputs
#IBOutlet weak var amountTextField: UITextField!
//Labels
#IBOutlet weak var TipPercentageLabel: UILabel!
#IBOutlet weak var numberOfPersonLabel: UILabel!
#IBOutlet weak var tipAmountLabel: UILabel!
#IBOutlet weak var totalBillLabel: UILabel!
#IBOutlet weak var billPerPersonLabel: UILabel!
//Slider & Stepper
#IBOutlet weak var tipSlider: UISlider!
#IBOutlet weak var personsStepper: UIStepper!
//Variables
var tipPercentage = 0.20
var numberOfPerson:Int = 1
let numberFormatter:NSNumberFormatter = NSNumberFormatter()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tipAmountLabel.text = "$0.00"
totalBillLabel.text = "Bill Total"
billPerPersonLabel.text = "$0.00"
TipPercentageLabel.text = "20.0%"
numberOfPersonLabel.text = "1"
self.amountTextField.becomeFirstResponder()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func setupContainer() {
tipSlider.minimumValue = 0
tipSlider.maximumValue = 100
tipSlider.value = 20
tipSlider.addTarget(self, action: "sliderTipChanged:", forControlEvents: .ValueChanged)
personsStepper.minimumValue = 1
personsStepper.maximumValue = 30
personsStepper.value = 1
personsStepper.addTarget(self, action: "sliderPersonChanged:", forControlEvents: .ValueChanged)
amountTextField.text = ""
refreshCalculation()
}
#IBAction func OnEditingFieldBill(sender: AnyObject) {
refreshCalculation()
}
func refreshCalculation() {
numberFormatter.numberStyle = NSNumberFormatterStyle.DecimalStyle
if let amount = numberFormatter.numberFromString(amountTextField.text!) as? Double {
let tipAmount = amount * tipPercentage
let totalBill = amount + tipAmount
let billPerPerson = totalBill / Double(numberOfPerson)
numberFormatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
tipAmountLabel.text = numberFormatter.stringFromNumber(tipAmount)
totalBillLabel.text = numberFormatter.stringFromNumber(totalBill)
billPerPersonLabel.text = numberFormatter.stringFromNumber(billPerPerson)
} else {
tipAmountLabel.text = "-"
totalBillLabel.text = "-"
billPerPersonLabel.text = "-"
}
numberFormatter.numberStyle = NSNumberFormatterStyle.PercentStyle
numberFormatter.minimumFractionDigits = 1
numberFormatter.maximumFractionDigits = 1
TipPercentageLabel.text = self.numberFormatter.stringFromNumber(tipPercentage)
numberOfPersonLabel.text = "\(numberOfPerson)"
}
#IBAction func sliderTipChanged(sender: AnyObject) {
tipPercentage = Double(round(tipSlider.value)) / 100
refreshCalculation()
}
#IBAction func StepperPersonChanged(sender: AnyObject) {
numberOfPerson = Int(round(personsStepper.value))
refreshCalculation()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let id = segue.identifier {
if id == "show settings" {
if let SettingsViewController = segue.destinationViewController as? SettingsViewController {
}
}
}
}
}
Settings View Controller
import UIKit
class SettingsViewController: UIViewController {
#IBOutlet weak var tipControl: UISegmentedControl!
var tipRates:Double?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func DefaultRate(sender: AnyObject) {
var tipRate = [5, 10, 15, 20, 25, 30]
var tipRates = Double(tipRate[tipControl.selectedSegmentIndex])
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let id = segue.identifier {
if id == "goBackToViewController" {
if let ViewController = segue.destinationViewController as? ViewController {
if let tip = tipRates {
ViewController.tipPercentage = tip/100
}
}
}
}
---- Edit from comments ----
I think the reason it is not updating as you would like is due to a minor error with this line.
var tipRates = Double(tipRate[tipControl.selectedSegmentIndex])
Inside of your DefaultRate action function for the UISegmentedControl
Using var is a redeclaration of the same variable name, thus what you are trying to pass in the prepareForSegue is an empty variable.
This function should be changed to:
#IBAction func DefaultRate(sender: AnyObject) {
var tipRate = [5, 10, 15, 20, 25, 30]
tipRates = Double(tipRate[tipControl.selectedSegmentIndex])}
Hopefully this will now solve the error.
---- End Edit ----
From what I can see in the viewDidLoad function of your viewController, you are setting the tip label, and not updating the value based on the variable var tipPercentage.
TipPercentageLabel.text = "20.0%"
is setting the value display to always be 20.0%, you could use this here.
var tipDisplay = tipPercentage * 100
TipPercentageLabel.text = "\(tipDisplay)%"
This should update the displayed value in the label, however you never call on your other functions to recalculate the amount etc.
Thus you should also be calling on
func setupContainer()
or
func refreshCalculation()
within your ViewDidLoad().
In terms of remembering the default value when the app is closed you should look into using NSUserDefaults.
Some information regarding NSUserDefaults can be found here, which explains implementing small amounts of saved data and can be implemented in your case quite simply.

Resources