Testing codepaths for older OS versions - swift

If I have some library with methods like:
public struct Foo {
#available(macOS 10.15, *)
func greatNewFeature() -> String {
return "new feature"
}
func legacyFeature() -> String {
return "legacy feature"
}
}
Then some code that uses it:
func methodToTest() -> String {
let foo = Foo()
guard #available(macOS 10.15, *) else {
return foo.legacyFeature()
}
return foo.greatNewFeature()
}
Is there a way I can write unit tests which give me complete coverage of methodToTest?
All ideas I have had so far, have not been helpful:
you can't treat the availability check as injected functionality - the compiler specifically needs the #available keyword in order to use the greatNewFeature method.
you can't add some hacky boolean which you could set in the tests like #available(macOS 10.15, *) || isMacOS10_15 for a similar reason to the previous point.
The only thing I think would work is to run the test suite multiple times - once for each supported OS version, then create a script to combine the code coverage stats. Can anyone think of a better approach?

You can create a flag in your tests whether to skip the OS version check or not and && that with your #available check.
This way you simply need to call testFooFeature with the flag both turned on and off and you'll be able to test both code paths on macOS 10.15.
var forceOldOSVersion = true
func testFooFeature() -> String {
let foo = Foo()
if !forceOldOSVersion, #available(macOS 10.15, *) {
return foo.greatNewFeature()
} else {
return foo.legacyFeature()
}
}
testFooFeature() // "legacy feature"

Related

Executing function for lower iOS versions

Is there an way for executing functions only for lower iOS versions.
I mean we can execute always using the available attribute for iOS versions greater than the specific one ; but is there any way to do the other way round?
It's simple. You can use this:
if #available(iOS 10, *) {} else {
print("Code only for versions below iOS 10")
}
It's really annoying to write if statement and then catch what you want in the else statement. Nevertheless you can define global function to handle all cases in one place, which is also have its own issues. Never call it with a variable! Always use hardcoded string version number to check iOS version. If you accidentally checked unhandled version, just insert it and go. fatalError() will help you.
func available(version: String) -> Bool {
switch version {
case "13.5":
if #available(iOS 13.5, *) { return true }
case "12":
if #available(iOS 12, *) { return true }
default:
fatalError("iOS \(version) is not handled. Insert:\n case \"\(version)\": \n if #available(iOS \(version), *) { return true }")
}
return false }
Usage:
if !available(version: "10") {
print("Not available")
}

Swift generic sequence observable ambiguity

I've got following code
protocol NamedOption {
var optionTitle: String { get }
}
struct DebugOption: NamedOption {
let optionTitle: String
let debugViewControllerType = UIViewController.self
}
func testFunk<T: Sequence>(d: Observable<T>) where T.Element == NamedOption {
}
func bindFullResultsRx() {
let dd: Observable<[DebugOption]> = self.dataModel.debugOptions // this is defined and properly assigned earlier
testFunk(d: dd)
}
and at the line where I call testFunk Xcode gives me following error:
Expression type '()' is ambiguous without more context
I have no idea why this is happening :( So far I was able to make it working by changing constraints on testFunk into this:
func funk<T: NamedOption>(d: Observable<[T]>) {
}
which seems to me more restrictive then version at the top. Does any one knows how to make it working with T: Sequence ?
Xcode version is 9.4, Swift version is 4.1.
After some digging and help from work colleagues I was able to make it working by simply changing == into : so it looks like this
func testFunk<T: Sequence>(d: Observable<T>) where T.Element: NamedOption {
}
It's just a matter of swift syntax
https://docs.swift.org/swift-book/ReferenceManual/GenericParametersAndArguments.html
conformance-requirement → type-identifier : protocol-composition-type
OO and generics don't play too well together. In order to do what you want, you have to manually cast up as in:
testFunk(d: dd.map { $0.map { $0 as NamedOption } })

swift 2.3 how to properly use deprecated CBCentralManagerState

I'm just converting my project to swift 2.3 (XCode 8 beta 6) and I can't figure out how to use enum CBManagerState on old iOS versions (my app has deployment target iOS7).
CBCentralManager state now uses different enum CBManagerState (it was CBCentralManagerState before).
Code below does not compile because manager.state can't be compared with deprecated enum CBCentralManagerState so what should I put into else block?
Thanks for any advise!
func isBluetoothAvailable() -> Bool {
if #available(iOS 10.0, *) {
return manager.state == CBManagerState.PoweredOn
} else {
return manager.state == CBCentralManagerState.PoweredOn
}
}
I don't know whether this is a solid solution but removing the enum type seems to work...
func isBluetoothAvailable() -> Bool {
return manager.state == .PoweredOn
}

Swift - Unit testing private variables and methods

I'm trying to test a class but I'm kind of confused as to what to test. Here is the class I want to unit test:
class CalculatorBrain {
private var accumulator = 0.0
func setOperand(operand: Double) {
accumulator = operand
}
var result: Double {
return accumulator
}
private var operations: Dictionary<String, Operation> = [
"=" : .Equals,
"π" : .Constant(M_PI),
"e" : .Constant(M_E),
"±" : .UnaryOperation({ (op1: Double) -> Double in return -op1 }),
"√" : .UnaryOperation(sqrt ),
"cos": .UnaryOperation(cos),
"+" : .BinaryOperation({ (op1: Double, op2: Double) -> Double in return op1 + op2 }),
"−" : .BinaryOperation({ (op1: Double, op2: Double) -> Double in return op1 - op2 }),
"×" : .BinaryOperation({ (op1: Double, op2: Double) -> Double in return op1 * op2 }),
"÷" : .BinaryOperation({ (op1: Double, op2: Double) -> Double in return op1 / op2 })
]
private enum Operation {
case Constant(Double)
case UnaryOperation((Double) -> Double)
case BinaryOperation((Double, Double) -> Double)
case Equals
}
func performOperation(symbol: String) {
if let operation = operations[symbol] {
switch operation {
case .Constant(let value):
accumulator = value
case .UnaryOperation(let function):
accumulator = function(accumulator)
case .BinaryOperation(let function):
executePendingBinaryOperation()
pendingBinaryOperation = PendingBinaryOperationInfo(binaryOperation: function, firstOperand: accumulator)
case .Equals:
executePendingBinaryOperation()
}
}
}
private var pendingBinaryOperation: PendingBinaryOperationInfo?
private struct PendingBinaryOperationInfo {
var binaryOperation: (Double, Double) -> Double
var firstOperand: Double
}
private func executePendingBinaryOperation() {
if let pending = pendingBinaryOperation {
accumulator = pending.binaryOperation(pending.firstOperand, accumulator)
pendingBinaryOperation = nil
}
}
}
For the code above, what would be good tests.
Is it worth testing every single operation (+, -, *, /, etc) in the dictionary operations?
Is it worth testing the private methods?
You can't test private methods in Swift using #testable. You can only test methods marked either internal or public. As the docs say:
Note: #testable provides access only for “internal” functions;
“private” declarations are not visible outside of their file even when
using #testable.
Read more here
Unit testing should be considered black box testing, which means you don't care about the internals of the unit you test. You are mainly interested to see what's the unit output based on the inputs you give it in the unit test.
Now, by outputs we can assert on several things:
the result of a method
the state of the object after acting on it,
the interaction with the dependencies the object has
In all cases, we are interested only about the public interface, since that's the one that communicates with the rest of the world.
Private stuff don't need to have unit tests simply because any private item is indirectly used by a public one. The trick is to write enough tests that exercise the public members so that the private ones are fully covered.
Also, one important thing to keep in mind is that unit testing should validate the unit specifications, and not its implementation. Validating implementation details adds a tight coupling between the unit testing code and the tested code, which has a big disadvantage: if the tested implementation detail changes, then it's likely that the unit test will need to be changed also.
Writing unit tests in a black box manner means that you'll be able to refactor all the code in those units without worrying that by also having to change the tests you risk into introducing bugs in the unit testing code. Unreliable unit tests are sometimes worse than a lack of tests, as tests that give false positives are likely to hide actual bugs in your code.
Although I agree with not testing private stuff, and I'd rather prefer to test just public interface, sometimes I've needed to test something inside a class that was hidden (like a complex state machine). For these cases what you can do is:
import Foundation
public class Test {
internal func testInternal() -> Int {
return 1
}
public func testPublic() -> Int {
return 2
}
// we can't test this!
private func testPrivate() -> Int {
return 3
}
}
// won't ship with production code thanks to #if DEBUG
// add a good comment with "WHY this is needed 😉"
#if DEBUG
extension Test {
public func exposePrivate() -> Int {
return self.testPrivate()
}
}
#endif
Then you can do this:
import XCTest
#testable import TestTests
class TestTestsTests: XCTestCase {
func testExample() {
let sut = Test()
XCTAssertEqual(1, sut.testInternal())
}
func testPrivateExample() {
let sut = Test()
XCTAssertEqual(3, sut.exposePrivate())
}
}
I understand perfectly that this is a hack. But knowing this trick can save your bacon in the future or not. Do not abuse this trick.
Diego's answer is clever but it is possible to go further.
Go into your project editor and define a new Testing Configuration by duplicating the Debug configuration.
Edit your scheme's Test action so that the build configuration is Testing.
Now edit your test target's build settings to define an additional Active Compilation Conditions value for the Testing configuration, "TESTING".
Now you can say #if TESTING, as distinct from mere DEBUG.
I use this, for example, to declare initializers that only a test can see.
Short answer is you can't. Private parts can't be tested.
However, I don't think "you shouldn't" is a valid answer. I used to think in this way, but real life scenarios are more complicated than we would expect. At some point, I need to write a FileScanner class as part of a framework, which conforms to a Scanner protocol that only has a scan(filename: String) function. Of course FileScanner.scan(filename: String) needs to be public, but how about the functions that support scan?
As I mentioned in a comment above, I want to:
keep the interface as clean as possible, and
limit access level as private as possible
Which means I don't want to expose other functions that are not used by other classes. I really hope there's a #testable modifier at function level (works like #discardable etc) but since it's not really there in Swift, we unfortunately only have 2 options:
Write unit tests for scan only, which is suggested by most people. This requires a lot of input files in the unit test bundle (not necessarily Target, as I'm using SPM only without Xcode, and it's just a Tests directory), and is hard to create specific cases for individual functions. Depends on how complex scan is, it's not really a good approach.
Expose private other functions. I ended up with this approach, and make a convention, that if a function doesn't have any modifier, we assume it's internal and can be used by other files in the same bundle (Target), just not public. But if we specifically mark it as internal func etc, it means we just want to make it #testable and it should never be used by other classes in the same bundle.
So, my conclusion is that even you can't test private methods and properties in Swift yet, I consider it as a limitation of Swift but not an invalid use case.
I found this link which is saying something similar with Cristik.
Basically, you are asking the wrong question, you should not be seeking to test the class/functions marked with "private".
I think actually don’t need to test of private members.
But if you want to use to private members(properties & methods) at UnitTest, there is a way that use Protocol.
Protocol PrivateTestable {
associatedtype PrivateTestCase
var privateTestCase: PrivateTestCase {get}
}
And try to extension the protocol in same file (target class file).
extension CalculatorBrain: PrivateTestable {
struct PrivateTestCase {
private let target: CalculatorBrain
var pendingBinaryOperation: PendingBinaryOperationInfo? {
return target.pendingBinaryOperation
}
init(target: CalculatorBrain) {
self.target = target
}
}
var privateTestable: PrivateTestCase {
return PrivateTestCase(target: self)
}
}
Then you can use pendingBinaryOperation in UnitTest
class CalculatorBrainTest: XCTestCase {
func testPendingBinaryOperation() {
let brain = CalculatorBrain()
XCTAssertNotNil(brain.privateTestCase.pendingBinaryOperation)
}
}
If you really want to get a private field in tests, you can use the Mirror class:
let testClass = CalculatorBrain()
let mirror = Mirror(reflecting: testClass)
func extract<T>(variable name: StaticString, mirror: Mirror?) -> T? {
guard let mirror = mirror else {
return nil
}
guard let descendant = mirror.descendant("\(name)") as? T
else {
return extract(variable: name, mirror: mirror)
}
return descendant
}
let result: Dictionary<String, Any>? = extract(variable: "operations", mirror: mirror)
print(result!)
For example, I made an extension of the class to check the output result
extension CalculatorBrain {
var test: Any {
operations
}
}
print("")
print(testClass.test)
As a result, I got this:
Mirror
["−": __lldb_expr_24.CalculatorBrain.Operation.BinaryOperation((Function)),
"√": __lldb_expr_24.CalculatorBrain.Operation.UnaryOperation((Function)),
"+": __lldb_expr_24.CalculatorBrain.Operation.BinaryOperation((Function)),
"÷": __lldb_expr_24.CalculatorBrain.Operation.BinaryOperation((Function)),
"e": __lldb_expr_24.CalculatorBrain.Operation.Constant(2.718281828459045),
"π": __lldb_expr_24.CalculatorBrain.Operation.Constant(3.141592653589793),
"cos": __lldb_expr_24.CalculatorBrain.Operation.UnaryOperation((Function)),
"=": __lldb_expr_24.CalculatorBrain.Operation.Equals,
"±": __lldb_expr_24.CalculatorBrain.Operation.UnaryOperation((Function)),
"×": __lldb_expr_24.CalculatorBrain.Operation.BinaryOperation((Function))]
Extension
["×": __lldb_expr_24.CalculatorBrain.Operation.BinaryOperation((Function)),
"÷": __lldb_expr_24.CalculatorBrain.Operation.BinaryOperation((Function)),
"√": __lldb_expr_24.CalculatorBrain.Operation.UnaryOperation((Function)),
"=": __lldb_expr_24.CalculatorBrain.Operation.Equals,
"−": __lldb_expr_24.CalculatorBrain.Operation.BinaryOperation((Function)),
"±": __lldb_expr_24.CalculatorBrain.Operation.UnaryOperation((Function)),
"e": __lldb_expr_24.CalculatorBrain.Operation.Constant(2.718281828459045),
"cos": __lldb_expr_24.CalculatorBrain.Operation.UnaryOperation((Function)),
"π": __lldb_expr_24.CalculatorBrain.Operation.Constant(3.141592653589793),
"+": __lldb_expr_24.CalculatorBrain.Operation.BinaryOperation((Function))]
Private methods will not be tested (at least I do not know how to do this without changing the main code)

