Gentle Introduction To Unit Testing.
5 (1)

Click to rate this post!
[Total: 1 Average: 5]
Gentle Introduction To Unit Testing (created with AI)

One Monday morning, some new developer (Penguin 🐧) started their first new job as a software engineer, the chat between two developers (Penguin 🐧) and their team leader (Rex 🦖) went like this.

(Penguin 🐧): I have noticed that we have test cases written for almost all features in our mobile app, why would we write unit tests if we have QA team that does the testing and quality assurance?

(Rex 🦖): Yes, writing unit tests does not only guarantee that features behave correctly just after being developed / shipped, it also guarantees that when someone write any relevant code of new feature, it does not break any existing or any piece of code.

the QA team can never test everything all over again when some new feature is introduced, it’s like an investment, you spend extra time writing unit tests during developing a feature, but prevent any potential bugs from happening in the future.

A CI/CD job running all tests will prevent any developer from merging a code that breaks an existing feature (in case that feature has well written tests), there are other benefits, like tests can be a good documentation too for anyone intending to read your code.

(Penguin 🐧): my first function I wrote is about (application force update) checking, it compares a string that resembles an application version like 1.0.2, and compares it with another version like 1.1.2 to check if the app needs force update or not.

so my responsibility would be checking against all the values of minimum value 000.000.000 up to 999.999.999 value, for both target and current versions, so my test function should iterate through all possible cases, right?

(Rex 🦖): No!!, the idea of test cases, is that covers edge cases, and maybe un-expected cases like minus numbers in this example, and maybe few random usual cases, maybe have these test functions…

testWhenCurrentVersionIsLessThanRequiredVersionRequiresUpdate
testWhenRequiredVersionIsEqualToCurrentVersionRequiresNoUpdate
testMaximumMajorNumberComparesCorrectly
testMaximumMinorNumberComparesCorrectly
testMinimumPatchNumberComparesCorrectly
testMinimumMajorNumberComparesCorrectly
testMinimumMinorNumberComparesCorrectly
testMinimumPatchNumberComparesCorrectly
testMinimumNumbersComparesCorrectly

…. + some random normal cases…

keep in mind, the naming convention should show the intention of the developer, even if the test function name becomes lengthy.

covering all the cases, will cause tests to take long time, maybe few minutes in your case, you must totally avoid that, remember the unit tests criteria? the way you do it will break the first criteria “Fast”, the normal time for tests is something like 50ms or something.

Unit Tests Criteria “F.I.R.S.T”:
Fast: we can run dozens of them in a second, if not more
Isolated: should not depend on each other, or any external state.
Repeatable: they should always give the same result when they are run, like a pure function.
Self-Verifying: the test must unambiguously say whether it passed or failed, with no room for interpretation.
Timely: they should be written before or alongside the production code that you are testing.


(Penguin 🐧): But we must have high test coverage, like 100% coverage to cover all cases, don’t we?

(Rex 🦖): Test coverage means coverage on the code logic itself, like the percentage of lines tested, not on the “possible values coverage”, and by the way, test coverage is a flawed metric, it only means we have test functions that call our code, it does not mean that the test functions are good.

(Penguin 🐧): Good, yeah, any other benefits for having unit tests?

(Rex 🦖): Testing reduces maintenance costs and therefore quantity of bugs, there are also other costs to consider like customer impact, the longer an issue goes undiscovered, the more expensive it is, which can result in negative reviews & lost trust, and of course lost money!

(Penguin 🐧): But why follow TDD (Test Driven Development) methodology? why write the tests before writing the feature itself?

(Rex 🦖): There are a lot of other development methodologies, like TDD, ATDD, DDD, BDD, .. these are lengthy topic, I encourage you to read about them quickly, and the RGR lifecycle of TDD.

(Penguin 🐧): That’s really cool, how can I make sure my code is testable? and what makes it not?

(Rex 🦖): You may consider architectural patterns, that make code more separated and easily tested, like MVVM, VIPER, VIP, …, FRP may make your code easier to test, using dependency injection, and coordinator pattern, using pure functions, etc…

