Kodokon kodokon.com

टेस्ट: Dart यूनिट टेस्ट और विजेट टेस्ट

हाथ से लिखे फ़ेक, एक सिम्युलेटेड क्लॉक और सही pump सहज-आदतों के साथ लॉजिक और विजेट का परीक्षण करें।

11 मिनट · 3 प्रश्न

इस पाठ को Kodokon में खोलें

Flutter का टेस्ट पिरामिड: शुद्ध Dart यूनिट टेस्ट का एक चौड़ा आधार, विजेट टेस्ट की एक परत, कुछ इंटीग्रेशन टेस्ट। एक यूनिट टेस्ट को कभी भी material.dart इम्पोर्ट नहीं करना चाहिए - यदि आपके लॉजिक को इसकी ज़रूरत पड़ती है, तो समस्या तकनीकी नहीं, बल्कि आर्किटेक्चरल है। कोड को उसकी निर्भरताओं से पृथक करने के लिए, जेनरेट किए गए mocks के बजाय हाथ से लिखे फ़ेक को प्राथमिकता दें: एक फ़ेक अनुबंध का एक सरलीकृत पर वास्तव में कार्यात्मक इम्प्लीमेंटेशन होता है (इन-मेमोरी रिपॉज़िटरी, नियंत्रणीय क्लॉक); यह रीफ़ैक्टरिंग को झेल जाता है और सामान्य कोड की तरह पढ़ा जाता है। Mocks, जो इंटरैक्शन को सत्यापित करते हैं ("इस मेथड को दो बार कॉल किया गया"), टेस्ट को इम्प्लीमेंटेशन के विवरणों से जोड़ देते हैं - उन्हें उन प्रोटोकॉल के लिए सुरक्षित रखें जहाँ इंटरैक्शन ही व्यवहार होता है।

DART
import 'package:flutter/foundation.dart';

abstract interface class ScoreRepository {
  Future<int> fetchBest();
}

class ScoreController extends ChangeNotifier {
  ScoreController(this._repository);

  final ScoreRepository _repository;

  int? best;
  Object? error;

  Future<void> load() async {
    try {
      best = await _repository.fetchBest();
      error = null;
    } on Exception catch (e) {
      error = e;
    }
    notifyListeners();
  }
}
परीक्षण के अधीन कोड: material.dart का कोई इम्पोर्ट नहीं।

setUp, हर टेस्ट से पहले ऑब्जेक्ट को दोबारा बनाता है: कोई साझा स्टेट नहीं, इसलिए कोई ऐसा टेस्ट नहीं जो निष्पादन के क्रम पर निर्भर करे - यही उनकी विश्वसनीयता की शर्त है। संयोजनीय matchers (isA<Exception>(), throwsA, isNull) सटीक विफलता संदेश उत्पन्न करते हैं, जो किसी बूलियन से कहीं अधिक उपयोगी हैं। नीचे दूसरे टेस्ट पर ध्यान दें: यह विफलता को फिर ठीक उसके बाद रिकवरी को सत्यापित करता है। त्रुटि पथ भी बाकी सब की तरह कोड हैं - यदि उनका परीक्षण नहीं किया गया, तो उन्हें टूटा हुआ ही मानें।

DART
import 'package:flutter_test/flutter_test.dart';

class FakeScoreRepository implements ScoreRepository {
  int calls = 0;
  bool failNextCall = false;

  @override
  Future<int> fetchBest() async {
    calls++;
    if (failNextCall) {
      failNextCall = false;
      throw Exception('network unavailable');
    }
    return 42;
  }
}

void main() {
  late FakeScoreRepository repository;
  late ScoreController controller;

  setUp(() {
    repository = FakeScoreRepository();
    controller = ScoreController(repository);
  });

  test('exposes the best score', () async {
    await controller.load();

    expect(controller.best, 42);
    expect(controller.error, isNull);
    expect(repository.calls, 1);
  });

  test('captures an error then recovers', () async {
    repository.failNextCall = true;

    await controller.load();
    expect(controller.error, isA<Exception>());

    await controller.load();
    expect(controller.best, 42);
    expect(controller.error, isNull);
  });

  test('notifies its listeners', () async {
    var notifications = 0;
    controller.addListener(() => notifications++);

    await controller.load();

    expect(notifications, 1);
  });
}
पंद्रह लाइन का एक फ़ेक mocks की पूरी लाइब्रेरी की जगह ले लेता है।

