繁体   English   中英

在隔离内使用 class 的特定实例

[英]Use a specific instance of a class inside an isolate

我通过compute()方法使用隔离来从 API(大约 10k 个条目)中获取、解析和排序数据。

My method getAllCards() is defined inside a class YgoProRepositoryImpl which has an instance of my remote datasource class YgoProRemoteDataSource it is in this class that the method to call my API is defined (it is a simple GET request).

代码示例

ygopro_repository_impl.dart

class YgoProRepositoryImpl implements YgoProRepository {
  final YgoProRemoteDataSource remoteDataSource;

  // ...

  YgoProRepositoryImpl({
    required this.remoteDataSource,
    // ...
  });

  // ...

  static Future<List<YgoCard>> _fetchCards(_) async {
    // As I'm inside an isolate I need to re-setup my locator
    setupLocator();
    final cards = await sl<YgoProRemoteDataSource>()
        .getCardInfo(GetCardInfoRequest(misc: true));
    cards.sort((a, b) => a.name.compareTo(b.name));
    return cards;
  }

  @override
  Future<List<YgoCard>> getAllCards() async {
    final cards = await compute(_fetchCards, null);
    return cards;
  }

  // ...
}

service_locator.dart

import 'package:get_it/get_it.dart';

import 'data/api/api.dart';
import 'data/datasources/remote/ygopro_remote_data_source.dart';
import 'data/repository/ygopro_repository_impl.dart';
import 'domain/repository/ygopro_repository.dart';

final sl = GetIt.instance;

void setupLocator() {
  // ...

  _configDomain();
  _configData();

  // ...

  _configExternal();
}

void _configDomain() {
  //! Domain
  
  // ...

  // Repository
  sl.registerLazySingleton<YgoProRepository>(
    () => YgoProRepositoryImpl(
      remoteDataSource: sl(),
      // ...
    ),
  );
}

void _configData() {
  //! Data
  // Data sources
  sl.registerLazySingleton<YgoProRemoteDataSource>(
    () => YgoProRemoteDataSourceImpl(sl<RemoteClient>()),
  );

  // ...
}

void _configExternal() {
  //! External
  sl.registerLazySingleton<RemoteClient>(() => DioClient());
  
  // ...
}

代码工作正常,但getAllCards()不可测试,因为我无法在我的隔离中注入 YgoProRemoteDataSource 的YgoProRemoteDataSource ,因为它总是会从我的服务定位器中获得引用。

如何不依赖我的服务定位器将YgoProRemoteDataSource注入到我的隔离中并使getAllCards()可测试?

据我了解,您有两个选择,要么通过参数注入static Future<List<YgoCard>> _fetchCards(_) async所需的依赖项,要么在定位器本身中模拟 object 。 我会 go 作为第一个选项,并且有类似的东西:

  static Future<List<YgoCard>> _fetchCards(_,YgoProRemoteDataSource remote) async {
    // No need to set up locator as you passed the needed dependencies
    // setupLocator();
    final cards = await remote
        .getCardInfo(GetCardInfoRequest(misc: true));
    cards.sort((a, b) => a.name.compareTo(b.name));
    return cards;
  }

  @override
  Future<List<YgoCard>> getAllCards() async {
    final cards = await compute(_fetchCards, null);
    return cards;
  }

编辑

刚刚更新了答案,因为在这里编辑它比在评论中更容易......

嗯,我能想到的唯一解决方法是将 setupLocator() function 作为参数传递给 class YgoProRepositoryImpl:

final Function setupLocator;
  YgoProRepositoryImpl({
    required this.remoteDataSource,
required this.setupLocator;
    // ...
  });

这样,您可以传递一个模拟来设置模拟类或service_locator.dart的真实setupLocator 这可能不太优雅。 但它应该使它可测试,因为现在你可以模拟设置并且它没有在 function 中硬编码

你真的需要测试getCards() function 吗? 你在那里真正测试什么? compute有效,当然希望 Dart SDK 团队对此进行测试。

_fetchCards()setupLocator()也不需要测试,这是您的测试逻辑的先决条件。 无论如何,您都想更改测试的设置。

所以你真正想要测试的是获取和排序。 将其重组为可测试的 static function 并预先设置您的定位器。 在上面放一个@visibleForTesting注释。

另外,根据您在服务定位器中绑定的数量,这对于之后仅使用一个存储库可能会产生巨大的开销。

例子:

  static Future<List<YgoCard>> _fetchCards(_) async {
    // As I'm inside an isolate I need to re-setup my locator
    setupLocator();
    return reallyFetchCards();
  }

  @visibleForTesting
  static Future<List<YgoCard>> reallyFetchCards() async {
    final cards = await sl<YgoProRemoteDataSource>()
        .getCardInfo(GetCardInfoRequest(misc: true));
    cards.sort((a, b) => a.name.compareTo(b.name));
    return cards;
  }

  @override
  Future<List<YgoCard>> getAllCards() async {
    final cards = await compute(_fetchCards, null);
    return cards;
  }

测试:


// Setup SL and datasource
...

final cards = await YgoProRepositoryImpl.reallyFetchCrads();

// Expect stuff

不是最干净的选项,但您可以在定位器主体中使用Platform.environment.containsKey('FLUTTER_TEST')环境变量来查看代码是否正在测试 session 中执行并调整正在实例化的内容。

请注意,您需要使用flutter_test package 而不是 Dart 的标准test (我相信是这种情况,因为您已经在使用compute() ,它是 Flutter SDK 的一部分)。

做了更认真的尝试,请看repo: https://github.com/maxim-saplin/compute_sl_test_sample

Essentially with the current state of affairs with Flutter/Dart you can't pass neither closures nor classes containing closures across isolates boundaries (yet that might change when newer features in Dart land Flutter https://github.com/dart-lang/sdk /issues/46623#issuecomment-916161528 )。 这意味着如果您不希望任何测试代码成为发布版本的一部分,则您无法传递服务定位器(包含闭包)或欺骗隔离以通过闭包实例化定位器的测试版本。 然而,您可以轻松地将数据源实例传递给隔离,以在其入口点作为参数使用。

此外,我认为要求隔离来重建整个服务定位器是没有意义的。 compute() 背后的整个想法是创建一个短的离开隔离,运行计算,返回结果并终止隔离。 初始化定位器是一种开销,最好避免。 此外,compute() 的整个概念似乎与应用程序的 rest 尽可能隔离。

您可以克隆存储库并运行测试。 关于样本的几句话:

  • 基于 Flutter 计数器启动器应用程序
  • lib/classes.dart重新创建您提供的代码片段
  • test/widget_test.dart验证YgoProRepositoryImpl在隔离运行假数据源版本时工作正常
  • YgoProRepositoryImpl模仿真实实现,位于 classes.dart 和YgoProRepositoryFake模仿测试版
  • 在 flutter_test 下运行隔离需要在tester.runAsync()中包装测试主体,以便进行实时异步执行(而不是测试默认使用的假异步并依赖泵送来推进测试时间)。 在这种模式下运行测试可能会很慢(实际上有 0.5 秒的等待时间),以不使用 compute() 或未在许多测试中测试的方式构建测试是合理的

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM