Skip to content

Commit ed96078

Browse files
committed
Expand vision document draft with discussion of project governance, distribution, and platform support. Also some light editing
1 parent 2c4e48a commit ed96078

File tree

1 file changed

+150
-55
lines changed

1 file changed

+150
-55
lines changed

visions/swift-testing.md

Lines changed: 150 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
# A New API Direction for Testing in Swift
1+
# A New Direction for Testing in Swift
22

33
## Introduction
44

55
A key requirement for the success of any developer platform is a way to use
66
automated testing to identify software defects. Better APIs and tools for
7-
testing can greatly improve a platform’s quality. Below, we propose a new API
7+
testing can greatly improve a platform’s quality. Below, we propose a new
88
direction for testing in Swift.
99

1010
We start by defining our basic principles and describe specific features that
1111
embody those principles. We then discuss several design considerations
1212
in-depth. Finally, we present specific ideas for delivering an all-new testing
13-
API in Swift, and weigh them against alternatives considered.
13+
solution for Swift, and weigh them against alternatives considered.
1414

1515
## Principles
1616

@@ -37,28 +37,28 @@ some tests to opt-out. It should be effortless to repeat a test with different
3737
inputs and see granular results. The library should be lightweight and
3838
efficient, imposing minimal overhead on the code being tested.
3939

40-
## Features of a great testing API
40+
## Features of a great testing system
4141

4242
Guided by these principles, there are many specific features we believe are
43-
important to consider when designing a new testing API.
43+
important to consider when designing a new testing system.
4444

4545
### Approachability
4646

