Archive for July 17, 2024

Wednesday, July 17, 2024

XCTest in Xcode 16

Jesse Squires:

The first is waitForNonExistence(withTimeout:), which provides the inverse of the existing waitForExistence(timeout:) API. Finally! This is such a welcome change. Often in UI testing it is more semantic to wait for an element to disappear rather than appear — for example, waiting for a loading indicator or waiting for a UIContentUnavailableView to disappear. Previously, you would have to roll your own implementation or awkwardly use waitForExistence(timeout:) and negate the result — both options are cumbersome and inefficient.

[…]

The second new API is wait(for:toEqual:timeout:), which waits for a property value of an element to equal a new value. This is useful for when the contents of an existing view should be updated and you want to verify the update happened. The most common use case here is likely for checking the contents of labels, text fields, or text views that change based on state updates or user interaction. Previously, there was not a great way to achieve this without introducing artificial timeouts in your test, or changing the UI element’s .accessibilityIdentifier in your app when its contents updated and then checking for the existence of the new identifier.

But he says that the latter currently doesn’t work.

Previously:

Swift Testing in Xcode 16

Stuart Montgomery (September 2023):

I’m excited to announce a new open source project exploring improvements to the testing experience for Swift.

John McCall:

I’m pleased to announce that the Swift project has accepted a vision document for A New Direction for Testing in Swift.

The vision:

It should gracefully coexist with projects that use XCTest or other testing libraries and allow incremental adoption so that users can transition at their own pace.

[…]

When a test fails, it should collect and show as much relevant information as reasonably possible, especially since it may not reproduce reliably.

[…]

There must be a way to carefully store per-test data, to ensure it is isolated to a single test and initialized deterministically to avoid unexpected dependencies or failures.

[…]

Many tests consist of a template with minor variations—for example, invoking a function multiple times with different arguments each time and validating the result of each invocation. A testing library should make this pattern easy to apply, and include detailed reporting so a failure during a single argument is represented clearly.

[…]

Depending on the library, these APIs may be called “assertions”, “expectations”, “checks”, “requirements”, “matchers“, or other names. In this document we refer to them as expectations.

What XCTest called “assertions” are now called “expectations,” what XCTest called “expectations” are now called “confirmations,” and what XCTest called “messages” are now called “comments.” As with SwiftData, it’s not clear to me that these renamings are accomplishing much.

Some specifics:

  1. @Test and @Suite attached macros: These declare test functions and suite types, respectively.
  2. Traits: Values passed to @Test or @Suite which customize the behavior of test functions or suite types.
  3. Expectations #expect and #require: expression macros which validate expected conditions and report failures.

I had hoped that Swift’s runtime features would be enhanced to the point where XCTest-style test discovery would be possible. Instead, it’s being done through macros.

Likewise, the trait stuff appears to be done through special-purpose macros rather than a general way of attaching metadata to functions.

I like the distinction between #require, which halts execution of the test, and #expect, which allows it to continue running and report more failures. #require is also used for unwrapping.

In existing test solutions available to Swift developers, there is limited diagnostic information available for a failed expectation such as assert(2 < 1). The expression is reduced at runtime to a simple boolean value with no context (such as the original source code) available to include in a test’s output.

[…]

We can also extract the components of an expression like a.contains(b) and, on failure, report the value of a and b.

There are two different things going on here. First, XCTest had a large number of macros with verbose names for different kinds of assertions (and object vs. primitive types). It has always been unergonomic, even compared with predecessors such as JUnit and its Objective-C ports. Swift Testing spells almost all of these as simply #expect, which is great. But it’s not clear to me why it took a decade to make this sort of ergonomic improvement. I’ve long been using very short names like eq() and overloads to achieve much the same effect. This was not really possible with Objective-C (without polluting the namespace) because you need macros (which are top-level) in order to capture the source location. But Swift can do this with methods on the test class. It can also use autoclosures to avoid evaluating the failure message on success.

The second cool thing is that, with XCTest, any values that were not passed as arguments to the assertion would be lost at runtime. To get detailed failure information you had to write extra code. Swift Testing’s #expect macro can look at the structure of the expression to extract these values (as well as how they were being used) automatically. This is a killer feature, which I first saw in Python nearly 20 years ago via pytest and once used to test my Objective-C code, too. (Python doesn’t have macros, but import hooks can modify the parsed AST before compilation.)

I’m not sure how to square the principle of scalability with the heavy use of macros and their effect on compilation time. There are also issues with runtime performance, though those seem more easily solveable.