(Penguin 🐧): So I always need to mock stuff when testing, right?

(Rex 🦖): No, Mocks are type of test doubles, there are also Fakes, Stubs, Spies, Dummies, look them up, and know when to use each, they are so confusing at first.

When we say test doubles, the name is derived from stunt doubles
(Penguin 🐧): what other tips do you have?

(Rex 🦖): yes, there are a few on top of my head

- In network testing for mobile in general, no HTTP request should be made, you test the networking feature it self.

- tests run alphabetically, you should not rename your tests to change their order of running, remember that tests should be independent, changing the order intentionally will break this criteria.

- Xcode provides performance tests, that compares between previous runs, where you can also change the baseline, it also gives nicely formatted test coverage markers.

- Writing no tests is better than writing flaky tests!


Refresher: Problem Solving (1-4)
5 (2)

Click to rate this post!
[Total: 2 Average: 5]

After reading the very popular book, grokking algorithms,
Will be blogging about algorithms and data structures… the book is very informative and easy to digest.

Grokking Algorithms book

It’s advised you get yourself familiar with data structures before starting to solve problems… I will not go into details, my advice is to try to solve the problems without looking at the solutions

Problem: 1
return a pair of 2 distinct values (if any) that sum up to a target number, from a nonempty array that has distinct integers.

Different Solutions with different time complexities

// Time: O(n^2)
func solution1(_ array: [Int], _ targetSum: Int) -> [Int] {
    for i in 0 ..< array.count-1 {
        for j in i+1 ..< array.count {
            if array[i] + array[j] == targetSum {
                return [array[i],array[j]]
            }
        }
    }
    return []
}

// Time: O(n^2)
func solution2(_ array: [Int], _ targetSum: Int) -> [Int] {
    for i in array {
        for j in array {
            if (i != j) && targetSum == (i + j) {
                return [i,j]
            }
        }
    }
    return []
}

// Time: O(n*log(n))
func solution3(_ array: [Int], _ targetSum: Int) -> [Int] {
    let sorted = array.sorted()
    var leftPointer = 0
    var rightPointer = sorted.count - 1
    while leftPointer < rightPointer {
        let leftMost = sorted[leftPointer]
        let rightMost = sorted[rightPointer]
        let currentSum = leftMost + rightMost
        if currentSum == targetSum {
            return [leftMost, rightMost]
        } else if currentSum < targetSum {
            leftPointer = leftPointer + 1
        } else if currentSum > targetSum {
            rightPointer = rightPointer - 1
        }
    }
    return []
}

// Time: O(n)
func solution4(_ array: [Int], _ targetSum: Int) -> [Int] {
    var numberDictionary = [Int: Bool]()
    for number in array {
        let mayMatch = targetSum - number
        if let exists = numberDictionary[mayMatch], exists {
            return [mayMatch, number]
        } else {
            numberDictionary[number] = true
        }
    }
    return []
}

I’m not going to explain each code, you comment here if you have a question, will leave the analysis to you, doing a simple benchmark on a 100,000 values array, we can see these results

solution1: 31.88 s.
solution2: 18.41 s.
solution3: 0.38 s.
solution4: 0.20 s. 🏆


functions used for benchmarking

func printTimeElapsedWhenRunningCode(title:String, operation:()->()) {
    let startTime = CFAbsoluteTimeGetCurrent()
    operation()
    let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
    print("Time elapsed for \(title): \(timeElapsed) s.")
}

func timeElapsedInSecondsWhenRunningCode(operation: ()->()) -> Double {
    let startTime = CFAbsoluteTimeGetCurrent()
    operation()
    let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
    return Double(timeElapsed)
}

Problem: 2

Given 2 non empty arrays, write a function that determines if the second array is a subsequence of array 1.

⚠️: Keep in mind, subsequence is not the same as subarray.

