e
sv

Mockito ile birim testi Flutter kodu

avatar

Yazılım Method

  • e 0

    Mutlu

  • e 0

    Eğlenmiş

  • e 0

    Şaşırmış

  • e 0

    Kızgın

  • e 0

    Üzgün

Birim testi, tek bir yöntemin veya sınıfın beklendiği gibi çalışıp çalışmadığını doğrular. Ayrıca, yeni değişiklikler yapıldığında mevcut mantığın hala çalışıp çalışmadığını onaylayarak sürdürülebilirliği artırır.

Genel olarak birim testlerinin yazılması kolaydır ancak bir test ortamında çalıştırılır. Bu, varsayılan olarak, bir ağ çağrısı veya HTTP isteği yapıldığında durum kodu 400 olan boş bir yanıt üretir. Bunu düzeltmek için, bir HTTP isteği yaptığımızda sahte bir yanıt döndürmek için Mockito'yu kolayca kullanabiliriz. Mockito, ilerledikçe kademeli olarak tanıtacağımız çeşitli kullanım örneklerine sahiptir.

Bu eğitimde, Flutter kodunu test etmek için Mockito'nun nasıl kullanılacağını göstereceğiz. Taklitlerin nasıl oluşturulacağını, saplama verilerinin nasıl oluşturulacağını ve akış yayan yöntemler üzerinde testler yapmayı öğreneceğiz. Başlayalım!

Mockito nedir?

Mockito, mevcut bir sınıfın sahte bir uygulamasını oluşturmayı kolaylaştıran iyi bilinen bir pakettir . Bu işlevleri tekrar tekrar yazmanın stresini ortadan kaldırır. Ayrıca, Mockito, beklenen bir sonucu test edebilmemiz için girişi kontrol etmeye yardımcı olur.

Mockito'yu varsayımsal olarak kullanmak, birim testleri yazmayı kolaylaştırır, ancak kötü mimariyle, alay etme ve birim testleri yazma kolayca karmaşık hale gelebilir.

Bu öğreticinin ilerleyen bölümlerinde, kod tabanını görünüm modeli ve depolar gibi farklı test edilebilir parçalara ayırmayı içeren model-görünüm-görünüm modeli (MVVM) modeliyle Mockito'nun nasıl kullanıldığını öğreneceğiz.

Sahte ve saplama verileri oluşturma

Sahteler, gerçek sınıfların sahte uygulamalarıdır. Genellikle bir testin beklenen sonucunu kontrol etmek için veya gerçek sınıflar bir test ortamında hatalara eğilimli olduğunda kullanılırlar.

Bunu daha iyi anlamak için, gönderi gönderme ve alma ile ilgilenen bir uygulama için birim testleri yazacağız.

Proje yapısına genel bakış

Başlamadan önce gerekli tüm paketleri projemize ekleyelim.

 bağımlılıklar:
  dio: ^4.0.6 # HTTP istekleri yapmak için

dev_bağımlılıklar:
  build_runner: ^2.2.0 # Kod oluşturmak için (Mocks, vb.)
  mockito: ^5.2.0 # Alay ve inatçılık için

Hem depolar hem de görünüm modelleri için testler içeren MVVM ve depo modelini kullanacağız. Flutter'da, tüm testleri test klasörüne yerleştirmek iyi bir uygulamadır ve lib klasörünün yapısıyla yakından eşleşir.

Ardından, dosya adlarına _test ekleyerek kimlik authentication_repository.dart ve authentication_repository_test.dart dosyalarını oluşturacağız. Bu, test çalıştırıcısının projede bulunan tüm testleri bulmasına yardımcı olur.

Dosyaları Test Et

