Your points
One thing you may have glossed over, which can change your outlook on things, is that const
values are inherently static
.
While they each serve a different purpose, there is some similarity in why you can justify the use of static
to list some fixed values, effectively using it in the same way you'd use a const
(but with less restriction on its constness).
- The expected result is not a good candidate for a class since the data is static and there can be no valid objects made out of it( It cannot be seen as a valid blue print).
Your expectation (object instantiation) clashes with using statics (predefined values for comparison), but we can argue over which of these should change.
If you consider your static properties as readonly properties (i.e. "const-like"), which is the basis for using statics to store expected values, then object initialization is not a relevant concern.
If you expect to dynamically generate and store values, then statics are not the way to go.
- Usage of static variables will make it stay in memory until all tests are run.
That is correct, but not necessarily a relevant concern for every test suite.
How appropriate an implementation is, is very much defined by how necessary its existence is. For a tiny test suite, I don't set up an elaborate fixture, but for an enterprise-grade test suite I do.
Similarly, your memory concern is relevant for a large test suite (or one that is expected to grow large at some point), but can be ignored in sufficiently small test suites.
- Expected results are meant to be part of test scenario itself and not to be kept under a different class.
It depends on the complexity of your domain and the tests surrounding it. If your domain uses a lot of information (e.g. metadata, audit fields, outcomes based on input, ...), then listing every expected/actual value in every test scenario can massively lower readability.
Especially when those same values are reusable across many tests, there's a good argument to be made for storing these concrete values away from the test logic itself.
That doesn't mean it has to be static properties, any storage method will do. It could be another file on disk, or an in-memory database, or ... The method of storing the data is irrelevant - what matters is that there are cases where storing the data external to the main body of the test is very much justified.
Example
As a real-world example, I work on a payroll-like software project where you need a boatload of data (people, authorization, schedules, grants, ...) if you want to be able to test a significant software feature. Similarly, a simple action can have a rather complex outcome which you'd want to test.
Both cases mean that any test drowns in literal values and it's hard to spot where the actual logic is.
To counteract this, we've created a reusable test fixture with a fluent syntax to significantly reduce clutter in the test bodies. Think something along the lines of:
fixture
.Given.PersonExists("Anna");
.Given.WorksUnderManager("Bob");
.Given.HasRights(...);
fixture
.When.RequestsPermission("Anna", "Bob", "to go on lunch break");
fixture
.Then.EmailReceived("Bob", "Anna", "RE: Lunch break");
This example has been heavily redacted for proprietary reasons.
When you see "Anna"
and "Bob"
, those are actually references to JSON files stored on disk. We've created certain characters and predefined their data in a test file, and instead of constantly reinventing (and setting up) a new character for every test, we simply use a repository of "test people".
The goal of the test is clear, and the complicated setup process is hidden from view. Each of those methods is actually an arrange (Given
) which mocks some data, an act (When
) which performs real actions, or an assert (Then
) which performs a complicated assertion.
The point I'm trying to get across here is that how applicable a given approach is completely depends on how complex your domain is. What is right for one use case, may be over/underengineered for another.