Exploration: Test ErrorType and swifterrortype in Swift

Source: Internet
Author: User

Exploration: Test ErrorType and swifterrortype in Swift

Original article: Testing Swift's ErrorType: An authentication

Translator: mmoaay

In this article, we will explore the nature of the new types of errors in Swift, observe and test the possibilities and limitations of error handling. Finally, we end with a demo and some useful resources.

How to Implement ErrorTypeProtocol

If you are redirected to the Swift standard libraryErrorTypeDefined position, we will find that it does not contain too many obvious necessary conditions.

protocol ErrorType {}

However, when we try to implementErrorTypeWill soon find at leastSomethingIs required. For example, if you implement it by enumeration, everything is OK.

enum MyErrorEnum : ErrorType {}

But if we implement it as a struct, the problem arises.

struct MyErrorStruct : ErrorType {}

Our initial thought may be, maybeErrorTypeIs a special type. The Compiler supports it in a special way and can only be implemented using native enumeration of Swift. But then you will rememberNSErrorIt also satisfies this protocol, so it cannot be so special. So our next attempt isNSObjectTo implement this protocol.

@objc class MyErrorClass: ErrorType {}

Unfortunately, it still does not work.

Update: From Xcode 7 beta 5, we can implement struct and classes without any effort.ErrorTypeProtocol. Therefore, the following solution is no longer needed, but it is still for reference.

Allowed struct and class implementationErrorTypeProtocol. (21867608)

Why?

PassLLDBFurther investigation found that the Protocol had some hidden conditions.

(lldb) type lookup ErrorTypeprotocol ErrorType {  var _domain: Swift.String { get }  var _code: Swift.Int { get }}

In this wayNSErrorThe reason for satisfying this definition is clear: it has these attributesivarsAnd can be accessed by Swift without dynamic search. It is not clear why Swift's first-class citizen enumeration can automatically meet this protocol. Maybe some magic still exists inside it?

If we use our new knowledge to implement structures and classes, everything will be okay.

struct MyErrorStruct : ErrorType {  let _domain: String  let _code: Int}class MyErrorClass : ErrorType {  let _domain: String  let _code: Int  init(domain: String, code: Int) {    _domain = domain    _code = code  }}
Capture other thrown errors

In historyNSErrorPointerMode plays an important role in error handling. These have become simpler when Objective-C APIs are perfectly connected to Swift. Domain-specific errors are exposed in enumeration mode, so that they can be captured simply without the use of "Fantastic numbers. But what if you need to capture an error that is not exposed?

Suppose we need to deserialize a json string, but we are not sure whether it is valid or not. We will useFoundationOfNSJSONSerializationTo do this. When we pass it an abnormal JSON string, it will throw an error code:3840.

Of course, you can capture it with common errors, and then manually check_domainAnd_codeDomain, but we have a more elegant alternative.

let json : NSString = "{"let data = json.dataUsingEncoding(NSUTF8StringEncoding)do {    let object : AnyObject = try NSJSONSerialization.JSONObjectWithData(data!, options: [])    print(object)} catch let error {    if   error._domain == NSCocoaErrorDomain      && error._code   == 3840 {        print("Invalid format")    } else {        throw error    }}

Another alternative solution is to introduce a common error struct that meetsErrorTypeProtocol. When we implement the pattern matching operator for it~=We cando … catchBranch.

struct Error : ErrorType {    let domain: String    let code: Int    var _domain: String {        return domain    }    var _code: Int {        return code    }}func ~=(lhs: Error, rhs: ErrorType) -> Bool {    return lhs._domain == rhs._domain        && rhs._code   == rhs._code}let json : NSString = "{"let data = json.dataUsingEncoding(NSUTF8StringEncoding)do {    let object : AnyObject = try NSJSONSerialization.JSONObjectWithData(data!, options: [])    print(object)} catch Error(domain: NSCocoaErrorDomain, code: 3840) {    print("Invalid format")}

However, in the current situationNSCocoaErrorThis helper class contains a large number of static methods that define various errors.

What is generated here isNSCocoaError.PropertyListReadCorruptErrorError. Although not so obvious, it does have the error code we need. Whether you capture errors through a standard library or a third-party framework, if there is something like this, you need to rely on the given constant instead of defining it yourself.

let json : NSString = "{"let data = json.dataUsingEncoding(NSUTF8StringEncoding)do {    let object : AnyObject = try NSJSONSerialization.JSONObjectWithData(data!, options: [])    print(object)} catch NSCocoaErrorDomain {    print("Invalid format")}
Coding rules for custom error handling

So what will we do next? After using Swift error processing to add code to our code, whether we replace all the distractingNSErrorPointer assignment, or back to the functional paradigmResultType. Make sure that the expected error is thrown correctly. The boundary value is always the most interesting scenario during testing. We want to confirm that all the protection measures are in place and will throw corresponding errors when appropriate.

Now we have some basic knowledge about the underlying working method of this error type, and also have some ideas about how to make it follow our wishes during testing. So let's demonstrate a small test case: We have a bank App, and then we want to create a model for realistic activities in the business logic. We have created a structure Account that represents a bank Account. It contains an interface that exposes a method to trade within the budget range.