Bu bölüme AuthRepository adında bir sınıf oluşturarak başlayacağız. Adından da anlaşılacağı gibi, bu sınıf, uygulamamızdaki tüm kimlik doğrulama işlevlerini yerine getirecektir. Bundan sonra, durum kodunun 200 eşit olup olmadığını kontrol eden ve kimlik doğrulama sırasında oluşan hataları yakalayan bir oturum açma yöntemi ekleyeceğiz.

 sınıf AuthRepository {
  Dio dio = Dio();

  AuthRepository();

  Gelecek<bool> giriş({
    gerekli Dize e-postası,
    gerekli Dize şifresi,
  }) zaman uyumsuz {
    denemek {
      nihai sonuç = bekle dio.post(
        '<https://reqres.in/api/login>',
        veri: {'email': e-posta, 'şifre': şifre},
      );

      if (result.statusCode != 200) {
        yanlış döndür;
      }
    } DioError yakalamada (e) {
      print(e.mesaj);
      yanlış döndür;
    }

    true döndür;
  }

  // ...
}
geçersiz ana() {
  geç AuthRepository authRepository;

  kurmak(() {
    authRepository = AuthRepository();
  });

  test('Kullanıcı başarıyla oturum açtı', () zaman uyumsuz {
    beklemek(
      authRepository.login (e-posta: 'james@mail.com', şifre: '123456'), bekle
      doğru,
    );
  });
}

Yukarıdaki testte, setup işlevinde AuthRepository . Her test ve test grubundan önce doğrudan main içinde çalışacağından, her test veya grup için yeni bir auth deposu başlatır.

Ardından, oturum açma yönteminin hata atmadan true döndürmesini bekleyen bir test yazacağız. Ancak, birim testi varsayılan olarak bir ağ isteğinde bulunmayı desteklemediği için test yine de başarısız olur, bu nedenle Dio ile yapılan oturum açma isteğinin neden 400 durum kodu döndürmesi gerekir.

Durum Kodu 400 Hatası

Bunu düzeltmek için Mockito'yu Dio gibi çalışan bir sahte sınıf oluşturmak için kullanabiliriz. Mockito'da, main yöntemin başına @GenerateMocks([classes]) ek açıklamasını ekleyerek alaylar oluştururuz. Bu, derleme çalıştırıcısına listedeki tüm sınıflar için alay oluşturması konusunda bilgi verir.

 @GenerateMocks([Dio, DiğerSınıf])
geçersiz ana(){
    // oturum açma testi
}

Ardından, terminali açın ve sınıflar için alay oluşturmaya başlamak için flutter pub run build_runner build komutunu çalıştırın. Kod oluşturma işlemi tamamlandıktan sonra, sınıf adının başına Mock ekleyerek oluşturulan mock'lara erişebileceğiz.

 @GenerateMocks([Dio])
geçersiz ana(){
      MockDio mockDio = MockDio()
      geç AuthRepository authRepository;
      ...
}

Oturum açma uç noktasını çağırdığımızda MockDio doğru yanıt verilerini döndürdüğünden emin olmak için verileri saplamamız gerekir. Flutter'da saplama, sahte yöntem çağrıldığında sahte bir nesneyi döndürmek anlamına gelir. Örneğin, test MockDio kullanarak oturum açma uç noktasını çağırdığında, durum kodu 200 olan bir yanıt nesnesi döndürmeliyiz.

Mock yöntemini çağırdığımızda gereken değerleri sağlamak için thenReturn , thenAnswer veya thenThrow ile kullanılabilen while when() işleviyle bir sahte saplama yapılabilir. thenAnswer işlevi, bir gelecek veya akış döndüren yöntemler için kullanılırken, thenReturn , sahte sınıfın normal, eşzamanlı yöntemleri için kullanılır.

 // Herhangi bir yöntemi saplamak için; vadeli işlemler veya akış için kullanıldığında hata veriyor
ne zaman(mock.method()).thenReturn(değer);

// Bir gelecek veya akış döndüren yöntemi saplamak için
ne zaman(mock.method()).thenAnswer(() => futureOrStream);

// Hatayı saplamak için
ne zaman(mock.method()).thenThrow(errorObject);

