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

Test Blocks In Src #4207

Open
mcmah309 opened this issue Dec 15, 2024 · 3 comments
Open

Test Blocks In Src #4207

mcmah309 opened this issue Dec 15, 2024 · 3 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@mcmah309
Copy link

mcmah309 commented Dec 15, 2024

It would be nice if tests could live next to their declarations in source. This would make it clearer what code does, make tests easier to write, and users would no longer have to maintain a mirrored directory tree in tests.
e.g.

int plusOne(int x) => x + 1;

test "PluseOne" {
   import 'package:test/test.dart'; // can import regular dependencies or dev dependencies

    expect(plusOne(-1), 0);
    expect(plusOne(0), 1);
    expect(plusOne(1), 2);
}

Prior art

Edit:
Alternative proposed representation

test {
   import 'package:test/test.dart';

    main() {
        test("PlusOne", () {
            expect(plusOne(-1), 0);
            expect(plusOne(0), 1);
            expect(plusOne(1), 2);
        });
    }
}
@mcmah309 mcmah309 added the feature Proposed language feature that solves one or more problems label Dec 15, 2024
@lrhn
Copy link
Member

lrhn commented Dec 15, 2024

Sounds very specialized. It would make testing into a language feature, not just something you do using normal dart code and a helper library, a special compilation mode with some code only accessible when compiled for testing.

Even if Dart chose to do that, it would probably be normal Day code inside the test block, so more likely

... The library code ...
test {
  import test; // assume shorthand imports.
  main() {
    test("Foo", () {
       // Some test code 
    });
  }
}

That would make the code inside the test block be normal top-level declaration code, which makes it so much easier to explain what's going on: it's conditional compilation. The code is only included when compiling on the mode called "test", and the compiler didn't have to understand what that means, just that modes exist.
(But would make it harder to have multiple blocks of code that get combined into one main function.)

I'd probably generalize it to something similar to conditional imports/parts, where the condition is something you could use in such a conditional URI:

if (test) {
  // Declarations
}

What would work the same as of that code was written inside a separate part file and included as

part "empty.dart"
  if (dart.mode.test) "the_code.dart";

Then you can define multiple modes.

Still doesn't allow you to have multiple blocks of test code if each tries to introduce a main function.
We need some way to combine multiple top-level declarations into sobbing that can be acted on by a generic main function.
There is a proposal for "accumulated collections", where multiple declarations of the same collection connect all the values. Maybe something like

if (test) {
  import test/inline;
  accumulator Tests tests = {};
  main() => testSet(tests);
} 
....
if (test) {
  tests += {
    test("Foo", () {
      // The test 
    });
  };
}

Which would collect all the test declarations as literals of one set, and then access that set once in main.

This is still much more complicated than what is being asked for, and therefore has more overhead, but it's also a general language feature that could be used for other things than testing. That makes it more likely to be worth doing. (Maybe, if that generality has any real uses.)

@tatumizer
Copy link

a general language feature that could be used for other things than testing

I'm aware of two more features: defer and atExit. Each of them requires language support anyway (to varying degrees).

@mcmah309
Copy link
Author

mcmah309 commented Dec 15, 2024

test {
  import test; // assume shorthand imports.
  main() {
    test("Foo", () {
       // Some test code 
    });
  }
}

I agree this representation is better. While slightly more verbose, it is actually more powerful.

it's conditional compilation. The code is only included when compiling on the mode called "test", and the Collier didn't have to understand what that means, just that modes exist.

Not necessarily, it may more sense if test blocks are treated as sub-programs that are only run during something like dart test, while ignored during regular compilation. Same concept as any regular *_test.dart file.

I'd probably generalize it to something similar to conditional imports/parts, where the condition is something you could use in such a conditional URI:

if (test) {
  // Declarations
}

I like the idea of making is more generalizable, if there are other use cases. But a top level if declaration does not sit well with my syntactically. e.g.

if (test) {
   import test;
  main() {
    test("Foo", () {
       // Some test code 
    });
  }
}

Is less clear what is happening here, plus additional declarations like classes may increase this weirdness.

Still doesn't allow you to have multiple blocks of test code if each tries to introduce a main function.

Yes, I think this is a problem.

This is still much more complicated than what is being asked for, and therefore has more overhead, but it's also a general language feature that could be used for other things than testing.

What may some of those use cases be? I agree this seems more complicated and possibly prone to user errors in declarations. I'm not sure how the user experience would be to identify/debug as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

3 participants