簡體   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