Can you create polyfills in swift using availability checking?

Swift 2 lets you check the current platform and version to make sure that newer methods/properties are available and allow you to write fallback code if they are not, by writing a conditional block with #available.
So for example, in OSX10.10, NSTextField has a property called placeholderString, but in older versions, you had to access this info via an intermediate NSTextFieldCell object. You could safely use the new system with a fallback for the old one like this:
var placeholder : String
if #available(OSX 10.10, *) {
placeholder = inputBox.placeholderString!
} else {
placeholder = (inputBox.cell as! NSTextFieldCell).placeholderString!
}
Swift also allows you to extend existing classes with extension
For example, adding the placeholder property to NSTextField
extension NSTextField {
var placeholderString: String? {
return (self.cell as! NSTextFieldCell).placeholderString
}
}
So... Is it possible to combine these 2 ideas and create something like a polyfill, where we extend a class conditionally on the current platform, in order to retrospectively add features of the new API to older platforms?
So in my example, I would want to write something like this:
if !#available(OSX 10.10, *) {
extension NSTextField {
var placeholderString: String? {
return (self.cell as! NSTextFieldCell).placeholderString
}
}
}
The above doesn't compile. But it illustrates the kind of thing I'm looking for. If a particular thing is available on the current platform, then use it, otherwise supply an implementation for it.
Is there any way in Swift to achieve this?
Subsequently you would be able to safely use textField.placeholderString without needing to worry about the platform version.
if #available() is checked a runtime, not at compile or link time.
Therefore I assume that the only possible way is to define a wrapper
method:
extension NSTextField {
var myPlaceholderString: String? {
if #available(OSX 10.10, *) {
return self.placeholderString
} else {
return (self.cell as! NSTextFieldCell).placeholderString
}
}
}
(and I understand that this is not exactly what you are looking for.)
Perhaps someone else has a better solution!