Pengujian statis atau simpan Private Ryan

Sebuah rilis sering merayap tanpa diketahui. Dan kesalahan apa pun yang tiba-tiba ditemukan di hadapannya mengancam kita dengan perubahan tenggat waktu, perbaikan terbaru, pekerjaan hingga pagi hari dan menghabiskan waktu dengan gelisah. Ketika terburu-buru seperti itu mulai terjadi secara sistematis, kami menyadari bahwa Anda tidak bisa hidup seperti itu lagi. Diputuskan untuk mengembangkan sistem validasi komprehensif untuk menyelamatkan pengembang Ryan Artyom biasa, yang pulang sebelum dirilis pada jam 9 malam, atau jam 10, atau jam 11 ... yah, Anda mengerti. Idenya adalah untuk pengembang untuk mencari tahu tentang kesalahan, sementara perubahan belum mencapai repositori, dan dia sendiri belum kehilangan konteks tugas.


Hari ini, perubahan yang dilakukan diperiksa dengan hati-hati terlebih dahulu secara lokal, dan kemudian dengan serangkaian tes integrasi di peternakan perakitan. Pada artikel ini, kita akan berbicara tentang tahap pertama verifikasi - pengujian statis, yang memantau kebenaran sumber daya dan menganalisis kode. Ini adalah subsistem pertama dalam rantai dan merupakan sebagian besar kesalahan yang ditemukan.

Bagaimana semuanya dimulai


Proses manual mengecek permainan sebelum rilis dimulai di QA satu setengah minggu sebelum rilis. Secara alami, bug yang ada pada tahap ini perlu diperbaiki sesegera mungkin.

Karena kurangnya waktu untuk solusi yang baik, "penopang" sementara ditambahkan, yang kemudian berakar untuk waktu yang lama dan dikelilingi oleh solusi lain yang tidak terlalu populer.

Pertama-tama, kami memutuskan untuk mengotomatiskan penemuan kesalahan terbuka: crash, ketidakmampuan untuk menyelesaikan serangkaian tindakan utama untuk game (buka toko, lakukan pembelian, mainkan level). Untuk melakukan ini, permainan dimulai dalam mode autogame khusus dan jika terjadi kesalahan, maka kita akan segera mengetahuinya setelah lulus tes di peternakan kami.

Tetapi sebagian besar kesalahan yang ditemukan oleh penguji dan uji asap otomatis kami adalah kurangnya sumber daya atau pengaturan yang salah dari sistem yang berbeda. Oleh karena itu, langkah selanjutnya adalah pengujian statis - memeriksa ketersediaan sumber daya, hubungan dan pengaturannya tanpa meluncurkan aplikasi. Sistem ini diluncurkan oleh langkah tambahan di farm perakitan dan sangat menyederhanakan penemuan dan perbaikan kesalahan. Tetapi mengapa membuang sumber daya farm perakitan jika Anda dapat mendeteksi kesalahan bahkan sebelum melakukan dan memasukkan kode masalah ke dalam repositori? Ini bisa dilakukan dengan kait precommit , yang baru saja dimulai sebelum komit dibuat dan dikirim ke repositori.

Dan ya, kami sangat keren sehingga pengujian statis sebelum melakukan dan di farm perakitan dilakukan oleh satu kode, yang sangat menyederhanakan dukungannya.

Upaya kami dapat dibagi menjadi tiga bidang:

  • pembuatan sebuah peternakan perakitan - tempat di mana segala sesuatu yang telah dikumpulkan dan diperiksa akan dikumpulkan;
  • pengembangan tes statis - memeriksa kebenaran sumber daya, hubungannya, meluncurkan analisis kode;
  • pengembangan uji runtime - meluncurkan aplikasi dalam mode putar otomatis.

Tugas terpisah adalah mengatur peluncuran pengujian pada mesin oleh pengembang. Itu perlu untuk meminimalkan waktu eksekusi secara lokal (pengembang tidak harus menunggu 10 menit untuk melakukan satu baris) dan memastikan bahwa setiap sistem yang membuat perubahan telah menginstal sistem kami.

Banyak persyaratan - satu sistem


Selama pengembangan, ada serangkaian perangkat yang dapat berguna: dengan dan tanpa curang, beta atau alfa, iOS atau Android. Dalam setiap kasus, sumber daya yang berbeda, pengaturan, atau bahkan kode yang berbeda mungkin diperlukan. Menulis skrip untuk pengujian statis untuk setiap kemungkinan perakitan menghasilkan sistem yang rumit dengan banyak parameter. Selain sulit untuk mempertahankan dan memodifikasi, setiap proyek juga memiliki set sepeda kruk sendiri.

Melalui trial and error, kami sampai pada satu sistem, setiap pengujian di mana dapat memperhitungkan konteks peluncuran dan memutuskan apakah akan menjalankannya, apa tepatnya dan bagaimana memeriksa. Pada awal pengujian, kami mengidentifikasi tiga sifat utama:

  • jenis perakitan: untuk sumber rilis dan debugging, pemeriksaan akan berbeda dalam hal ketelitian, kelengkapan cakupan, serta pengaturan untuk pengidentifikasi dan verifikasi fungsionalitas yang tersedia;
  • platform: apa yang valid untuk android mungkin salah untuk iOS, sumber daya juga dikumpulkan secara berbeda dan tidak semua sumber daya dalam versi android akan ada di iOS dan sebaliknya;
  • lokasi peluncuran: di mana tepatnya kami meluncurkan - pada agen pembangunan di mana semua tes yang tersedia diperlukan atau di komputer pengguna di mana daftar startup perlu diminimalkan.


Sistem uji statis


Inti dari sistem dan set dasar tes statis diimplementasikan dalam python. Dasarnya hanya beberapa entitas:


Konteks pengujian adalah konsep yang luas. Ia menyimpan parameter build dan launch yang kita bicarakan di atas, serta meta-informasi yang diisi dan digunakan oleh tes.

Pertama, Anda perlu memahami tes mana yang harus dijalankan. Untuk ini, meta-informasi berisi jenis sumber daya yang kami tertarik secara khusus dalam peluncuran ini. Jenis sumber daya ditentukan oleh tes yang terdaftar dalam sistem. Tes dapat "dikaitkan" dengan satu atau beberapa tipe tunggal, dan jika, pada saat komit, ternyata file yang diperiksa oleh tes ini telah berubah, maka sumber daya yang terkait telah berubah. Ini sesuai dengan ideologi kami - untuk menjalankan pemeriksaan lokal sesedikit mungkin: jika file yang bertanggung jawab atas tes tidak berubah, maka Anda tidak perlu menjalankannya.

Sebagai contoh, ada deskripsi ikan, di mana model 3D dan tekstur ditunjukkan. Jika file deskripsi telah berubah, maka diperiksa bahwa model dan tekstur yang ditunjukkan di dalamnya ada. Dalam kasus lain, tidak perlu menjalankan cek ikan.

Di sisi lain, mengubah sumber daya mungkin memerlukan perubahan dan entitas tergantung padanya: jika set tekstur yang kami simpan dalam file xml telah berubah, maka perlu untuk memeriksa model 3D tambahan, karena mungkin ternyata tekstur yang Anda butuhkan dihapus. Optimalisasi yang dijelaskan di atas hanya diterapkan secara lokal pada mesin pengguna pada saat komit, dan ketika diluncurkan di majelis perakitan, diasumsikan bahwa semua file telah berubah dan kami menjalankan semua tes.

Masalah selanjutnya adalah ketergantungan dari beberapa tes pada yang lain: tidak mungkin untuk memeriksa ikan sebelum menemukan semua tekstur dan model. Oleh karena itu, kami membagi semua eksekusi menjadi dua tahap:

  • persiapan konteks
  • memeriksa

Pada tahap pertama, konteksnya diisi dengan informasi tentang sumber daya yang ditemukan (dalam kasus ikan, dengan pengidentifikasi pola dan tekstur). Pada tahap kedua, menggunakan informasi yang disimpan, cukup periksa apakah sumber daya yang diinginkan ada. Konteks yang disederhanakan disajikan di bawah ini.

class VerificationContext(object):   def __init__(self, app_path, build_type, platform, changed_files=None):       self.__app_path = app_path       self.__build_type = build_type       self.__platform = platform       #          self.__modified_resources = set()       self.__expected_resources = set()       #      ,              self.__changed_files = changed_files       # -  ,          self.__resources = {} def expect_resources(self, resources):   self.__expected_resources.update(resources) def is_resource_expected(self, resource):   return resource in self.__expected_resources def register_resource(self, resource_type, resource_id, resource_data=None):   self.__resources.setdefault(resource_type, {})[resource_id] = resource_data def get_resource(self, resource_type, resource_id):   if resource_type not in self.__resources or resource_id not in self.__resources[resource_type]:       return None, None   return resource_id, self.__resources[resource_type][resource_id] 