4747
* **Be easy to learn and use**: There should be few individual APIs to
4848
memorize, they should have thorough documentation, and using them to write a
49-
new test should be fast and seamless. More tests are likely to be written when
50-
there is less friction.
49+
new test should be fast and seamless. Its APIs should be egonomic and adhere
50+
to Swift’s [design guidelines](https://www.swift.org/documentation/api-design-guidelines/).
5151
* **Validate expected behaviors or outcomes**: The most important job of any
5252
testing library is checking that code meets specific expectations—for example,
5353
by confirming that a function returns an expected result or that two values
5454
are equal. There are many interesting variations on this, such as comparing
55-
whole collections or checking for errors. A robust testing API should cover
56-
all these needs, and the APIs themselves should be ergonomic and adhere to
57-
Swift’s [API Design Guidelines](https://www.swift.org/documentation/api-design-guidelines/).
55+
whole collections or checking for errors. A robust testing system should cover
56+
all these needs, while using progressive disclosure to remain simple for
57+
common cases.
5858
* **Enable incremental adoption:** It should gracefully coexist with projects
59-
that currently use XCTest or other testing libraries and allow incremental
60-
adoption so that users can transition at their own pace. This is especially
61-
important because this new API may take time to reach feature parity.
59+
that use XCTest or other testing libraries and allow incremental adoption so
60+
that users can transition at their own pace. This is especially important
61+
because this new system may take time to achieve feature parity.
6262
* **Integrate with tools, IDEs, and CI systems:** A useful testing library
6363
requires supporting tools for functionality such as listing and selecting
6464
tests to run, launching runner processes, and collecting results. These
@@ -67,22 +67,22 @@ important to consider when designing a new testing API.
6767

6868
### Expressivity
6969

70-
* **Include detailed, actionable failure information**: Tests provide the most
71-
value when they fail and catch bugs, but for a failure to be actionable it
72-
needs to be sufficiently detailed. When a test fails, it should collect and
73-
show as much relevant information as reasonably possible, especially since it
74-
may not reproduce reliably.
75-
* **Offer flexible naming, comments, and metadata:** Test authors should be
76-
able to customize the way tests are presented by giving them an informative
77-
name, comments, or assigning metadata like labels to tests which have things in
78-
common.
70+
* **Include actionable failure details**: Tests provide the most value when they
71+
fail and catch bugs, but for a failure to be actionable it needs to be
72+
sufficiently detailed. When a test fails, it should collect and show as much
73+
relevant information as reasonably possible, especially since it may not
74+
reproduce reliably.
75+
* **Offer flexible naming, comments, and metadata:** Test authors should be able
76+
to customize the way tests are presented by giving them an informative name,
77+
comments, or assigning metadata like labels to tests which have things in
78+
common.
7979
* **Allow customizing behaviors:** Some tests share common set-up or tear-down
80-
logic, which need to be performed once for each test or group. Other times, a
81-
test may begin failing for an irrelevant reason and must be temporarily
82-
disabled. Some tests only make sense to run under certain conditions, such as
83-
on specific device types or when an external resource is available. A modern
84-
testing system should be flexible enough to satisfy all these needs, without
85-
complicating simpler use cases.
80+
logic, which need to be performed once for each test or group. Other times, a
81+
test may begin failing for an irrelevant reason and must be temporarily
82+
disabled. Some tests only make sense to run under certain conditions, such as
83+
on specific device types or when an external resource is available. A modern
84+
testing system should be flexible enough to satisfy all these needs, without
85+
complicating simpler use cases.
8686

8787
### Flexibility
8888

@@ -98,7 +98,7 @@ important to consider when designing a new testing API.
9898
avoid unexpected dependencies or failures.
9999
* **Allow observing test events:** Some use cases require an ability to observe
100100
test events—for example, to perform custom reporting or analysis of results. A
101-
testing library should offer API hooks for event handling.
101+
testing library should offer hooks for event handling.
102102

103103
### Scalability
104104

@@ -145,7 +145,7 @@ any error was caught while evaluating an expression passed to an expectation,
145145
that should be included.
146146

147147
Beyond the values of evaluated expressions, there are other pieces of
148-
information that may be useful to capture and include in expectation APIs:
148+
information that may be useful to capture and include in expectations:
149149

150150
* The **source code location** of the expectation, typically using the format
151151
`#file:#line:#column`. This helps test authors jump quickly to the line of
@@ -174,7 +174,7 @@ information that may be useful to capture and include in expectation APIs:
174174
Since the most important details to include in expectation failure messages are
175175
the expression(s) being compared and the kind of expression, some testing
176176
libraries offer a large number of specialized APIs for detailed reporting. Here
177-
are some expectation APIs from other prominent testing libraries:
177+
are some examples from other prominent testing libraries:
178178

179179
| | Java (JUnit) | Ruby (RSpec) | XCTest |
180180
|----|----|----|----|
@@ -185,7 +185,7 @@ are some expectation APIs from other prominent testing libraries:
185185
| Throws | `assertThrows(E.class, () -> { ... });` | `expect {...}.to raise_error(E)` | `XCTAssertThrowsError(...) { XCTAssert($0 is E) }` |
186186

187187
Offering a large number of specialized expectation APIs is a common practice
188-
among existing libraries: XCTest has 40+ APIs in its
188+
among testing libraries: XCTest has 40+ functions in its
189189
[`XCTAssert` family](https://developer.apple.com/documentation/xctest/boolean_assertions);
190190
JUnit has
191191
[several dozen](https://junit.org/junit5/docs/5.0.1/api/org/junit/jupiter/api/Assertions.html);
@@ -199,7 +199,7 @@ Although this approach allows straightforward reporting, it is not scalable:
199199
new APIs and remember to use the correct one in each circumstance, or risk
200200
having unclear test results.
201201
* More complex use cases may not be supported—for example, if there is no
202-
expectation API for testing that a `Sequence` starts with some prefix using
202+
expectation for testing that a `Sequence` starts with some prefix using
203203
`starts(with:)`, the user may need a workaround such as adding a custom
204204
comment which includes the sequence for the results to be actionable.
205205
* It requires testing library maintainers add bespoke APIs supporting many use
@@ -209,9 +209,10 @@ Although this approach allows straightforward reporting, it is not scalable:
209209

210210
We believe expectations should strive to be as simple as possible and involve
211211
few distinct APIs, but be powerful enough to include detailed results for every
212-
expression. Instead of offering a large number of specialized expectation APIs,
213-
the APIs should be general and rely on built-in language operators and APIs to
214-
cover all use cases.
212+
expression. Instead of offering a large number of specialized expectations,
213+
there should only be a few basic expectations and they should rely on ordinary
214+
expressions, built-in language operators, and the standard library to cover all
215+
use cases.
215216

216217
#### Evaluation rules
217218

@@ -247,15 +248,15 @@ to control on a global, per-test, or per-expectation basis.
247248
Often, expectation APIs do not preserve raw expression values when reporting a
248249
failure, and instead generate a string representation of those values for
249250
reporting purposes. Although a string representation is often sufficient,
250-
failure presentation could be improved if an expectation API were able to keep
251+
failure presentation could be improved if an expectation were able to keep
251252
values of certain, known data types.
252253

253254
As an example, imagine a hypothetical expectation API call
254255
`ExpectEqual(image.height, 100)`, where `image` is a value of some well-known
255256
graphical image type `UILibrary.Image`. Since this uses a known data type, the
256-
expectation API could potentially keep `image` upon failure and include it in
257-
test results, and then an IDE or other tool could present the image graphically
258-
for easier diagnosis. This capability could be extensible and cross-platform by
257+
expectation could potentially keep `image` upon failure and include it in test
258+
results, and then an IDE or other tool could present the image graphically for
259+
easier diagnosis. This capability could be extensible and cross-platform by
259260
using a protocol to describe how to convert arbitrary values into one of the
260261
testing library’s known data types, delivering much richer expectation results
261262
presentation for commonly-used types.
@@ -508,13 +509,13 @@ will likely span several years, and we aim to thoughtfully design and deliver a
508509
solution that will be even more powerful while bearing in mind the many lessons
509510
learned from maintaining it over the years.
510511

511-
## A new API direction
512+
## A new direction
512513

513514
> [!NOTE]
514515
> The approach described below is not meant to include a solution for _every_
515516
> consideration or feature discussed in this document. It describes a starting
516-
> point for this new API direction, and covers many of the topics, but leaves
517-
> some to be pursued as part of follow-on work.
517+
> point for this new direction, and covers many of the topics, but leaves some
518+
> to be pursued as part of follow-on work.
518519
519520
The new direction includes 3 major components exposed via a new module named
520521
`Testing`:
@@ -751,11 +752,11 @@ trait shown here on `InnerTests` would have the effect of adding the tag
751752

752753
#### Parameterized tests
753754

754-
Parameterized testing is easy to support using this API direction: The `@Test`
755-
functions shown earlier do not accept any parameters, making them
756-
non-parameterized, but if a `@Test` function includes a parameter, then a
757-
different overload of `Test.init` is used which accepts a `Collection` whose
758-
associated `Element` type matches the type of the parameter:
755+
Parameterized testing is easy to support using this API: The `@Test` functions
756+
shown earlier do not accept any parameters, making them non-parameterized, but
757+
if a `@Test` function includes a parameter, then a different overload of the
758+
`@Test` macro can be used which accepts a `Collection` whose associated
759+
`Element` type matches the type of the parameter:
759760

760761
```swift
761762
/// Declare a test function parameterized over a collection of values.
@@ -837,7 +838,7 @@ let b = 4
837838

838839
#### Handling collections
839840

840-
We can also leverage existing language features for yet more expressiveness.
841+
We can also leverage built-in language features for yet more expressiveness.
841842
Consider the following test logic:
842843

843844
```swift
@@ -852,12 +853,107 @@ leverage
852853
to capture exactly how these arrays differ and present that information to the
853854
developer as part of the test output or in the IDE.
854855

856+
### Project governance
857+
858+
For this testing solution to stand the test of time, it needs to be well
859+
maintained and any new features added to it should be thoughtfully designed and
860+
undergo community review.
861+
862+
The codebase for this testing system will be open source. Any significant
863+
additions to its feature set or changes to its API or behavior will follow a
864+
process inspired by, but separate from,
865+
[Swift Evolution](https://www.swift.org/swift-evolution/). Changes will be
866+
described in writing using a standard
867+
[proposal template](https://github.com/apple/swift-testing/blob/main/Documentation/Proposals/0000-proposal-template.md) and discussed in the
868+
[swift-testing](https://forums.swift.org/c/related-projects/swift-testing/103)
869+
category of the Swift Forums.
870+
871+
A new group—tentatively named the _Swift Testing Workgroup_—will be formed to
872+
act as the primary governing body for this project. The responsibilities of
873+
members of this workgroup will include:
874+
875+
* defining and approving roadmaps for the project;
876+
* scheduling proposal reviews;
877+
* guiding community discussion;
878+
* making decisions about proposals; and
879+
* working with members of related workgroups, such as the Ecosystem, Platform,
880+
Server, or Documentation workgroups, on topics which intersect with their
881+
areas.
882+
883+
The membership of this workgroup and specifics about its role in the project
884+
will be formalized separately.
885+
886+
### Distribution
887+
888+
As mentioned in [Approachability](#approachability) above, testing should be
889+
easy to use. The easier it is to write a test, the more tests will be written,
890+
and software quality generally improves with more automated tests.
891+
892+
Most open source Swift software is distributed as source code and built by
893+
clients, using tools such as Swift Package Manager. Although this is very common,
894+
if we took this approach, every client would need to download the source for
895+
this project and its dependencies. A test target is included in SwiftPM's
896+
standard New Package template, so if this project became the default testing
897+
library used by that template, this would mean a newly-created package would
898+
require internet access to build its tests. In addition to downloading source,
899+
clients would also need to _build_ this project along with its dependencies.
900+
Since this project relies on Swift Macros, it depends on
901+
[swift-syntax](https://github.com/apple/swift-syntax) and that is a large
902+
project known to have lengthy build times as of this writing.
903+
904+
Due to these practical concerns, this project will be distributed as part of the
905+
Swift toolchain, at least initially. Being in the toolchain means clients will
906+
not need to download or build this project or its dependencies, and this
907+
project's macros can use the copy of swift-syntax in the toolchain. Longer-term,
908+
if the practical concerns described above are resolved, the library could be
909+
removed from the toolchain, and doing so could yield other benefits such as more
910+
explicit dependency tracking and more portable toolchains.
911+
912+
#### Package support
913+
914+
Although the Swift toolchain will be the primary method of distribution, the
915+
project will support building as a Swift package. This will facilitate
916+
development of the package itself by its maintainers and outside contributors.
917+
918+
Clients will also have the option of declaring an explicit package dependency on
919+
this project rather than relying on the built-in copy in the toolchain. If a
920+
client does this, it will be possible for tools to integrate with the client's
921+
specified copy of the project, as long as it is a version of the package the
922+
tool supports.
923+
924+
### Platform support
925+
926+
Testing should be broadly available across platforms. The long-term goal is for
927+
this project to be available on all platforms Swift itself supports.
928+
929+
Whenever the
930+
[Swift Platform Steering Group](https://www.swift.org/platform-steering-group/)
931+
declares intention to support a new platform, this project will be considered
932+
one of the highest-priority components to get working on the new platform (after
933+
dependencies such as the standard library) since this project will enable
934+
qualification of many other components in the stack. The maintainers of this
935+
project will work with other Swift workgroups or steering groups to help enable
936+
support on new platforms.
937+
938+
One reason why broad plaform support is important is so that this project can
939+
eventually support testing the Swift standard library. The standard library
940+
currently uses a custom library for testing
941+
([StdlibUnittest](https://github.com/apple/swift/tree/main/stdlib/private/StdlibUnittest))
942+
but many of this project's benefits would be useful in that context as well, so
943+
eventually we would like to rebase StdlibUnitTest on this project.
944+
945+
While the goal is for this project to work on every platform Swift does, it
946+
currently does not work on Embedded Swift due to its reliance on existentials.
947+
It is possible this limitation may be overcome through project changes, but if
948+
it cannot be, the project maintainers will work with other Swift work/steering
949+
groups to identify a solution.
950+
855951
## Alternatives considered
856952

857953
### Declarative test definition using Result Builders
858954

859-
While exploring new testing API directions, we considered and thoroughly
860-
prototyped an approach relying heavily on
955+
While exploring new testing directions, we considered and thoroughly prototyped
956+
an approach relying heavily on
861957
[Result Builders](https://docs.swift.org/swift-book/LanguageGuide/AdvancedOperators.html#ID630).
862958
At a high level, the idea involved a few pieces:
863959

@@ -903,10 +999,9 @@ significant drawbacks:
903999
Another approach for defining tests is using a builder pattern with an
9041000
imperative style API. A good example of this is Swift’s own
9051001
[StdlibUnittest](https://github.com/apple/swift/tree/main/stdlib/private/StdlibUnittest)
906-
library which is used to test APIs in the standard library. To define
907-
tests, a user first creates a `TestSuite` and then calls
908-
`.test("Some name") { /* body */ }` one or more times to add a closure
909-
containing each test.
1002+
library which is used to test the standard library. To define tests, a user
1003+
first creates a `TestSuite` and then calls `.test("Some name") { /* body */ }`
1004+
one or more times to add a closure containing each test.
9101005

9111006
One problem with generalizing this approach is that it doesn’t have a way to
9121007
deterministically discover tests either after a build or while authoring

0 commit comments

Comments
 (0)