Skip to content
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

Investigate how we might get rid of the Quick testing framework #1201

Closed
lawrence-forooghian opened this issue Oct 29, 2021 · 3 comments · Fixed by #1240
Closed

Investigate how we might get rid of the Quick testing framework #1201

lawrence-forooghian opened this issue Oct 29, 2021 · 3 comments · Fixed by #1240
Assignees

Comments

@lawrence-forooghian
Copy link
Collaborator

lawrence-forooghian commented Oct 29, 2021

What?

I'd like us to consider getting rid of the Quick testing framework.

Why?

It's a non-standard thing for new developers to learn, but more importantly it's actually making the test suite harder to maintain.

I was recently trying to investigate some tests that are intermittently failing in CI and on my machine. I wanted to see what happens when I try running these tests in isolation instead of in the full suite. Unfortunately, this wasn’t really possible because of Quick’s lack of integration with the Xcode testing UI controls. So, I think removing it would help us towards our short-term goal of identifying and quarantining flaky tests.

How?

This would be a very time-consuming thing to do manually – our test suite is big. I think it would be possible to automate it, though, using some sort of code transformation tool such as SwiftSyntax with some simple rules, to transform our QuickSpec subclasses into XCTestCase ones.

I think automating it, other than saving us a heap of time, would save us from having to have disruptive code freezes whilst we migrate a file. It would also allow us to experiment with different approaches for how we’d like the migrated code to look.

I suggest that we prototype something to see whether this automation is viable.

References

┆Issue is synchronized with this Jira Uncategorised by Unito

@lawrence-forooghian
Copy link
Collaborator Author

lawrence-forooghian commented Nov 3, 2021

A progress update, and some further thoughts.

What I've done

I've put together a prototype using SwiftSyntax, which is capable of:

  • changing the superclass of a QuickSpec object into XCTestCase
  • transforming all it invocations contained directly inside (i.e. without an intermediate context or describe) a spec() declaration into top-level func test* instance methods

Neither of these are hugely complicated things but they've given me confidence in how to use SwiftSyntax to interrogate the contents of a method and extract things like calls to context(), describe() etc, and how to generate new code.

Viability of the approach

I believe that we will be able to extend this prototype to handle the (more complicated) nested test cases. This is where having access to the AST will truly show its utility over a simple find-and-replace.

Potential complications for automation