Public enum Error: ErrorType {case TransactionExceedsFunds case NonPositiveTransactionNotAllowed (amount: Int)} public struct Account {var fund: Int public mutating func withdraw (amount: Int) throws {guard amount <fund else {throw Error. transactionExceedsFunds} guard amount> 0 else {throw Error. nonPositiveTransactionNotAllowed (amount: amount)} fund-= amount} class AccountTests {func testPreventNegativeWithdrawals () {var account = Account (fund: 100) do {try account. withdraw (-10) XCTFail ("Withdrawal of negative amount succeeded, but was expected to fail. ")} catch Error. nonPositiveTransactionNotAllowed (let amount) {XCTAssertEqual (amount,-10)} catch {XCTFail ("Catched error \" \ (error) \ ", but not the expected: \ "\ (Error. nonPositiveTransactionNotAllowed) \ "")} func testPreventExceedingTransactions () {var account = Account (fund: 100) do {try account. withdraw (101) XCTFail ("Withdrawal of amount exceeding funds succeeded, but was expected to fail. ")} catch Error. transactionExceedsFunds {// expected result} catch {XCTFail ("Catched error \" \ (error) \ ", but not the expected: \" \ (Error. transactionExceedsFunds )\"")}}}

Now, we suppose we have more methods and more error scenarios. In a test-oriented development mode, we want to test both of them, so as to ensure that all errors are correctly dropped out-we certainly do not want to transfer the money to the wrong place! Ideally, we do not want to repeat this in all the test code.do-catch. To implement an abstraction, we can put it into a higher-order function.

/// Matching public func ~ For ErrorType Implementation Mode ~ = (Lhs: ErrorType, rhs: ErrorType)-> Bool {return lhs. _ domain = rhs. _ domain & lhs. _ code = rhs. _ code} func AssertThrow <R> (expectedError: ErrorType, @ autoclosure _ closure: () throws-> R)-> () {do {try closure () XCTFail ("Expected error \" \ (expectedError) \ "," + "but closure succeeded. ")} catch expectedError {// expected results .} catch {XCTFail ("Catched error \" \ (error) \ "," + "but not from the expected type" + "\" \ (expectedError )\". ")}}

This code can be used as follows:

class AccountTests : XCTestCase {    func testPreventExceedingTransactions() {        var account = Account(fund: 100)        AssertThrow(Error.TransactionExceedsFunds, try account.withdraw(101))    }    func testPreventNegativeWithdrawals() {        var account = Account(fund: 100)        AssertThrow(Error.NonPositiveTransactionNotAllowed(amount: -10), try account.withdraw(-20))    }}

However, you may find that the expected parameterization ErrorNonPositiveTransactionNotAllowedMore than the parameters used hereamount. How can we make strong assumptions about the error scenarios and their related values? First, we can implement the Error TypeEquatableProtocol, and then add a check on the number of parameters in the relevant scenario in the implementation of equal operators.

/// Extend the error type and implement 'equatable '. /// This must be done for each specific type, /// instead of 'errortype. Extension Error: Equatable {} // The '=' operator is implemented in the form of required for the Protocol 'equatable. Public func = (lhs: Error, rhs: Error)-> Bool {switch (lhs, rhs) {case (. nonPositiveTransactionNotAllowed (let l ),. nonPositiveTransactionNotAllowed (let r): return l = r default: // we need to return false for various combination scenarios in the default scenario. // By comparing domain and code, we can ensure that // once we add other error scenarios, if this scenario has a corresponding value // I just need to return and modify the Equatable implementation to return lhs. _ domain = rhs. _ domain & lhs. _ code = rhs. _ code }}

The next step isAssertThrowKnow that there are reasonable errors. You may think that we can expand the existingAssertThrowImplementation, just to check whether the expected errors are reasonable. However, unfortunately, it is useless:

The "Equatable" Protocol can only be used as a generic constraint because it must meet the necessary conditions for Self or associated types.

On the contrary, we can use one more generic parameter as the first parameter to overloadAssertThrow.

func AssertThrow<R, E where E: ErrorType, E: Equatable>(expectedError: E, @autoclosure _ closure: () throws -> R) -> () {    do {        try closure()        XCTFail("Expected error \"\(expectedError)\", "            + "but closure succeeded.")    } catch let error as E {        XCTAssertEqual(error, expectedError,            "Catched error is from expected type, "                + "but not the expected case.")    } catch {        XCTFail("Catched error \"\(error)\", "            + "but not the expected error "            + "\"\(expectedError)\".")    }}

Then, as expected, our test finally returned a failure.

Note that the latter's assertion implements a strong assumption on the wrong type.

Do not use the method below "capture other thrown errors", because it cannot match the type compared with the current method. Maybe this is only an objection, not a rule.

Some useful resources

In Realm, we use XCTest and our ownXCTestCaseSub-classes and some schedulers can meet our special needs. Fortunately, if you want to use this code, you do not need to copy-paste or recreate the wheel. The error prediction tool is available in the CatchingFire project on GitHub. If you are notXCTestIf you are a fan of the predicer style, you may prefer Nimble-like testing frameworks. They can also provide testing support.

Be happy to test it ~

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.