[Pitch] @testable private members

When writing unit tests sometimes it is necessary to artificially elevate a member to `internal` in order to make it visible to unit tests where it could otherwise be `private` or `fileprivate`. We could introduce an `@testable` attribute that could be applied anywhere an access modifier is used. This attribute would elevate the access modifier to `internal` when the module is imported using the `@testable import MyModule` syntax in a test suite.

Is this something that others have interest in? Is it something that might be considered for Swift 4 now that phase 2 has begun?

Matthew

3 Likes

Whoops, accidentally sent this off-list.

-thislooksfun (tlf)

···

On Feb 18, 2017, at 12:26 PM, thislooksfun <thislooksfun@repbot.org> wrote:

Big +1 from me. I see no point in having to elevate access just to make sure everything is working.

-thislooksfun (tlf)

On Feb 18, 2017, at 12:14 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

When writing unit tests sometimes it is necessary to artificially elevate a member to `internal` in order to make it visible to unit tests where it could otherwise be `private` or `fileprivate`. We could introduce an `@testable` attribute that could be applied anywhere an access modifier is used. This attribute would elevate the access modifier to `internal` when the module is imported using the `@testable import MyModule` syntax in a test suite.

Is this something that others have interest in? Is it something that might be considered for Swift 4 now that phase 2 has begun?

Matthew
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

extension MyModel {
    public func load(fromJSON jsonData: Data) throws {
      ...
    }
    
    private func parse(_ json: Data) -> Any { … }
  }
  
  extension MyModel {
    public func load(fromPropertyList plistData: Data) throws {
      …
    }
    
    private func parse(_ plist: Data) -> Any { … }
  }

What happens when you try to test `MyModel.parse(_:)`?

···

On Feb 18, 2017, at 10:14 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

When writing unit tests sometimes it is necessary to artificially elevate a member to `internal` in order to make it visible to unit tests where it could otherwise be `private` or `fileprivate`. We could introduce an `@testable` attribute that could be applied anywhere an access modifier is used. This attribute would elevate the access modifier to `internal` when the module is imported using the `@testable import MyModule` syntax in a test suite.

--
Brent Royal-Gordon
Architechies

@testable is already a hack. Why not just extend it to fileprivate members?

~Robert Widmann

···

On Feb 18, 2017, at 1:14 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

When writing unit tests sometimes it is necessary to artificially elevate a member to `internal` in order to make it visible to unit tests where it could otherwise be `private` or `fileprivate`. We could introduce an `@testable` attribute that could be applied anywhere an access modifier is used. This attribute would elevate the access modifier to `internal` when the module is imported using the `@testable import MyModule` syntax in a test suite.

Is this something that others have interest in? Is it something that might be considered for Swift 4 now that phase 2 has begun?

Matthew
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

@testable is already a hack. Why not just extend it to fileprivate members?

That would be fine with me if others prefer that solution. It certainly leaves the rest of our code a lot cleaner.

···

Sent from my iPad

On Feb 19, 2017, at 12:08 AM, Robert Widmann <devteam.codafi@gmail.com> wrote:

~Robert Widmann

On Feb 18, 2017, at 1:14 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

When writing unit tests sometimes it is necessary to artificially elevate a member to `internal` in order to make it visible to unit tests where it could otherwise be `private` or `fileprivate`. We could introduce an `@testable` attribute that could be applied anywhere an access modifier is used. This attribute would elevate the access modifier to `internal` when the module is imported using the `@testable import MyModule` syntax in a test suite.

Is this something that others have interest in? Is it something that might be considered for Swift 4 now that phase 2 has begun?

Matthew
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

When writing unit tests sometimes it is necessary to artificially elevate a member to `internal` in order to make it visible to unit tests where it could otherwise be `private` or `fileprivate`. We could introduce an `@testable` attribute that could be applied anywhere an access modifier is used. This attribute would elevate the access modifier to `internal` when the module is imported using the `@testable import MyModule` syntax in a test suite.

   extension MyModel {
       public func load(fromJSON jsonData: Data) throws {
           ...
       }
       
       private func parse(_ json: Data) -> Any { … }
   }
   
   extension MyModel {
       public func load(fromPropertyList plistData: Data) throws {
           …
       }
       
       private func parse(_ plist: Data) -> Any { … }
   }

What happens when you try to test `MyModel.parse(_:)`?

With what I suggested you would not be able to unless you change the declarations to `@testable private`.

With Robert's alternative this would already be testable.

···

Sent from my iPad
On Feb 19, 2017, at 12:21 AM, Brent Royal-Gordon <brent@architechies.com> wrote:

On Feb 18, 2017, at 10:14 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

--
Brent Royal-Gordon
Architechies

+1 for this.

I recently wrote a class with methods that do some complicated logic that isn't used anywhere else. I'd like to define them as private, but I also want to unit test the individual methods without having to generate verbose inputs for the class initializer (which calls the other methods).

There's also an auxiliary class which I'd like to be fileprivate since it's not relevant anywhere else, but it can't be because one or two unit tests are much simpler by accessing it directly.

So yeah, I get the "unit tests should only test the public API" argument. It makes sense. But it also makes sense to unit tests individual methods without having to worry about the context in which they are called.

Making private members visible in unit tests would be great, we have a lot of private @IBOutlets in our code which we either have to make internal just for unit testing or get them via KVC (which I consider a hack and it breaks if we e.g. rename the outlet.

1 Like

My favourite solution to this is to allow tests in the same scope of the function (as previously mentioned by someone like D does). Though it is quite a big language feature to implement which I don't even know if Swift wants, and if so it might even need to move away from XCTest...

Edit: It seems like @codafi went ahead and did a full proposal just minutes before I wrote this :stuck_out_tongue:

2 Likes