एक विजेट टेस्ट स्क्रीन पर कुछ भी रेंडर नहीं करता: यह एक विशिष्ट binding (AutomatedTestWidgetsFlutterBinding) में चलता है जहाँ समय सिम्युलेट किया जाता है और जहाँ हर फ़्रेम माँग पर उत्पन्न होता है। pumpWidget ट्री को माउंट करता है; pump() फ़ेक क्लॉक को आगे बढ़ाता है और एक फ़्रेम उत्पन्न करता है; pump(const Duration(milliseconds: 150)) समय में छलांग लगाता है - किसी मध्यवर्ती एनिमेशन स्टेट को फ़्रीज़ कर उसका निरीक्षण करने के लिए आदर्श। Finder (find.text, find.byType, find.byKey) किसी स्क्रीनशॉट को नहीं, बल्कि Elements के असली ट्री को क्वेरी करते हैं। चूँकि कुछ भी सचमुच एसिंक्रोनस नहीं है - न क्लॉक, न नेटवर्क - इसलिए ये टेस्ट नियतात्मक (deterministic) होते हैं: कोई विफलता हमेशा पुनरुत्पादनीय होती है।

DART
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

class BadgeScreen extends StatefulWidget {
  const BadgeScreen({super.key});

  @override
  State<BadgeScreen> createState() => _BadgeScreenState();
}

class _BadgeScreenState extends State<BadgeScreen> {
  bool _visible = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          if (_visible) const Text('New'),
          IconButton(
            icon: const Icon(Icons.close),
            onPressed: () =>
                setState(() => _visible = false),
          ),
        ],
      ),
    );
  }
}

void main() {
  testWidgets('hides the badge after the tap',
      (tester) async {
    await tester.pumpWidget(
      const MaterialApp(home: BadgeScreen()),
    );
    expect(find.text('New'), findsOneWidget);

    await tester.tap(find.byIcon(Icons.close));
    await tester.pump();

    expect(find.text('New'), findsNothing);
  });
}
tap कोई फ़्रेम ट्रिगर नहीं करता: उसके बाद आने वाला pump आवश्यक है।
BASH
flutter test
flutter test test/score_controller_test.dart
flutter test --coverage
flutter test --update-goldens
किसी फ़ाइल को लक्षित करें, कवरेज मापें, goldens को पुनर्जनित करें।

ज्ञान जांच

सुनिश्चित करें कि आपको इस पाठ के मुख्य बिंदु याद हैं।

  1. repeat() से शुरू किए गए किसी एनिमेशन के सामने pumpAndSettle क्यों विफल हो जाता है?
    • यह तब तक फ़्रेम पंप करता है जब तक कोई शेड्यूल बना रहे: एक अनंत एनिमेशन कभी स्थिर नहीं होता, इसलिए एक टाइमआउट
    • test binding द्वारा repeat() समर्थित नहीं है
    • pumpAndSettle केवल इम्प्लिसिट एनिमेशन को संभालता है
  2. किसी fake और किसी mock के बीच क्या अंतर है?
    • एक fake एक सरलीकृत पर कार्यात्मक इम्प्लीमेंटेशन है; एक mock मुख्य रूप से इंटरैक्शन को सत्यापित करने का काम करता है
    • mock हाथ से लिखा जाता है, fake को किसी टूल द्वारा जेनरेट किया जाता है
    • कोई नहीं: दोनों शब्द पर्यायवाची हैं
    • एक fake किसी Dart इंटरफ़ेस को इम्प्लीमेंट नहीं कर सकता
  3. किसी विजेट टेस्ट में, await tester.pump() आख़िर करता क्या है?
    • यह सिम्युलेटेड क्लॉक को आगे बढ़ाता है और एक ही नया फ़्रेम उत्पन्न करता है
    • यह सभी चल रहे एनिमेशन के पूरा होने की प्रतीक्षा करता है
    • यह टेस्ट को शुरू से फिर से चालू करता है
    • यह असली नेटवर्क अनुरोधों के पूरा होने की प्रतीक्षा करता है