// Time: O(n)
func isValidSubsequence_solution1(_ array: [Int], _ sequence: [Int]) -> Bool {
    
    // sequence is empty
    if (sequence.count == 0) {
      return false
    }
    
    // if arrays are equal, directly return true.
    if (array == sequence) {
        return true
    }
    
    // the sequence is larger than the array, return false.
    if (sequence.count > array.count) {
        return false
    }
    
    var arrIdx = 0
    var seqIdx = 0
    
    while arrIdx < array.count, seqIdx < sequence.count {
        if array[arrIdx] == sequence[seqIdx] {
            seqIdx += 1
        }
        arrIdx += 1
    }
    
    return seqIdx == sequence.count
}

// Time: O(n)
func isValidSubsequence_solution2(_ array: [Int], _ sequence: [Int]) -> Bool {
    
    // sequence is empty
    if (sequence.count == 0) {
      return false
    }
    
    // if arrays are equal, directly return true.
    if (array == sequence) {
        return true
    }
    
    // the sequence is larger than the array, return false.
    if (sequence.count > array.count) {
        return false
    }
    
    var seqIdx = 0
    
    for value in array {
        if seqIdx == sequence.count {
            break
        }
        if value == sequence[seqIdx] {
            seqIdx += 1
        }
    }
    return seqIdx == sequence.count
}

test results for these arrays

let myArray1 = Array(stride(from: -900005, through: 900005, by: 1))
let myArray2 = Array(stride(from: -900000, through: 900000, by: 1))

Time elapsed for solution1: 28.102 s.
Time elapsed for solution2: 14.446 s. 🏆

can you guess why Solution 2 is better, even though they have same time complexity? 🤓

Problem 3:

Write a function that takes in a non-empty array of integers that are sorted in ascending order and returns a new array with the squares of the original integers also sorted in ascending order.

let me add 4 solutions along with explanation.

// Bad solution, appending is expensive, it's better to init an array with the length
func sortedSquaredArray_solution1(_ array: [Int]) -> [Int] {
    var sortedSquares = [Int]()
    for value in array {
        sortedSquares.append(value * value)
    }
    return sortedSquares.sorted()
}

// Time: O(nlog(n)) | Space O(n)
func sortedSquaredArray_solution2(_ array: [Int]) -> [Int] {
    var sortedSquares = Array(repeating: 0, count: array.count)
    for (idx, value) in array.enumerated() {
        sortedSquares[idx] = value * value
    }
    return sortedSquares.sorted()
}

// same as before, but higher order functions is tuned for high performance
func sortedSquaredArray_solution3(_ array: [Int]) -> [Int] {
    return array.map { $0 * $0 }.sorted()
}

// Time: O(n) | Space O(n)
func sortedSquaredArray_solution4(_ array: [Int]) -> [Int] {
    var sortedSquares = Array(repeating: 0, count: array.count)
    
    var smallerValueIdx : Int = 0
    var largerValueIdx : Int = array.count - 1
    
    for idx in stride(from: array.count - 1, through: 0, by: -1) {
        let smallerValue = array[smallerValueIdx]
        let largerValue = array[largerValueIdx]
        if abs(smallerValue) > abs(largerValue) {
            sortedSquares[idx] = smallerValue * smallerValue
            smallerValueIdx += 1
        } else {
            sortedSquares[idx] = largerValue * largerValue
            largerValueIdx -= 1
        }
    }
    return sortedSquares
}

for the following input

let myArraySortedSquares = Array(stride(from: -5000000, through: 5000000, by: 1))

Time elapsed for solution1: 6.786 s.
Time elapsed for solution2: 6.275 s.
Time elapsed for solution3: 5.106 s.
Time elapsed for solution4: 1.637 s. 🥇

Problem 4:

2 chess teams, competed for 1,000,000 times 🧐

given a 2D array of matches [host, guest]

Example
[[“Nepomniachtchi”, “Grischuk”], [“Karjakin”, “Grischuk”], [“Nepomniachtchi”, “Keymer”], [“Ding Liren”, “Grischuk”], [“Karjakin”, “Andreikin”], [“Carlsen”, “Gukesh D”], [“Aronian”, “Gukesh D”], [“Carlsen”, “Andreikin”], [“Nepomniachtchi”, “Gukesh D”], [“Aronian”, “Gukesh D”]]