// Dart oyunu
@GenerateMocks([Dio])
geçersiz ana() {
  MockDio mockDio = MockDio();
  geç AuthRepository authRepository;

  kurmak(() {
    authRepository = AuthRepository();
  });

  test('Kullanıcı başarıyla oturum açtı', () zaman uyumsuz {
    // saplama
    ne zaman(mockDio.post(
      '<https://reqres.in/api/login>',
      veri: {'email': 'james@mail.com', 'şifre': '123456'},
    )).o zaman cevapla(
      (inv) => Future.value(Response(
        durumKodu: 200,
        veri: {'token': 'ASjwweiBE'},
        requestOptions: RequestOptions(yol: '<https://reqres.in/api/login>'),
      )),
    );

    beklemek(
      authRepository.login (e-posta: 'james@mail.com', şifre: '123456'), bekle
      doğru,
    );
  });
}

Saplamamızı oluşturduktan sonra, gerçek dio sınıfı yerine kullanılması için MockDio test dosyasına geçirmemiz gerekiyor. Bunu uygulamak için, gerçek dio sınıfının tanımını veya örneğini authRepository ve onun yapıcısından geçmesine izin vereceğiz. Bu konsepte bağımlılık enjeksiyonu denir.

Bağımlılık enjeksiyonu

Flutter'da bağımlılık enjeksiyonu, bir nesnenin veya sınıfın başka bir nesnenin bağımlılıklarını sağladığı bir tekniktir. Bu model, test ve görünüm modellerinin kullanmak istedikleri dio türünü tanımlayabilmesini sağlar.

 class AuthenticationRepository{
        diyo diyo;

        // Kullanılacak dio türünü belirtmek yerine
        // test veya görünüm modelinin tanımlamasına izin veriyoruz
        AuthenticationRepository(this.dio)
}

@GenerateMocks([Dio])
geçersiz ana() {
  MockDio mockDio = MockDio();
  geç AuthRepository authRepository;

  kurmak(() {
    // şimdi bir argüman olarak Dio'ya geçebiliriz
    authRepository = AuthRepository(mockDio);
  });
}

Argüman eşleştiricileri kullanma

Önceki oturum açma örneğinde, istek yapılırken sam@mail.com e-postası james@mail.com olarak değiştirilirse, test bir no stub found hatası verir. Bunun nedeni, yalnızca james@mail.com için saplamayı oluşturmamızdır.

Ancak çoğu durumda, Mockito tarafından sağlanan argüman eşleştiricileri kullanarak gereksiz mantığı tekrarlamaktan kaçınmak istiyoruz. Argüman eşleştiricilerle, tam tür yerine çok çeşitli değerler için aynı saplamayı kullanabiliriz.

Eşleşen argümanları daha iyi anlamak için PostViewModel test edeceğiz ve PostRepository için taklitler oluşturacağız. Saplama yaptığımızda yanıtlar ve haritalar yerine özel nesneler veya modeller döndüreceğimiz için bu yaklaşımın kullanılması önerilir. Ayrıca çok kolay!

İlk olarak, verileri daha temiz bir şekilde temsil etmek için PostModel oluşturacağız.

 sınıf PostModel {
  PostModel({
    gerekli this.id,
    gerekli this.userId,
    gerekli this.body,
    gerekli this.title,
  });

  son int kimliği;
  final String userId;
  son Dize gövdesi;
  son Dize başlığı;

  // bunun için fromJson ve toJson yöntemlerini uygula
}

Ardından PostViewModel oluşturuyoruz. Bu, PostRepository veri almak veya göndermek için kullanılır. PostViewModel , yalnızca depodan veri gönderip almaktan ve kullanıcı arabirimini yeni verilerle yeniden oluşturması için bilgilendirmekten başka bir şey yapmıyor.

 'package:flutter/material.dart' dosyasını içe aktarın;
'package:mockito_article/models/post_model.dart' içe aktarın;
'package:mockito_article/repositories/post_repository.dart' içe aktarın;

class PostViewModel, ChangeNotifier'ı genişletir {
  PostRepository postRepository;
  bool isLoading = yanlış;

  nihai Harita<int, PostModel> postMap = {};

  PostViewModel(this.postRepository);

  Gelecek<void> sharePost({
    gerekli int kullanıcı kimliği,
    gerekli Dize başlığı,
    gerekli Dize gövdesi,
  }) zaman uyumsuz {
    isLoading = doğru;
    postRepository.sharePost(
      kullanıcı kimliği: kullanıcı kimliği,
      başlık: başlık,
      vücut vücut,
    );

    isLoading = yanlış;
    notifyListeners();
  }

  Gelecek<void> updatePost({
    gerekli int kullanıcı kimliği,
    gerekli int postId,
    gerekli Dize gövdesi,
  }) zaman uyumsuz {
    isLoading = doğru;
    postRepository.updatePost(postId, body);

    isLoading = yanlış;
    notifyListeners();
  }

  Future<void> deletePost(int id) zaman uyumsuz {
    isLoading = doğru;
    postRepository.deletePost(id);

    isLoading = yanlış;
    notifyListeners();
  }

  Gelecek<void> getAllPosts() zaman uyumsuz {
    isLoading = doğru;
    final postList = bekle postRepository.getAllPosts();

    for (var post in postList) {
      postMap[post.id] = gönderi;
    }

    isLoading = yanlış;
    notifyListeners();
  }
}

Daha önce belirtildiği gibi, test ettiğimiz gerçek sınıflarla değil, bağımlılıklarla alay ederiz. Bu örnekte, PostViewModel için birim testleri yazıyoruz ve PostViewModel ile alay PostRepository . Bu, muhtemelen bir hata oluşturabilecek PostRepository yerine oluşturulan MockPostRepository sınıfındaki yöntemleri çağıracağımız anlamına gelir.

Mockito, eşleşen argümanları çok kolaylaştırır. Örneğin, PostViewModel içindeki updatePost yöntemine bir göz atın. Yalnızca iki konumsal bağımsız değişkeni kabul eden depo updatePost yöntemini çağırır. Bu sınıf yöntemini saplamak için, tam postId ve body sağlamayı seçebiliriz veya işleri basit tutmak için Mockito tarafından sağlanan any değişkeni kullanabiliriz.

 @GenerateMocks([PostRepository])
geçersiz ana() {
  MockPostRepository mockPostRepository = MockPostRepository();
  geç PostViewModel postViewModel;

  kurmak(() {
    postViewModel = PostViewModel(mockPostRepository);
  });

  test('Yazı başarıyla güncellendi', () {
    // argüman eşleştiriciler ve 'any' ile saplama
    ne zaman(
      mockPostRepository.updatePost(herhangi biri, argThat(içerir('sap'))),
    ).o zaman cevapla(
      (inv) => Future.value(),
    );

    // Bu yöntem, mockPostRepository güncelleme yöntemini çağırır
    postViewModel.updatePost(
      kullanıcı kimliği: 1,
      posta kimliği: 3,
      gövde: 'saplamayı almak için 'sap'ı dahil et',
    );

    // sahte deponun çağrıldığını doğrulayın
    doğrulama(mockPostRepository.updatePost(3, 'saplamayı almak için 'sap'ı dahil et'));
  });
}

Yukarıdaki saplama, hem any değişkeni hem de argThat(matcher) işlevini içerir. Dart'ta, test beklentilerini belirtmek için eşleştiriciler kullanılır. Farklı test durumları için uygun farklı türde eşleştiricilerimiz var. Örneğin, nesne ilgili değeri içeriyorsa, eşleştirici contains(value) true değerini döndürür.

Konumsal ve adlandırılmış bağımsız değişkenleri eşleştirme

Dart'ta hem konumsal argümanlarımız hem de adlandırılmış argümanlarımız var . Yukarıdaki örnekte, updatePost yöntemi için sahte ve saplama, konumsal bir argümanla ilgilenir ve any değişkeni kullanır.

