-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
|
||
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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Granted, large test suites implies large libraries which is something I think the SPM authors aim to discourage. Just a thought. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would really like to see any arguments after Example, the test framework Spectre allows command line flags to use different reporters. For example, There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
||
### 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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 This is the approach I've done in Spectre and you can find an example using this approach (Makefile, main.swift). There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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:
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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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) There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.