Code Kata thoughts for Swift Testing

I love what you've done with Swift-Testing and want to offer some thoughts on how it might be used in teaching in the way that Code Katas work in Ruby and other languages.

I'm not sure that I'm asking for the correct solution below, but let me describe what I think I want.

Imagine you are teaching using TDD and want to provide a test suite that students have to make pass. Some of the tests contain code that doesn't even compile as the code being called doesn't exist yet.

struct Example {
}

@Test func example() {
let ex = Example()
#expect(ex.value == 6) // doesn't compile
}

I think I want a trait for @Test that is more extreme than disabled as I don't even want this test to be compiled and I don't want to comment out the test.

I also think I want something more extreme than #require so that when the test is enabled the test compiles but emits an error that Example() doesn't have a member named value - or something like that.

Finally, but beyond the scope of this request, I'd love to be able to create a test runner that only exposes one failing test at a time. So when a coder has passed tests 1-4, test 5 will be enabled either by changing disabled to enabled or changing the non-compiling trait to enabled.

Again, I'm more interested in the pedagogical flow of a Code Kata than in the implementation I (most likely wrongly) suggest.

Thank you,

Daniel

3 Likes

Macros require that the code it's attached to be syntactically and lexically valid before macro expansion occurs. That means that if a test function doesn't compile in isolation, @Test won't be evaluated at all and you'll just get a compiler diagnostic. This is an intentional design aspect of Swift macros; @Douglas_Gregor or @hborla can probably provide more context than I can here.

I understand, in broad strokes, the sort of design you're interested in. Swift Testing runs tests in parallel, so there's not really a general concept of test ordering: the order of execution of tests is de facto randomized by the task scheduler. You can force serial execution using the experimental .serialized trait—perhaps what you could do is take a look at the implementation of that trait and create a similar trait that also tracks how many tests have run so far (and stop at some externally-defined limit.) Then you could write something like:

@Test(.codeKata(id: "unique kata ID"))
func test5() { /* ... */ }

Where .codeKata(id:) tracks the execution of all tests with a common ID (so you can have multiple katas AKA sequences of tests, maybe?)

4 Likes

Thanks

I actually don't need serial execution so much as what you suggest with .codeKata() so I can restrict which tests run.

As for the macros - I understand that restriction (macros were last year's adventure for me) and am thinking of how there might be a way around that.

Thank you,

Daniel

1 Like

You understand your needs better than I do! :slight_smile:

1 Like