如何使用build_runner和source_gen以字符串形式获取函数体?

问题描述 投票:0回答:1

我的目标是让我的单元测试易于理解。目前,它们很难理解,因为它们有太多嵌套函数。

我想使用 build_runner 生成单元的代码,并解开所有函数。

这是我当前测试的一个例子:

测试.dart

import 'package:example_usage/src/unwrap.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';

class Cat {
  String sound() => "Meow";
  int walk() => 4;
}

class Dog {
  final Cat cat;

  Dog(this.cat);

  String sayHi() {
    return this.cat.sound();
  }

  int jump() {
    return this.cat.walk();
  }
}

class MockCat extends Mock implements Cat {}

void main() {
  MockCat cat;
  Dog dog;

  @UnWrap()
  void setupCatSoundStub() {
    when(cat.sound()).thenReturn("Woof");
  }

  @UnWrap()
  void setupCatWalkstub() {
    when(cat.walk()).thenReturn(2);
  }

  @UnWrap()
  void expectCatCalled() {
    verify(cat.sound());
  }

  @UnWrap()
  void testDogWoof() {
    setupCatSoundStub();
    dog = Dog(cat);
    final sound = dog.sayHi();
    expect(sound, "Woof");
    expectCatCalled();
  }

  void expectCatWalked() {
    verify(cat.walk());
  }

  group('Dog Cat Play', () {
    setUp(() {
      cat = MockCat();
    });

    test('Dog woof', () {
      testDogWoof();
    });

    test('Dog woof then jump', () {
      testDogWoof();
      setupCatWalkstub();
      final steps = dog.jump();
      expect(steps, 2);
      expectCatWalked();
    });
  });
}

我想生成这样的代码

_$test.dart

void _$main() {
  MockCat cat;
  Dog dog;
  void expectCatWalked() {
    verify(cat.walk());
  }

  group('Dog Cat Play', () {
    setUp(() {
      cat = MockCat();
    });

    test('Dog woof', () {
      // testDogWoof();
      // setupCatSoundStub();
      when(cat.sound()).thenReturn("Woof");
      dog = Dog(cat);
      final sound = dog.sayHi();
      expect(sound, "Woof");
      // expectCatCalled();
      verify(cat.sound());
    });

    test('Dog woof then jump', () {
      // testDogWoof();
      // setupCatSoundStub();
      when(cat.sound()).thenReturn("Woof");
      dog = Dog(cat);
      final sound = dog.sayHi();
      expect(sound, "Woof");
      // expectCatCalled();
      verify(cat.sound());
      // setupCatWalkstub();
      when(cat.walk()).thenReturn(2);
      final steps = dog.jump();
      expect(steps, 2);
      expectCatWalked();
    });
  });
}

我在网上找到了一些教程,但我可以找到有关将函数体转换为字符串的文档(有些像 JavaScript 的

Function.prototype.toString()
方法)我是代码生成新手,所以我尝试打印所有字段,但我找不到类似的内容。

import 'dart:async';

import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:source_gen/source_gen.dart';

class InfoGenerator extends Generator {
  @override
  FutureOr<String> generate(LibraryReader library, BuildStep buildStep) {
    var buffer = StringBuffer();

    // library.allElements.forEach((element) {
    //   buffer.writeln(
    //       '// ${element.displayName} - ${element.source.fullName} - ${element.declaration}');
    // });
    library.allElements.whereType<TopLevelVariableElement>().forEach((element) {
      buffer.writeln('/*');
      buffer.writeln(element.toString());
      buffer.writeln('*/');
      buffer.writeln(
          '// ${element.name} - ${element.kind.displayName} - ${element.declaration}');
    });

    return buffer.toString();
  }
}

我对注释也是新手,所以我只是做了这个

/// What to do here ?
class UnWrap {
  const UnWrap();
}

我正在尝试做的事情可能吗?

flutter dart code-generation build-runner
1个回答
0
投票

您尝试做的事情确实是可能的,但它需要一种更复杂的方法,使用分析器包来解析和操作 Dart 代码的抽象语法树(AST)。让我们一步步分解问题并创建解决方案。

import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:source_gen/source_gen.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';

class UnWrapGenerator extends GeneratorForAnnotation<UnWrap> {
  @override
  String generateForAnnotatedElement(
      Element element, ConstantReader annotation, BuildStep buildStep) {
    if (element is FunctionElement) {
      final functionBody = _getFunctionBody(element);
      return '// Unwrapped ${element.name}:\n$functionBody\n';
    }
    return '';
  }

  String _getFunctionBody(FunctionElement element) {
    final result = element.session.getParsedLibraryByElement(element.library);
    if (result is ParsedLibraryResult) {
      final functionDeclaration = result.getElementDeclaration(element);
      if (functionDeclaration != null) {
        final functionNode = functionDeclaration.node as FunctionDeclaration;
        final body = functionNode.functionExpression.body;
        if (body is BlockFunctionBody) {
          return body.block.toSource();
        }
      }
    }
    return '// Unable to retrieve function body';
  }
}

Builder unwrapGenerator(BuilderOptions options) =>
    LibraryBuilder(UnWrapGenerator(), generatedExtension: '.unwrap.dart');

该生成器执行以下操作:

它扩展了 GeneratorForAnnotation,这意味着它只会处理用 @UnWrap() 注解的元素。 在generateForAnnotatedElement方法中,它检查带注释的元素是否是一个函数。 如果它是一个函数,它会调用 _getFunctionBody 来检索函数的主体。 _getFunctionBody 方法使用分析器解析库并找到函数声明,然后将函数体提取为字符串。

现在,您需要设置 build.yaml 文件才能使用此生成器:

targets:
  $default:
    builders:
      your_package_name|unwrap:
        enabled: true

builders:
  unwrap:
    import: "package:your_package_name/builder.dart"
    builder_factories: ["unwrapGenerator"]
    build_extensions: {".dart": [".unwrap.dart"]}
    auto_apply: dependents
    build_to: source

您的包中还需要一个 builder.dart 文件:

library unwrap_builder;

导入'package:build/build.dart'; 导入'src/unwrap_generator.dart';

Builder unwrapGenerator(BuilderOptions 选项) => LibraryBuilder(UnWrapGenerator(), generatedExtension: '.unwrap.dart');

通过此设置,当您运行 pub run build_runner build 时,它将为每个包含 @UnWrap() 注释的文件生成一个新文件。生成的文件将包含解包的函数体。 要在测试中使用这些生成的函数,您可以导入生成的文件并直接调用解包的函数。以下是修改测试文件的方法:

import 'package:example_usage/src/unwrap.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'test.unwrap.dart'; // Import the generated file

class Cat {
  String sound() => "Meow";
  int walk() => 4;
}

class Dog {
  final Cat cat;

  Dog(this.cat);

  String sayHi() {
    return this.cat.sound();
  }

  int jump() {
    return this.cat.walk();
  }
}

class MockCat extends Mock implements Cat {}

void main() {
  MockCat cat;
  Dog dog;

  void expectCatWalked() {
    verify(cat.walk());
  }

  group('Dog Cat Play', () {
    setUp(() {
      cat = MockCat();
    });

    test('Dog woof', () {
      // Use the generated unwrapped functions
      setupCatSoundStub(cat);
      dog = Dog(cat);
      final sound = dog.sayHi();
      expect(sound, "Woof");
      expectCatCalled(cat);
    });

    test('Dog woof then jump', () {
      setupCatSoundStub(cat);
      dog = Dog(cat);
      final sound = dog.sayHi();
      expect(sound, "Woof");
      expectCatCalled(cat);
      setupCatWalkstub(cat);
      final steps = dog.jump();
      expect(steps, 2);
      expectCatWalked();
    });
  });
}

这种方法允许您保持测试的可读性和可维护性,同时在需要时仍然可以访问未包装的函数体。生成的文件将包含每个未包装函数的完整实现,从而可以轻松准确地查看每个测试用例中发生的情况。 每当您更改 @UnWrap() 带注释的函数时,请记住运行 pub run build_runner build 以重新生成未包装的版本。

© www.soinside.com 2019 - 2024. All rights reserved.