Setelah menentukan semua parameter yang mempengaruhi peluncuran tes, kami berhasil menyembunyikan semua logika di dalam kelas dasar. Dalam tes tertentu, tetap hanya menulis tes itu sendiri dan nilai yang diperlukan untuk parameter.

 class TestCase(object):  def __init__(self, name, context, build_types=None, platforms=None, predicate=None,               expected_resources=None, modified_resources=None):      self.__name = name      self.__context = context      self.__build_types = build_types      self.__platforms = platforms      self.__predicate = predicate      self.__expected_resources = expected_resources      self.__modified_resources = modified_resources      #               #   ,          self.__need_run = self.__check_run()      self.__need_resource_run = False  @property  def context(self):      return self.__context  def fail(self, message):      print('Fail: {}'.format(message))  def __check_run(self):      build_success = self.__build_types is None or self.__context.build_type in self.__build_types      platform_success = self.__platforms is None or self.__context.platform in self.__platforms      hook_success = build_success      if build_success and self.__context.is_build('hook') and self.__predicate:          hook_success = any(self.__predicate(changed_file) for changed_file in self.__context.changed_files)      return build_success and platform_success and hook_success  def __set_context_resources(self):      if not self.__need_run:          return      if self.__modified_resources:          self.__context.modify_resources(self.__modified_resources)      if self.__expected_resources:          self.__context.expect_resources(self.__expected_resources)   def init(self):      """        ,                    ,          """      self.__need_resource_run = self.__modified_resources and any(self.__context.is_resource_expected(resource) for resource in self.__modified_resources)  def _prepare_impl(self):      pass  def prepare(self):      if not self.__need_run and not self.__need_resource_run:          return      self._prepare_impl()  def _run_impl(self):      pass  def run(self):      if self.__need_run:          self._run_impl() 

Kembali ke contoh ikan, kita perlu dua tes, salah satunya menemukan tekstur dan mendaftarkannya dalam konteks, yang lain mencari tekstur untuk model yang ditemukan.

 class VerifyTexture(TestCase):  def __init__(self, context):      super(VerifyTexture, self).__init__('VerifyTexture', context,                                          build_types=['production', 'hook'],                                          platforms=['windows', 'ios'],                                          expected_resources=None,                                          modified_resources=['Texture'],                                          predicate=lambda file_path: os.path.splitext(file_path)[1] == '.png')  def _prepare_impl(self):      texture_dir = os.path.join(self.context.app_path, 'resources', 'textures')      for root, dirs, files in os.walk(texture_dir):          for tex_file in files:              self.context.register_resource('Texture', tex_file) class VerifyModels(TestCase):  def __init__(self, context):      super(VerifyModels, self).__init__('VerifyModels', context,                                         expected_resources=['Texture'],                                         predicate=lambda file_path: os.path.splitext(file_path)[1] == '.obj')  def _run_impl(self):      models_descriptions = etree.parse(os.path.join(self.context.app_path, 'resources', 'models.xml'))      for model_xml in models_descriptions.findall('.//Model'):          texture_id = model_xml.get('texture')          texture = self.context.get_resource('Texture', texture_id)          if texture is None:              self.fail('Texture for model {} was not found: {}'.format(model_xml.get('id'), texture_id)) 

Penyebaran Proyek


Pengembangan game di Playrix dilakukan dengan mesinnya sendiri dan, karenanya, semua proyek memiliki struktur file dan kode yang sama menggunakan aturan yang sama. Oleh karena itu, ada banyak tes umum yang ditulis satu kali dan ada dalam kode umum. Cukup bagi proyek untuk memperbarui versi sistem pengujian dan menghubungkan pengujian baru ke diri mereka sendiri.

Untuk menyederhanakan integrasi, kami menulis pelari yang menerima file konfigurasi dan tes desain (tentangnya nanti). File konfigurasi berisi informasi dasar yang kami tulis di atas: jenis perakitan, platform, jalur ke proyek.

 class Runner(object):  def __init__(self, config_str, setup_function):      self.__tests = []      config_parser = RawConfigParser()      config_parser.read_string(config_str)      app_path = config_parser.get('main', 'app_path')      build_type = config_parser.get('main', 'build_type')      platform = config_parser.get('main', 'platform')      '''      get_changed_files         CVS      '''      changed_files = None if build_type != 'hook' else get_changed_files()      self.__context = VerificationContext(app_path, build_type, platform, changed_files)      setup_function(self)  @property  def context(self):      return self.__context  def add_test(self, test):      self.__tests.append(test)  def run(self):      for test in self.__tests:          test.init()      for test in self.__tests:          test.prepare()      for test in self.__tests:          test.run() 