Swift Testing ships with Xcode 16 and has two WWDC videos and a repo.

Rachel Brindle:

My current spike: Implementing a BDD DSL on top of Swift Testing using resultbuilders.

[…]

Already filed my first issue: The Test struct needs a public initializer.

Jonathan Grynspan:

One of the downsides of having a public initializer for Test is that it encourages people to use it. But since it doesn’t produce an instance of Test that’s visible to Swift Testing’s infrastructural layer, there’s no actual way to run it.

It’s a continual worry with Swift and Swift-based APIs that third-party developers will get locked out.

See also:

Previously:

Update (2024-07-18): See also: Jonathan Grynspan (Mastodon). I also want to note this thread, which discusses explicitly using SourceLocation when writing helper functions.

Update (2024-09-18): Antoine van der Lee:

We’ve only seen the basics today, but I’ll update this article in the upcoming weeks with references to more in-depth articles on each macro, test traits, test organization, and more.

Update (2024-10-17): Donny Wals:

So whenever that confirmation closure returns, Swift Testing expects that we have confirmed all of our confirmations. In a traditional completion handler-based setup, this won’t be the case because you’re not awaiting anything because you don’t have anything to await.

This was quite tricky to figure out.

[…]

So what I’ve really found is that the best way to test your completion handler-based APIs is to use continuations.

You can use a continuation to wrap your call to the completion handler-based API and then in the completion handler, do all of your assertions and resume your continuation. This will then resume your test and it will complete your test.

Update (2024-11-01): Donny Wals:

I think that parameterized tests are probably the feature of Swift testing that I am most excited about.

A lot of the syntax changes around Swift testing are very nice but they don’t really give me that much new power. Parameterized testing on the other hand are a superpower.

Update (2024-11-05): Majid Jabrayilov:

The most powerful feature of the Swift Testing framework is the trait system. Traits allow us to annotate a test or test suite to customize its behavior.

Update (2024-12-10): Keith Harrison:

I’m finding that I prefer writing tests with Swift Testing over XCTest (see below for some of the reasons). I started adding new tests with Swift Testing to XCTest unit test source files but at a certain point I want to migrate all the tests in a source file to Swift Testing. These are my notes on that process.

Xcode 16 Announced

Apple:

Discover the latest productivity and performance improvements in Xcode 16. Learn about enhancements to code completion, diagnostics, and Xcode Previews. Find out more about updates in builds and explore improvements in debugging and Instruments.

See also: Download, Release Notes, Updates.

• • •

Adam Bell:

The new Xcode 16 AI autocomplete tech is actually really slick when it has contextual awareness.

Oskar:

First look at AI autocomplete in Xcode 16. It feels really nice to have Tab fill in actual code, and it stays mostly on task. However the speed isn’t great and it does hallucinate a lot. For example, in this run it tried to use a view that did not exist.

It also tries to use UIKit in a Mac app…

Jonathan Wight:

Xcode’s AI code completions are by far the worst AI based code completions I’ve come across.

It just totally hallucinates bullshit code with 100% confidence.

• • •

John Voorhees:

Swift Assist allows developers to type a natural language prompt to generate code and UIs. Code can even be created from the text of a developer’s inline comment. The model that powers Swift Assist has an awareness of the Human Interface Guidelines, Apple’s frameworks, and what Apple considers coding best practices.

Saagar Jha:

I guess if you write Objective-C you don’t get good code completion

Jesse Squires:

Kind of ridiculous that the first 2 default configurations of the latest M3 MacBook Pro (up to $1800!!!) can’t even do full Xcode 16.

Call me crazy, but I think every MacBook Pro should be able to handle all of the new Xcode things.

How does a “Pro” level laptop come with 8GB RAM by default?

• • •

Der Teilweise:

“Fixed an issue where previously resolved Swift compiler diagnostics would reappear in the log and issue navigator in subsequent builds. (119533281)” [I want to believe.]

Marcin Krzyzanowski:

I……… don’t think Xcode 16 actually “fixed an issue where previously resolved Swift compiler diagnostics would reappear in the log and issue navigator in subsequent builds (119533281)”

I’m not finding this to be fixed, either. I’m also still seeing the same spurious errors related to conditional compilation in Swift.

Craig Hockenberry:

If you see the error below when switching SwiftUI previews from a macOS target to an iOS target the following WILL NOT help:

  • Cleaning the build
  • Killing CoreSimulator processes
  • Quitting and restarting Xcode

What WILL work is finding another tab with a hidden preview canvas for the wrong platform. And the subsequent swearing.

