Skip to content

Proposal for testing support in SwiftPM #51

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 2, 2016
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
239 changes: 239 additions & 0 deletions proposals/0008-package-manager-testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
# Swift Testing

* Authors:
[Max Howell](https://github.com/mxcl),
[Daniel Dunbar](https://github.com/ddunbar),
[Mattt Thompson](https://github.com/mattt)
* Status: **Review**
* Review Manager: Rick Ballard

## Introduction

Testing is an essential part of modern software development.
Tight integration of testing into the Swift Package Manager
will help ensure a stable and reliable packaging ecosystem.

## Proposed Solution

We propose to extend our conventional package directory layout
to accomodate test modules.
Any subdirectory of the package root directory named "Tests"
or any subdirectory of an existing module directory named "Tests"
will comprise a test module.
For example:

Package
├── Sources
│ └── Foo
│ └──Foo.swift
└── Tests
└── Foo
└── Test.swift

Or:

Package
└── Sources
├── Foo.swift
└── Tests
└── Test.swift

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for this variant? Will one format be recommended over the other? Having one recommended structure seems like it would be better in terms of consistency/providing guidance to developers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One will be recommended. I believe in strong recommendations for flexible workflows.

Or, for simpler projects:

Package
├── Sources
│ └── Foo.swift
└── Tests
└── TestFoo.swift

The filename: `TestFoo.swift` is arbitrary.

In the examples above
a test case is created for the module `Foo`
based on the sources in the relevant subdirectories.

A test-module is created per subdirectory of Tests, so:

Package
├── Sources
│ └── Foo
│ └──Foo.swift
└── Tests
└── Foo
└── Test.swift
└── Bar
└── Test.swift

Would create two test-modules. The modules in this example may
test different aspects of the module Foo, it is entirely up
to the package author.

Additionally we will support directories called `FooTests`.
This layout style is prevalent in existing open source projects
and supporting it will minimize vexation for their authors.
However in the interest of consistency and the corresponding
reduction of cognitive-load when examining new Swift packages
we will not recommend this layout. For example:

Package
└── Sources
│ └── Foo.swift
└── FooTests
└── Test.swift

Additionally, we propose that building a module
also builds that module's corresponding tests.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent idea! But would it be possible to explicitly disable this behavior by passing command-line options to swiftpm?

I ask because I can envision several situations in which this is not desirable behavior, such as when the tests are known to be broken. In this situation, a developer could pull the latest revision down because its tests are failing on a continuous integration suite, and needs to then build and run that revision in order to attach a debugger like LLDB.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes a lot of sense to follow the directory pattern used for the Sources with the Tests. 👍

Would this also naturally extend to allow for multiple test bundles using names like TestN? Having a build system which can build multiple test bundles (just by creating multiple directories) would be a boon for distributing testing across modern CI infrastructure which typically uses multiple virtualized agents. Something like $ swift build --test --n 1

Granted, large test suites implies large libraries which is something I think the SPM authors aim to discourage. Just a thought.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add to the proposal detailing that a flag will be available that disables building tests.

@danthorpe multiple test modules/bundles will be built in the same manner as multiple source modules are currently.

Although this would result in slightly increased build times,
we believe that tests are important enough to justify this
(one might even consider slow building tests to be a code smell).
We would prefer to go even further by executing the tests
each time a module is built as well,
but we understand that this would impede debug cycles.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Often it is simpler/faster to completely disable testing when making changes/prototyping to then re-enable them just before submitting (to make sure nothing has been missed and everything passes). Would it make sense to provide this kind of workflow?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes agreed, via some command line flag. I’ll add this to the proposal.

As an exception, when packages are built in release mode we will
not build tests because for release builds we should not enable
testability. However, considering the need for release-mode testing
this will be a future direction.

### Command-Line Interface

We propose the following syntax to execute tests:

$ swift build --test

Or:

$ swift build -t

In the future, we may choose to promote the `--test` option
to be a subcommand of the `swift` command itself:

$ swift test

However, any such decision would warrant extensive design consideration,
so as to avoid polluting or crowding the command-line interface.
Should there be sufficient demand and justification for it, though,
it would be straightforward to add this functionality.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would really like to see any arguments after swift build --test or swift test pass though to the test runner.

Example, the test framework Spectre allows command line flags to use different reporters. For example, --tap, --dot etc.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very much agree in terms of command-line arguments being used by test runners, and in terms of the necessity of custom test reporters.

Still, I believe Swift's Process.environment may be able to suffice for now, and would allow us to decouple that important discussion from this proposal.


### Command Output

Executing a test from the terminal will produce user-readable output.
This should incorporate colorization and other formatting
similar to other testing tools
to indicate the success and failure of different tests.
For example:

$ swift test --output module
Running tests for PackageX (x/100)
.........x.....x...................

Completed
Elapsed time: 0.2s

98 Success
2 Failure
1 Warning

FAILURE: Tests/TestsA.swift:24 testFoo()
XCTAssertTrue expected true, got false

FAILURE: Tests/TestsB.swift:10 testBar()
XCTAssertEqual

WARNING: Tests/TestsC.swift:1
"Some Warning"

An additional option may be passed to the testing command
to output JUnit-style XML or other formats that can be integrated
with continuous integration (CI) and other systems.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this system use a plugin architecture to allow users to specify their own test reporters? For example, one that outputs the results in a JSON format? Or would each reporter need to be merged into the swiftpm codebase?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first no, however this makes sense to implement at the same time as the feature whereby custom test frameworks can be used. Probably the same route would work well, ie. a protocol is defined and a dependency can be built into the test-runner that generates output.


### Backwards Compatibility

In order to accomodate existing packages,
we will allow test module targets and their targets
to be overridden in the `Package.swift` manifest file.
However, this functionality will likely not be implemented
in the initial release of this feature,
and instead be added at a later point in time.

### Automatic Dependency Determination

Testing is important and it is important to make the barrier to testing
as minimal as possible. Thus, by analyzing the names of test targets,
we will automatically determine the most likely dependency of that test
and accomodate accordingly.
For example,
a test for "Foo" will depend on compilation of the library target `Foo`.
Any additional dependencies or dependencies that could not be automatically determined
would need to be specified in a package manifest separately.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify the author's intent for resolving additional dependencies - does this mean that in Package.swift there will be a "private" section for dependencies used by the tests, (things like DVR), or that the Test directory will include its own manifest?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just want to link this to swiftlang/swift-package-manager#74

There is a pull request and discussion around "private dependencies" in this pull request.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As much as I like the idea of having another manifest and thus keeping it all separate, maintaining two manifests is an extra layer of tedium for packagers, so I think we'll (longterm) be learning towards keeping all of this in one manifest file. The linked PR is being reviewed today, but is not a firm decision, we may at a later time change the manifest format before Swift 3 drops.

### Debug / Release Configuration

Although tests built in debug configuration
are generally run against modules also build in debug configuration,
it is sometimes necessary to specify the build configuration for tests separately.
It is also sometimes necessary to explicitly specify this information for every build,
such as when building in a release configuration to execute performance tests.
We would like to eventually support these use cases,
however this will not be present in the initial implementation of this feature.

### Testability

Swift can build modules with "testability",
which allows tests to access entities with `internal` access control.
Because it would be tedious for users to specify this requirement for tests,
we intend to build debug builds with testability by default.

### Test Frameworks

Initially,
the Swift Package Manager will use `XCTest` as its underlying test framework.

However, testing is an evolving artform,
so we'd like to support other approaches
that might allow frameworks other than XCTest
to be supported by the package manager.
We expect that such an implementation would take the form of
a Swift protocol that the package manager defines,
which other testing frameworks can adopt.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps it could just be simpler, and if the test module has a main.swift file it's treated as a command line tool and can be built and run.

This is the approach I've done in Spectre and you can find an example using this approach (Makefile, main.swift).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have the authors considered decoupling swiftpm's testing support from XCTest, and indeed any specific testing framework? The alternative I propose is to simply run a test package's main.swift file. A test failure would be any exit status that is not 0.

This would allow developers writing their tests to choose how they test their applications--something that is very difficult in Objective-C XCTest, which hides the entry point to the test program from developers. In this proposed ecosystem, swift-corelibs-xctest could be expanded to provide seamless integration with Objective-C XCTest. And meanwhile, developers who aren't interested in integration with Objective-C XCTest could use entirely different testing frameworks, like @kylef's Spectre framework. I consider this to be an improvement upon the current Objective-C XCTest ecosystem, which prevents developers from changing even the order in which their test case classes are run.

If coupling is indeed the intent of the authors, I'd like to flesh out what exactly the following sentence means:

Initially, the Swift Package Manager will use XCTest as its underlying test framework.

Does this mean swift-corelibs-xctest will be linked and available to use within packages' tests, and those tests are still expected to use a main.swift file to call XCTMain()? Does it mean that swiftpm will call XCTMain() somehow, perhaps automatically discovering the objects conforming to XCTestCaseProvider in the test package? Or does it mean something else?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have the authors considered decoupling swiftpm's testing support from XCTest, and indeed any specific testing framework? The alternative I propose is to simply run a test package's main.swift file. A test failure would be any exit status that is not 0.

+1. I have been using Quick (https://github.com/Quick/Quick https://github.com/Quick/Quick) and Nimble (https://github.com/Quick/Nimble https://github.com/Quick/Nimble) and really like them. SwiftCheck (https://github.com/typelift/SwiftCheck https://github.com/typelift/SwiftCheck) is something I may use in the future as well. It takes a completely different approach to testing. It would be a shame to tie ourselves to one test framework.

Of course if SPM supports alternative test frameworks it would need to be able to resolve those dependencies in a similar way to the dependencies of the package itself.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also like to advocate for support for different test runners in addition to the concepts of passing down test reports as additional modules. I very much like XCTest as a default, and in other languages I've found it immensely beneficial to utilize different test additions and libraries. That may be more appropriate determined from an ENV variable.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 on making this more flexible here. Additionally being able to include modules/plugins for other aspects of testing - failing on regressions of code coverage, static code analysis for detecting known errors/bad practice, enforcing code styles and so on - will be beneficial for code quality.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a protocol could be an interesting approach. Without knowing what sort of protocol that would be, however, it's hard to comment on what would or wouldn't be possible.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^^ Agreed, but I think as long as that protocol isn't part of XCTest and is more of a part of the build-tool / package management infrastructure itself, my testing framework can adopt that protocol fairly easily, while XCTest can be integrated as a sensible default. But each package should have a measure of control over what testing tools they want to use.

As someone developing a third-party testing tool, I really just don't want my testing framework to have an XCTest dependency just so I can package it with the most common tools. As long as that happens, I'll be a happy camper.

(edit to add I think it sounds like what @mxcl is suggesting gets a 👍 from me -- just want to ensure we're of the same mind)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, for me, the biggest question in terms of implementation is: how do we package or distribute those custom test running plugins / protocols that are going to be visible from the build process itself -- project-level runtime dependencies listed from Package.swift aren't going to be accessible at build-time, because they're being built at build-time.

npm solves this by allowing you to provide a test command for your module, which seems like the simplest implementation. Other build tools like leiningen have a way to get build-time dependencies set up using a separate field for plugins. This means that each project's build is run in a sandbox with an import path set up for various user-specified dependencies. From there we could just import our test protocol and give the user a place to configure it (probably in a Package initializer).

Curious to see what folks around here are thinking?

(edit: also, if this is not the appropriate place for this particular conversation, would it be appropriate to start a mailing list thread to chat about these ideas? On swift-build-dev perhaps? Bit of a new face around here, any advice is appreciated)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kylef’s proposal that the presence of a main.swift causes standalone test-binaries has the serious con IMO that any additional infrastructure people may want to use on top of swiftpm testing will be decoupled. If the minimum layer is a protocol, then we can build whatever test-runners the community wants on top of that.

@mxcl Can you elaborate on what the problem is with things being decoupled? In my opinion, being decoupled would be a huge improvement.

I think it's fine that there is the XCTest protocol, but I really would like to have a way to not use it, even if that option is not default. Having the flexibility to write tests without any limitations or constraints imposed by the XCTest protocol would be beneficial. There are many different preferences on how testing can be done and I would hate to see a certain design being tightly coupled into the language and ecosystem. Tight coupling to a particular testing design might hamper Swift adoption in as-yet unforeseen applications.

As a proof of concept. I've build a simple runner (and unfortunately also a simple build tool for tests) which allows Spectre to work with SPM (although not integrated). This allows a Tests/ directory where I can have a main.swift which runs the tests. https://github.com/kylef/spectre-build

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kylef Would you mind starting a thread on [email protected] about your proposal here? I'd love for us to put together a concrete proposal for how we'll support other test frameworks sooner rather than later – it sounds like you and others would like to get that support added soon!

FWIW, the main concern I have with the lightweight "just use a main.swift that runs whatever test code you want" approach is that it doesn't lead to unified test behavior for swift packages. I'd really like package users to be able to rely on being able to run the tests for all their dependencies without needing to manually install other test support first (re: @bppr 's concern), and to get parseable test output in a uniform format, so that tools like a CI dashboard, or a package index that checks package health, can easily be built on top.

Let's discuss on-list what sort of protocol we'd need to define to let you easily get up and running with a different test framework while a) making it possible for swiftPM to get all the components it needs to run your tests automatically and b) providing the test output in a uniform format. Thanks!



### Command Line Interface

The command line should accept the names of specific test cases to run:

swift build -t FooTestCase

Or specific tests:

swift build -t FooTestCase.test1

SwiftPM would forward arguments to the underlying testing framework and it
would decide how to interpret them.

---

Sometimes test sources cannot compile and fixing them is no the most
pressing priority. Thus it will be posssible to skip building tests
with an additional flag:

swift build --without-tests

---

It is desirable to sometimes specify to only build specific tests, the
command line for this will fall out of future work that allows specification
of targets that `swift build` should speficially build in isolation.


## Impact On Existing Code

Current releases of the package manager already exclude directories named
"Tests" from target-determination. Directories named `FooTests` are not
excluded, but as it stands this is a cause of compile failure, so in fact
these changes will positively impact existing code.

## Alternatives Considered

Because this is a relatively broad proposal,
no complete alternatives were considered.