Keindahan dari file konfigurasi adalah dapat dihasilkan di farm assembly untuk rakitan berbeda dalam mode otomatis. Tetapi melewati pengaturan untuk semua tes melalui file ini mungkin tidak nyaman. Untuk melakukan ini, ada tuning xml khusus yang disimpan dalam repositori proyek dan daftar file yang diabaikan, topeng untuk mencari dalam kode, dan seterusnya tertulis di dalamnya.

Contoh file konfigurasi
 [main] app_path = {app_path} build_type = production platform = ios 

Tuning contoh xml
 <root> <VerifySourceCodepage allow_utf8="true" allow_utf8Bom="false" autofix_path="ci/autofix"> <IgnoreFiles>*android/tmp/*</IgnoreFiles> </VerifySourceCodepage> <VerifyCodeStructures> <Checker name="NsStringConversion" /> <Checker name="LogConstructions" /> </VerifyCodeStructures> </root> 

Selain bagian umum, proyek memiliki kekhasan dan perbedaan mereka sendiri, oleh karena itu, ada set tes proyek yang terhubung ke sistem melalui konfigurasi pelari. Untuk kode dalam contoh, beberapa baris akan cukup untuk dijalankan:

 def setup(runner):  runner.add_test(VerifyTexture(runner.context))  runner.add_test(VerifyModels(runner.context)) def run():  raw_config = '''  [main]  app_path = {app_path}  build_type = production  platform = ios  '''  runner = Runner(raw_config, setup)  runner.run() 

Penggaruk yang dikumpulkan


Meskipun python itu sendiri adalah cross-platform, kami secara teratur memiliki masalah dengan fakta bahwa pengguna memiliki lingkungan unik mereka sendiri, yang mungkin tidak memiliki versi yang kami harapkan, beberapa versi, atau tidak ada juru bahasa sama sekali. Akibatnya, itu tidak berfungsi seperti yang kita harapkan atau tidak berfungsi sama sekali. Ada beberapa iterasi untuk menyelesaikan masalah ini:

  1. Python dan semua paket diinstal oleh pengguna. Tapi ada dua "tetapi": tidak semua pengguna adalah pemrogram dan menginstal melalui instalasi pip untuk desainer, dan untuk programmer juga, bisa menjadi masalah.
  2. Ada skrip yang menginstal semua paket yang diperlukan. Ini sudah lebih baik, tetapi jika pengguna memiliki python yang salah terinstal, maka konflik dapat terjadi dalam pekerjaan.
  3. Memberikan versi penerjemah dan dependensi yang benar dari penyimpanan artefak (Nexus) dan menjalankan tes di lingkungan virtual.

Masalah lainnya adalah kinerja. Semakin banyak tes, semakin lama perubahan diperiksa pada komputer pengguna. Setiap beberapa bulan ada profil dan optimalisasi kemacetan. Jadi konteksnya ditingkatkan, cache untuk file teks muncul, mekanisme predikat ditingkatkan (menentukan bahwa file ini menarik untuk pengujian).

Dan kemudian hanya menyelesaikan masalah tentang bagaimana menerapkan sistem pada semua proyek dan memaksa semua pengembang untuk memasukkan kait yang dimiliki sebelumnya, tetapi ini adalah cerita yang sama sekali berbeda ...

Kesimpulan


Selama proses pengembangan, kami menari di menyapu, berjuang keras, tetapi masih punya sistem yang memungkinkan kami untuk menemukan kesalahan selama komit, mengurangi pekerjaan penguji, dan tugas sebelum rilis tentang tekstur yang hilang adalah hal di masa lalu. Untuk kebahagiaan total, pengaturan sederhana lingkungan dan optimalisasi tes individu tidak cukup, tetapi golem dari departemen ci bekerja keras dalam hal ini.

Contoh lengkap kode yang digunakan sebagai contoh dalam artikel dapat ditemukan di repositori kami .

Source: https://habr.com/ru/post/id452926/


All Articles