Ancak, Dart bir öğenin adlandırılmış bağımsız değişken olarak kullanılıp kullanılmadığını bilmek için bir mekanizma sağlamadığından, adlandırılmış bağımsız değişkenler any değişkeni desteklemez. Bunun yerine, adlandırılmış bağımsız değişkenlerle uğraşırken anyNamed('name') işlevini kullanırız.

 ne zaman(
  mockPostRepository.sharePost(
    body: argThat(startsWith('stub'), adlı: 'body'),
    postId: anyNamed('postId'),
    başlık: anyNamed('başlık'),
    kullanıcı kimliği: 3,
  ),
).o zaman cevapla(
  (inv) => Future.value(),
);

Eşleştiricileri adlandırılmış bir argümanla kullanırken, bir hatayı önlemek için argümanın adını sağlamalıyız. Tüm olası seçenekleri görmek için Dart belgelerinde eşleştiriciler hakkında daha fazla bilgi edinebilirsiniz.

Mockito'da sahte oluşturma

Alaylar ve sahteler genellikle karıştırılır, bu yüzden ikisi arasındaki farkı hızlıca açıklığa kavuşturalım.

Sahteler, bağımsız değişken eşleştiriciler kullanılarak saplamaya izin veren sınıflardır. Ancak sahteler, daha fazla esneklik sağlamak için gerçek sınıfın mevcut yöntemlerini geçersiz kılan sınıflardır ve bunların tümü argüman eşleştiriciler kullanılmaz.

Örneğin, posta deposunda sahte yerine sahte kullanmak, sahte depo işlevini gerçek olana benzer hale getirmemize izin verir. Bu mümkündür, çünkü sağlanan değerlere dayalı sonuçları döndürebiliriz. Daha basit bir ifadeyle, testte sharePost çağırdığımızda, gönderiyi kaydetmeyi seçebilir ve daha sonra gönderinin getAllPosts kullanılarak kaydedildiğini onaylayabiliriz.

 class FakePostRepository, Fake'in PostRepository uygulamasını genişletir {
  Harita<int, PostModel> fakePostStore = {};

  @geçersiz kıl
  Gelecek<PostModel> sharePost({
    int? posta kimliği,
    gerekli int kullanıcı kimliği,
    gerekli Dize başlığı,
    gerekli Dize gövdesi,
  }) zaman uyumsuz {
    son gönderi = PostModel(
      kimlik: postId ?? 0,
      kullanıcı kimliği: kullanıcı kimliği,
      vücut vücut,
      başlık: başlık,
    );
    fakePostStore[postId ?? 0] = gönderi;
    gönderiyi iade et;
  }

  @geçersiz kıl
  Future<void> updatePost(int postId, Dize gövdesi) zaman uyumsuz {
    fakePostStore[postId] = fakePostStore[postId]!.copyWith(body: body);
  }

  @geçersiz kıl
  Future<List<PostModel>> getAllPosts() zaman uyumsuz {
    fakePostStore.values.toList() döndür;
  }

  @geçersiz kıl
  Future<bool> deletePost(int id) zaman uyumsuz {
    fakePostStore.remove(id);

    true döndür;
  }
}

fake kullanılarak güncellenen test aşağıda gösterilmiştir. fake ile tüm yöntemleri aynı anda test edebiliriz. Bir gönderi, eklendiğinde veya paylaşıldığında havuzdaki haritaya gönderilir.

 @GenerateMocks([PostRepository])
geçersiz ana() {
  FakePostRepository fakePostRepository = FakePostRepository();
  geç PostViewModel postViewModel;

  kurmak(() {
    postViewModel = PostViewModel(fakePostRepository);
  });

  test('Yazı başarıyla güncellendi', () zaman uyumsuz {
    bekle(postViewModel.postMap.isEmpty, true);
    const postId = 123;

    postViewModel.sharePost(
      postId: postId,
      kullanıcı kimliği: 1,
      başlık: 'İlk Mesaj',
      gövde: 'İlk gönderim',
    );
    postViewModel.getAllPosts()'u bekleyin;
    bekle(postViewModel.postMap[postId]?.body, 'İlk mesajım');

    postViewModel.updatePost(
      postId: postId,
      kullanıcı kimliği: 1,
      gövde: 'Güncellenmiş gönderim',
    );
    postViewModel.getAllPosts()'u bekleyin;
    bekle(postViewModel.postMap[postId]?.body, 'Güncellenmiş gönderim');
  });
}

Gönderi Başarıyla Güncellendi Ekranı

Flutter'da akışlarla alay etme ve test etme

Mockito ile alay ve saplama akışları geleceğe çok benzer çünkü saplama için aynı sözdizimini kullanıyoruz. Bununla birlikte, akışlar, yayımlandıkça değerleri sürekli olarak dinlemek için bir mekanizma sağladıkları için geleceklerden oldukça farklıdır.

Akış döndüren bir yöntemi test etmek için, yöntemin çağrıldığını test edebilir veya değerlerin doğru sırada yayınlanıp yayınlanmadığını kontrol edebiliriz.

 class PostViewModel, ChangeNotifier'ı genişletir {
  ...
  PostRepository postRepository;
  son likesStreamController = StreamController<int>();

  PostViewModel(this.postRepository);

  ...
  void listenForLikes(int postId) {
    postRepository.listenForLikes(postId).listen((beğeniler) {
      likesStreamController.add(beğeniler);
    });
  }
}


@GenerateMocks([PostRepository])
geçersiz ana() {
  MockPostRepository mockPostRepository = MockPostRepository();
  geç PostViewModel postViewModel;

  kurmak(() {
    postViewModel = PostViewModel(mockPostRepository);
  });

  test('Beğenileri dinle', () {
    son mocklikesStreamController = StreamController<int>();

    ne zaman(mockPostRepository.listenForLikes(herhangi bir))
        .thenAnswer((inv) => mocklikesStreamController.stream);

    postViewModel.listenForLikes(1);

    mocklikesStreamController.add(3);
    mocklikesStreamController.add(5);
    mocklikesStreamController.add(9);

    // beğenileri dinlemenin çağrılıp çağrılmadığını kontrol eder
    doğrula(mockPostRepository.listenForLikes(1));
    bekle(postViewModel.likesStreamController.stream, emitsInOrder([3, 5, 9]));
  });
}

Yukarıdaki örnekte, PostRepository yöntemini çağıran ve dinleyebileceğimiz bir akış döndüren bir listenforLikes yöntemi ekledik. Ardından, akışı dinleyen ve yöntemin doğru sırada çağrıldığını ve yayınlanıp yayınlanmadığını kontrol eden bir test oluşturduk.

Bazı karmaşık durumlarda, yalnızca expect işlevini kullanmak yerine expectLater veya expectAsync1 kullanabiliriz.

Çözüm

Bu mantığın çoğu ne kadar basit görünse de, bu işlevleri tekrar tekrar QA yapmamamız için testler yazmak çok önemlidir. Test yazmanın amaçlarından biri, uygulamalarınız büyüdükçe tekrarlayan QA'yı azaltmaktır.

Bu yazıda, birim testleri yazarken Mockito'yu sahte oluşturmak için nasıl etkili bir şekilde kullanabileceğimizi öğrendik. İşlevsel testler yazmak için sahte ve argüman eşleştiricilerin nasıl kullanılacağını da öğrendik.

Umarım, alay etmeyi kolaylaştırmak için uygulamanızı nasıl yapılandıracağınızı daha iyi anlamışsınızdır. Okuduğunuz için teşekkürler!

Mockito ile birim testi Flutter kodu ilk olarak LogRocket Blog'da göründü.

etiketlerETİKETLER
Üzgünüm, bu içerik için hiç etiket bulunmuyor.

Sıradaki içerik:

Mockito ile birim testi Flutter kodu