1
- # A New API Direction for Testing in Swift
1
+ # A New Direction for Testing in Swift
2
2
3
3
## Introduction
4
4
5
5
A key requirement for the success of any developer platform is a way to use
6
6
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
8
8
direction for testing in Swift.
9
9
10
10
We start by defining our basic principles and describe specific features that
11
11
embody those principles. We then discuss several design considerations
12
12
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.
14
14
15
15
## Principles
16
16
@@ -37,28 +37,28 @@ some tests to opt-out. It should be effortless to repeat a test with different
37
37
inputs and see granular results. The library should be lightweight and
38
38
efficient, imposing minimal overhead on the code being tested.
39
39
40
- ## Features of a great testing API
40
+ ## Features of a great testing system
41
41
42
42
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 .
44
44
45
45
### Approachability
46
46
47
47
* ** Be easy to learn and use** : There should be few individual APIs to
48
48
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/ ) .
51
51
* ** Validate expected behaviors or outcomes** : The most important job of any
52
52
testing library is checking that code meets specific expectations—for example,
53
53
by confirming that a function returns an expected result or that two values
54
54
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 .
58
58
* ** 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.
62
62
* ** Integrate with tools, IDEs, and CI systems:** A useful testing library
63
63
requires supporting tools for functionality such as listing and selecting
64
64
tests to run, launching runner processes, and collecting results. These
@@ -67,22 +67,22 @@ important to consider when designing a new testing API.
67
67
68
68
### Expressivity
69
69
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.
79
79
* ** 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.
86
86
87
87
### Flexibility
88
88
@@ -98,7 +98,7 @@ important to consider when designing a new testing API.
98
98
avoid unexpected dependencies or failures.
99
99
* ** Allow observing test events:** Some use cases require an ability to observe
100
100
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.
102
102
103
103
### Scalability
104
104
@@ -145,7 +145,7 @@ any error was caught while evaluating an expression passed to an expectation,
145
145
that should be included.
146
146
147
147
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 :
149
149
150
150
* The ** source code location** of the expectation, typically using the format
151
151
` #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:
174
174
Since the most important details to include in expectation failure messages are
175
175
the expression(s) being compared and the kind of expression, some testing
176
176
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:
178
178
179
179
| | Java (JUnit) | Ruby (RSpec) | XCTest |
180
180
| ----| ----| ----| ----|
@@ -185,7 +185,7 @@ are some expectation APIs from other prominent testing libraries:
185
185
| Throws | ` assertThrows(E.class, () -> { ... }); ` | ` expect {...}.to raise_error(E) ` | ` XCTAssertThrowsError(...) { XCTAssert($0 is E) } ` |
186
186
187
187
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
189
189
[ ` XCTAssert ` family] ( https://developer.apple.com/documentation/xctest/boolean_assertions ) ;
190
190
JUnit has
191
191
[ 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:
199
199
new APIs and remember to use the correct one in each circumstance, or risk
200
200
having unclear test results.
201
201
* 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
203
203
` starts(with:) ` , the user may need a workaround such as adding a custom
204
204
comment which includes the sequence for the results to be actionable.
205
205
* 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:
209
209
210
210
We believe expectations should strive to be as simple as possible and involve
211
211
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.
215
216
216
217
#### Evaluation rules
217
218
@@ -247,15 +248,15 @@ to control on a global, per-test, or per-expectation basis.
247
248
Often, expectation APIs do not preserve raw expression values when reporting a
248
249
failure, and instead generate a string representation of those values for
249
250
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
251
252
values of certain, known data types.
252
253
253
254
As an example, imagine a hypothetical expectation API call
254
255
` ExpectEqual(image.height, 100) ` , where ` image ` is a value of some well-known
255
256
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
259
260
using a protocol to describe how to convert arbitrary values into one of the
260
261
testing library’s known data types, delivering much richer expectation results
261
262
presentation for commonly-used types.
@@ -508,13 +509,13 @@ will likely span several years, and we aim to thoughtfully design and deliver a
508
509
solution that will be even more powerful while bearing in mind the many lessons
509
510
learned from maintaining it over the years.
510
511
511
- ## A new API direction
512
+ ## A new direction
512
513
513
514
> [ !NOTE]
514
515
> The approach described below is not meant to include a solution for _ every_
515
516
> 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.
518
519
519
520
The new direction includes 3 major components exposed via a new module named
520
521
` Testing ` :
@@ -751,11 +752,11 @@ trait shown here on `InnerTests` would have the effect of adding the tag
751
752
752
753
#### Parameterized tests
753
754
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:
759
760
760
761
``` swift
761
762
/// Declare a test function parameterized over a collection of values.
@@ -837,7 +838,7 @@ let b = 4
837
838
838
839
#### Handling collections
839
840
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.
841
842
Consider the following test logic:
842
843
843
844
``` swift
@@ -852,12 +853,107 @@ leverage
852
853
to capture exactly how these arrays differ and present that information to the
853
854
developer as part of the test output or in the IDE.
854
855
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
+
855
951
## Alternatives considered
856
952
857
953
### Declarative test definition using Result Builders
858
954
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
861
957
[ Result Builders] ( https://docs.swift.org/swift-book/LanguageGuide/AdvancedOperators.html#ID630 ) .
862
958
At a high level, the idea involved a few pieces:
863
959
@@ -903,10 +999,9 @@ significant drawbacks:
903
999
Another approach for defining tests is using a builder pattern with an
904
1000
imperative style API. A good example of this is Swift’s own
905
1001
[ 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.
910
1005
911
1006
One problem with generalizing this approach is that it doesn’t have a way to
912
1007
deterministically discover tests either after a build or while authoring
0 commit comments