We have some (I don't know how many) dynamically-defined tests — for example, it being called in a loop with different parameters. There probably isn't going to be anything automated we can do with these, and my suggestion is that as we encounter them, we switch them to being individual it calls which call a shared method. We can do this as a sequence of small groundwork PRs. As I say, I'm not sure how much work this will be because I think there's no easy way to find them by hand, so we'll find them programatically as we start delving deeper into the syntax tree.

Considerations for landing this change

We need to make sure that these changes are reviewable by a human being. That means we need to be able to view a diff that shows only the structural changes to the tests (we can't expect anyone to review our ~25,000 lines of test code, and even less so expect them to compare two different sets of 25,000 lines of code to make sure most of it hasn't changed by accident). That means that we'll probably need to preserve the current indentation levels of the tests, even though syntactically we're completely flattening the structure (since all of the generated func test* methods will now be at the top level of their class). Once the changes have been reviewed and merged, we can do a second pass with a code formatter to sort out the indentation.

Other logistical considerations

  • We have a bunch of GitHub issues that relate to failing test cases. We need to make sure that it's still possible to locate the test case referred to by these issues. Not thought much about that yet.

Decisions to make

  • We have to figure out exactly how we want to flatten the test structure (i.e. how we want to combine the describe / context (possibly multiple) / it names into a single test method). We can experiment with different options here. I wish there were some way to not have to flatten the test structure – that is, to find some alternative syntactical structure that allows us to preserve the nesting. (It would also probably make the whole exercise amenable to a find & replace instead.) I couldn't think of anything, though. I thought about nested XCTestCase subclasses (or even a hierarchy of subclasses, but that brings further problems like duplicated tests) but this is no use since Xcode doesn't know what to do with the results – you end up with a list of mangled class names in the Xcode test navigator after running the tests.

  • Figure out how we'd like to handle tests skipped using xitfunc skip_test method name, or something else? (Look into XCSkip)

@lawrence-forooghian
Copy link
Collaborator Author

I think the next steps, if we're happy to continue down the automation route, are to build out the prototype to handle nesting.

@lawrence-forooghian
Copy link
Collaborator Author

A status update. Once I finished the first version of the migrator, the migrated code had three new test failures. Long story short, I've spent a fair chunk of time investigating these, operating under the assumption that they must result from the behaviour of the migrated code differing from that of the Quick code. I believe I've now got the migrator emitting code that behaves almost identically to the original Quick code, and I'm happy to say this appears to have resolved the failures.

I'm now going to start landing some of this work into main, starting with some groundwork PRs.

lawrence-forooghian added a commit that referenced this issue Nov 24, 2021
The existing implementation used a loop to implement a form of
table-based testing (i.e. define a list of tests at runtime). Here I
remove this loop, extracting its body to a function (whose name begins
with `reusableTests`) and replacing the iteration with a static list of
invocations fo this function.

The motivation for this change is that it’s groundwork for removing the
Quick testing framework using an automated migrator tool (#1201). The
migrator that I’m writing only knows how to handle a specific set of
statements at a `context`/`describe` level, and would not know what to
do with e.g. an expression like a `forEach` call. (It wouldn’t be able
solve this problem generally without executing the test code during the
migration.)

Hence we replace these expressions with something that the migrator
knows how to handle. The `reusableTests` prefix is important here — the
migrator _does_ know how handle a call to this kind of function and
knows that, unlike other function declarations, it should look inside
the body of this function, where it will find more tests that it needs
to migrate.
lawrence-forooghian added a commit that referenced this issue Nov 24, 2021
The motivation for this change is that it’s groundwork for removing the
Quick testing framework using an automated migrator tool (#1201). The
migrator that I’m writing won’t know what to do with the body of the
closure passed to `pending` — since Quick doesn’t execute this closure,
its contents are a black box to us.
lawrence-forooghian added a commit that referenced this issue Nov 25, 2021
…th-reusableTests

[Groundwork for #1201] Prefix `rsh3*` function names with `reusableTests`
lawrence-forooghian added a commit that referenced this issue Nov 25, 2021
 [Groundwork for #1201] Fix test with duplicate name
lawrence-forooghian added a commit that referenced this issue Nov 25, 2021
[Groundwork for #1201] Replace call to `pending` with `xit`
lawrence-forooghian added a commit that referenced this issue Nov 25, 2021
[Groundwork for #1201] Unroll simple test-defining loops in test files
lawrence-forooghian added a commit that referenced this issue Nov 25, 2021
[Groundwork for #1201] Unroll test-defining loops in test files into `reusableTests` function calls
lawrence-forooghian added a commit that referenced this issue Nov 26, 2021
The motivation for this change is that it’s groundwork for removing the
Quick testing framework using an automated migrator tool (#1201). The
migrator that I’m writing won’t know what to do with arbitrary
statements at a `spec` / `describe` / `context` level. It does, however,
know what to do with variable declarations. So, let’s manually move this
statement inside the declaration of the variable that it’s related to.
lawrence-forooghian added a commit that referenced this issue Nov 26, 2021
There’s no need for these to be instance variables. We’re not mutating
anything, it’s just static testing data.

The motivation for this change is that it’s groundwork for removing the
Quick testing framework using an automated migrator tool (#1201). The
migrator that I’m writing won’t be able to handle the capture lists in
the QuickSpec method call trailing closures, so I’m replacing them.
lawrence-forooghian added a commit that referenced this issue Nov 26, 2021
…-closure

[Groundwork for #1201] Remove capture in Quick method call trailing closures
lawrence-forooghian added a commit that referenced this issue Nov 29, 2021
I happened to notice that these variables are only used in setting up
other variables, so let’s move them inside the declaration of those
variables.

The motivation for this change is that it’s (not strictly necessary)
groundwork for removing the Quick testing framework using an automated
migrator tool (#1201). The migrator that I’m writing will pull all
context-level variable declarations in a file into a single, potentially
large, flattened list of declarations. So it would be nice to make this
list as small as can be. (I'm not saying that the list _is_ now "as
small as can be", I'm just fixing a few things that I happened to
notice.)
lawrence-forooghian added a commit that referenced this issue Nov 29, 2021
[Groundwork for #1201] Remove floating statements at `spec` / `describe` / `context` level
lawrence-forooghian added a commit that referenced this issue Nov 29, 2021
[Groundwork for #1201, but also a fix] Fix setting of fallback retry timeout in tests
lawrence-forooghian added a commit that referenced this issue Nov 29, 2021
These variables are actually only used to set up some other variables,
so we move them inside those variables’ declarations.

The motivation for this change is that it’s groundwork for removing the
Quick testing framework using an automated migrator tool (#1201). The
migrator that I’m writing will pull all `spec` / `describe` / `context`
level local variables to a single namespace for the whole file, so we
need to avoid clashes. The easiest way in this particular case is to
just remove the variables.
lawrence-forooghian added a commit that referenced this issue Nov 29, 2021
The motivation for this change is that it’s groundwork for removing the
Quick testing framework using an automated migrator tool (#1201). The
migrator that I’m writing will pull all `spec` / `describe` / `context`
level local variables outside of the class declaration and into
fileprivate global variables. But if we let the migrator hoist this
particular variable to a global with the same name, then it’ll be
shadowed by XCTestCase’s `name` property, which we don’t want.
lawrence-forooghian added a commit that referenced this issue Nov 30, 2021
[Groundwork for #1201] Rename or remove clashing variables
@lawrence-forooghian lawrence-forooghian linked a pull request Nov 30, 2021 that will close this issue
lawrence-forooghian added a commit that referenced this issue Nov 30, 2021
The motivation for this change is that it’s groundwork for removing the
Quick testing framework using an automated migrator tool (#1201). The
migrator that I’m writing will convert all symbols in test descriptions
to underscores in the emitted method names. This is okay most of the
time, but we have a few places where the symbols have meaning, and
removing them will change the description of the test. The migrator
emits a warning when it thinks this is the case, but it’s up to us to
look at this output and decide which ones need changing.
lawrence-forooghian added a commit that referenced this issue Dec 1, 2021
…ve-meaning

[Groundwork for #1201] Rewrite test descriptions containing meaningful symbols
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging a pull request may close this issue.

1 participant