and an array of results, where 1 means host team won
Example
[1, 1, 0, 0, 0, 0, 0, 1, 1, 1]

find the winning player, 

** for sake of simplicity, assume there is no draw in total points between players.

import Foundation

let HOST_TEAM_WON = 1
let WIN_POINTS = 1

// O(n) time | O(k) space , where n: are matches and k is the number of teams
func chessWinner(_ matches: [[String]], _ results: [Int]) -> String {
    var bestPlayer = ""
    var scores = [String: Int]()
    scores[bestPlayer] = 0
    for (idx, match) in matches.enumerated() {
        let (host, guest) = (match[0], match[1])
        let winning = (results[idx] == HOST_TEAM_WON) ? (host) : (guest)
        if scores[winning] == nil { scores[winning] = 0}
        scores[winning] = scores[winning]! + WIN_POINTS
        if scores[winning]! > scores[bestPlayer]! {
            bestPlayer = winning
        }
    }
    return bestPlayer
}


func generateData() -> ([[String]] , [Int]) {
    
    let players1 = ["Carlsen", "Ding Liren", "Nepomniachtchi", "Karjakin", "Aronian"]
    let players2 = ["Keymer", "Vitiugov", "Gukesh D", "Andreikin", "Grischuk"]

    var matches = [[String]] ()
    var results = [Int]()

    let possibleResults = [0,1]
    
    for _ in 0 ..< 10 {
        matches.append([players1.randomElement() ?? "", players2.randomElement() ?? ""])
        results.append(possibleResults.randomElement() ?? 0)
    }
    print(matches)
    print(results)
    
    return (matches, results)
}

func problem_04_solutions() {
    let data = generateData()
    printTimeElapsedWhenRunningCode(title:"solution1") {
        let winner = chessWinner(data.0, data.1)
        print(winner)
    }
}

iOS/Android Developer Security Basics
5 (4)

Click to rate this post!
[Total: 4 Average: 5]
Unsplash.com

I couldn’t find a single place that covers the mobile developer security (must know) basics, this will be again like a chat between two developers (Lulu 👩🏼‍💻) and (Sam 🦖)

👩🏼‍💻: What are the risks of not having good security precautions? I can see we spend big money on security.
🦖: There are over 500 reported incidents of data-breach each year, It’s estimated that each incident costs 3.5M~5.0M USD on average.
If you look at statistics, you can see that remote jobs increased these costs by 15%, since attackers would have more opportunities of attacking a target that is spread on different locations.

👩🏼‍💻: When people talk about security, the server is being presented as the main defense line, why would a mobile developer be concerned with security if the server/website is secure?
🦖: Even if a website is securely developed and maintained, mobile apps still need to implement security best practices, while the server plays an important role, the client-side code in a mobile app can potentially expose users to risks if not developed securely, if the client is poorly written, you should expect a lot of attacks on the server.

Mobile apps have direct access to users’ sensitive data like location, contacts, files etc. on their devices, if an app is compromised, it could leak private user information, unlike browsers, mobile apps run locally on devices without the same protections of a browser sandbox, so vulnerabilities in app code could allow malicious actors to directly attack the device.

Many mobile attacks happen by exploiting vulnerabilities in how apps download and execute code. Developers need to ensure app updates and payloads come from verified, untampered sources.

As users spend more time in apps than browsers, mobile presents a larger attack surface. Security needs to be a priority throughout the development and deployment process on both frontend and backend.

👩🏼‍💻: But you are an iOS developer, and I develop for Android, would your tips and hints be applicable to Android too? oh, I almost forgot that both are based on Linux.
🦖: wait, they are not both based on Linux, iOS is a Unix-like Operating System which is based on Darwin(BSD) operating system, while Android is Linux-based Operating System and is an open source mobile operating system, there are a lot of precautions that are applicable on both OSs.

👩🏼‍💻: I was asking myself in the past, why not rely on HTTPS and that’s it?
🦖: HTTPS protects your data in transit between your client application to the server, but only if you can validate the TLS certificate has not been compromised.

👩🏼‍💻: What are the mobile weak points? what can be hacked?
🦖: Network , Disk, USB Port, etc…

👩🏼‍💻: That looks like a lot of terminology, can you explain the basics?
🦖: You will be good to go if you know these as a start.


TermBasic Explanation.
Authentication establish a user’s identity
Authorizationgrant a user an access to a resource, look up the difference between Authorization and Authentication
Cryptographythe study of concepts like Encryption, decryption.
Encryptionmeans of securing digital data using one or more mathematical techniques, along with a password or “key” used to decrypt the information
Decryptionthe conversion of encrypted data to its original form.
Hashinga hash function is any function that can be used to map data of arbitrary size to fixed-size values, used mainly for fingerprinting.
Forensicsa branch of digital forensic science pertaining to evidence found in computers and digital storage media.
Sniffingprocess of monitoring and capturing all data packets passing through given network
HTTPSHypertext Transfer Protocol Secure, a web protocol
SSLSecure Sockets Layer
TLSTransport Layer Security (TLS)
IP SpoofingIP spoofing is the creation of Internet Protocol (IP) packets which have a modified source address in order to either hide the identity of the sender
Reverse Engineeringis a process in which software, machines, aircraft, architectural structures and other products are deconstructed to extract design information from them.
MITMMan in the middle, it’s a type of a cyber attack where they secretly relay and possibly alter the data between two parties.
XSSCross Site Scripting (XSS)
SQL Injection:Read About it here.
OWASPOpen Web Application Security Project (OWASP)
MASVSMobile Application Security Verification Standard, it’s a sister project of OWASP Mobile Security testing guide.
Binarya non-text encoded file
Mach-O binaryUnder NeXTSTEP, OPENSTEP, macOS, and iOS, multiple Mach-O files can be combined in a multi-architecture binary
Basic Terminology


👩🏼‍💻: Why would people with a jailbroken device be a threat on the app itself, a jailbreak only make the user hackable, right?
🦖: No, The best practice, is to prevent people with Jailbroken devices from running your app, see my x04_checker repo, iOS Developers can use it to prevent jailbroken devices from running their applications.

Jailbreaking is the process of exploiting the flaws of a locked-down electronic device to install software other than what the manufacturer has made available for that device. It allows the owner to gain full access to the root of the operating system and access all the features, you must know that 100% jailbreak detection is not possible, we need to make (bypassing jailbreak detection) time-consuming, because an attacker can steal the developer info, and thus steal the app info.

Say a banking app has insufficient security on a jailbroken phone that is compromised, it could result in costly consequences for the banking app operator. For instance, if a user’s money is stolen due to their mobile banking app being hacked as a result of the jailbroken phone, the bank may need to reimburse the user even though it was not necessarily the bank’s fault.

This is because banks often guarantee protection of users’ money from theft, so a breach of security on the mobile app that enables theft could trigger the bank’s reimbursement responsibilities regardless of the root cause being the jailbroken phone.

Similarly, social media or other apps with user accounts could face defamation lawsuits if inadequate security on a compromised mobile app allows hackers to use a user’s account without their consent due to the jailbroken phone.

In such scenarios, the app operator may have to bear costs due to reputational or legal risks, despite the primary vulnerability being the jailbroken phone.

👩🏼‍💻: They say debugging and print statements can be a security vulnerability, is this true?
🦖: Print normally only works on development builds, print will still send the data to the usb interface.

As you mentioned print, It’s a heavy command though, even if your debugging device is not connected in debug mode with Xcode, leaving print statement of heavy objects might make your app slower during development, but anyway, this is not our topic.

Keep in mind, NSLog will be left even in Distribution builds, any normal user can see the logs using the macOS console app, I used them when debugging opening the app using a notification when it’s terminated, this is not easy to do in Xcode directly, anyway, just use NSLog carefully, and treat the error log files with care, don’t log sensitive data, or leave trace of code symbols.

With regards to logging on mobile apps, a common security concern is the potential leakage of personally identifiable information (PII) about users. Sensitive data like users’ names, financial account details, physical locations and other personal information could inadvertently be written to log files if not properly scrubbed or encrypted.

This exposes the risk of PII being accessed and misused by unauthorized parties who gain access to the device’s logs. Such access could occur either after the fact if the device is stolen, or even in real-time if a hacker manages to achieve remote access. Proper handling of logging is important to prevent such PII leaks that can negatively impact users’ privacy and security.

👩🏼‍💻: What about certificate pinning?
🦖: Certificate pinning simply prevents people from having the victim installing an incorrect certificate and making altered requests.

When implementing TLS pinning, bundling the certificate within the mobile app may not be the most suitable approach on its own. While including a default certificate is reasonable, the app should also incorporate the ability to dynamically update the pinned certificate if the server’s certificate expires or needs revocation. Any bundled certificate would also need to be encrypted in a way that prevents attackers from simply replacing the file on the device.

This is to ensure the certificate can be refreshed securely over time, while an attacker cannot tamper with the pinned certificate to circumvent TLS validation. A more robust TLS pinning implementation would account for eventual certificate changes on the server through a secure update mechanism within the app.

👩🏼‍💻: What about sensitive static strings?
🦖: Never have your sensitive strings inside plist or any asset file, this is the most basic tip you should never miss.
👩🏼‍💻: Wait Sam, I downloaded a facebook apk online, and ran a simple command to scan the binary for strings that look similar to google api keys, that begin with AIz, and I can see this, any explanation why this is not hidden in the binary?

 strings targetFile | grep "targetString"

using the strings command on macos

🦖: There are multiple ways google makes sure that a request is authentic, from comparing hashes, to domain binding to bundle ids etc… again, you need to have your sensitive data hidden, this is not an excuse.

👩🏼‍💻: Why is it said to be risky to use 3rd party libraries when developing sensitive apps like banks?
👨🏻‍💻: In normal cases, libraries are fine, just make sure that they are being audited, NPM libraries had a lot of these, for sensitive apps, it’s better to avoid them altogether.

👩🏼‍💻: If I follow all of these tips, I will be completely secure?
🦖: The sad news is, No, there are a lot of other attacks, like URL-scheme attacks, when you define a url scheme, your app responds to that scheme, so for instance, you create a scheme myapp://. all links starting with your scheme will directly launch your app. A universal link doesn’t include your scheme in the url but they will still launch your application, apple allows other apps to the register the same url.
👩🏼‍💻: the alternative is universal links?
🦖: correct!

👩🏼‍💻: That was a huge amount of info for a starter, any final thoughts?
🦖: 100% Security for any Application is not possible but we can try to make the attacking/cracking of iOS App as much harder as possible.

Security is not always about “make accessing the data impossible”. sometimes it is about cost and likelihood of retrieval versus importance of the data.

More tips on top of my head:

  • Avoid Objective-C if possible, it’s very easy to reverse engineer.
  • Don’t write sensitive data into logs.
  • Disable Keyboard caching (3rd party keyboard)
  • Password/Pin should not be exposed during user interaction – Don’t include sensitive data in backup.
  • User Credentials should be stored in (keystore, keychain…).
  • App Transport Security – Certificate Pinning.
  • Integrity Check.
  • Prevent Running on rooted devices.
  • Detect JailBreak, (Jail Monkey library).
  • Use WebView carefully.
  • Securing Data on disk.
  • Securing Data across a network connection.
  • Secure application logic.
  • prevent Reverse Engineering (even if there is no sensitive data, if your app is novel, you don’t want people to see the code)
  • Don’t share sensitive data with 3rd party
  • Avoid keywords like (“jail”,”security”,”cydia”,”apt”) in variable & function names
  • Hide the JB check deep in the app, not in appdelegate…
  • Some Jailbreaks are permanent, and are done by exploiting security flaws in hardware.