• • •

Keith Harrison:

Xcode 16 introduces an experimental setting to explicitly build Swift modules. Here’s my notes from trying it out.

[…]

This explicit discovery and build process avoids the build system having to wait for unbuilt modules. Apple also claims this makes the debugger faster as it can share the already built modules with the debugger.

[…]

I’m not sure how representative my timings are but I’m not seeing any faster builds using explicitly built modules. If anything, it’s slower than the implicit builds in my tests.

Ben Cohen:

If you’ve experienced long pauses when first inspecting variables in the debugger, you may find enabling explicit modules makes a big difference. When this is enabled, the debugger can make use of the same module files created during the build.

• • •

Matt Massicotte:

I still cannot get over it. Xcode getting EditorConfig support AND directory-based compilation. I could cry.

Everyone using local packages take note. Static libs are far more powerful, and their biggest downside is now gone.

Marin Todorov:

I certainly love this new feature in Xcode 16 🥰

Isaiah Carew:

on sequoia you can only run xcode 16 beta.

that means i can’t really install sequoia on my primary dev machine — releasing software on a beta OS with beta Xcode seems… well… bad.

dasdom:

Did you know that you can open a file from another project side by side in your current project in Xcode? The file is not copied to your current project. It is just shown in the editor.

This is especially useful with the demo code from WWDC.

James Dempsey:

It’s disappointing that side-by-side diffs have not made it back to Xcode’s source control views.

It bums me out that the user experience of GitHub on the web is more flexible than the native IDE, especially since the side-by-side diffs used to be there in Xcode.

Aaron Pearce:

Seems no big changes to Xcode Cloud this year. Was hoping to get webhooks that tell us when a build has processed.

Daniel Jalkut:

Best new feature I’ve seen in Xcode 16 Beta: simple breakpoints set in the lldb console are reflected in the UI. No need to reset them on every launch. Unfortunately, regex breakpoints are still not reflected nor settable via UI. Can I dare to dream?

Sami Samhuri:

In Xcode 16 beta 1 the keyboard shortcuts ctrl-n and ctrl-p no longer let you select a completion suggestion, and instead they move the cursor. It bugs me so much I actually filed a feedback. Please dupe if you use those too! I’d hate to have to start using the arrow keys for this.

Der Teilweise:

Oh, in case you wonder why you never heard of @retroactive that is supported in Xcode 16b1: It’s from an enhancement that is currently in review. It’s not mentioned in the Swift 6 migration guide. It is required if you want to use a CNContactViewController.

bjosh:

This wasted 2 days of development time, but in WKNavigationDelegate, the webView(_:decidePolicyFor:decisionHandler:) method has a new type signature that will ONLY work in the latest SDK. The change was that the decisionHandler now has a @MainActor attribute. This causes Swift to recognize that it “almost” meets an optional requirement and suggests that you change it. If you change it, it will cause builds to not include the optional method.

Previously:

Update (2024-07-18): Apple:

Apple Intelligence features are not supported on Virtual Machines and Simulators.

Update (2024-07-30): andrzejr:

Later on after installing Sequoia-beta2 and Xcode16-beta2 the predictive code completion stopped working. The info message in the Xcode settings said then that “Predictive code completion is not supported in this region”. Now, after installing Sequoia beta-4 and Xcode-beta4 the same message says “Predictive code completion is not available when booted from an external disk”.

Previously:

Update (2024-08-08): Avery Vine:

While localization extraction does occur after the build completes (and thus after macro expansion), localizations are extracted by reading the contents of your actual source code, and not expanded macros. Unfortunately, that means that it won’t pick any localized content generated by macros, unless the localized string is part of what’s used to expand the macro.

Update (2024-08-13): Tony Arnold:

Has anybody successfully enabled Explicitly Built Modules with Xcode 16? It’s causing build failures relating to type inference in SwiftUI for me.

Mac Marketshare in Q2 2024

William Gallagher:

Overall, the global PC market grew by 3.4% year on year in Q2 2024, for a total of 62.8 million shipped. Of those, laptops represented 50 million, which by itself is a 4% rise YoY.

Global desktop computer shipments rose by 1% to reach 12.8 million.

Across both laptops and desktops, Apple came in fourth with shipments of 5.5 million. That gave it a 9% market share, which is a 6% increase on the same period in 2023.

Jason Snell:

Apple has been outpacing the PC market for years now, but with the overall market now growing and the possibility of a sales spurt due to the introduction of Copilot Plus PCs, it’ll be interesting to see how Apple fares overall.

Previously: