Menulis Autotests Secara Efektif - Tes subkutan

Mari kita bayangkan sosisitasi hipotetis (ke mana kita secara teratur terjun). Anda telah ditugaskan ke proyek untuk otomatisasi "gash". Anda diberi rencana pengujian besar dengan jumlah besar (ribuan!) Dari tes "manual", dan mereka mengatakan bahwa Anda perlu melakukan sesuatu, dan di sana. Dan juga, dengan cepat dan stabil.

Sudah terlambat untuk menulis tes Unit, atau bahkan memikirkan TDD , kode produk telah lama ditulis. Kata-kata Anda, autotester kawan!

gambar

Untungnya, ada trik kecil yang akan memungkinkan Anda untuk meningkatkan cakupan dan membuat tes stabil dan cepat - Tes subkutan ("tes subkutan"), tetapi hal pertama yang pertama.


Inti dari masalah


Refleks bersyarat pertama dari automator adalah untuk mengambil Selenium (baik, baik di sana, Selenide, atau keahlian lain untuk tes UI). Ini adalah standar industri, tetapi ada banyak alasan mengapa itu tidak lepas landas:

  • Tes UI lambat. Tidak ada jalan keluar dari ini. Mereka dapat dijalankan secara paralel, diajukan dan dilakukan sedikit lebih cepat, tetapi mereka akan tetap lambat.
  • Tes UI tidak stabil. Sebagian karena mereka lambat. Dan juga karena browser Web dan antarmuka pengguna tidak diciptakan untuk dikendalikan oleh komputer (tren ini sedang berubah, tetapi bukan fakta bahwa ini bagus).
  • UI- β€” . . ( , , , «» - , ).
  • , , , UI- . . ID XPath . , «» - - . , , β€” - , .
  • Seseorang akan mengatakan bahwa beberapa fungsi tidak dapat diuji jika tidak. Saya akan mengatakan bahwa jika ada fungsionalitas yang dapat diuji hanya dengan tes UI (dengan pengecualian dari logika UI itu sendiri), ini bisa menjadi pertanda baik masalah arsitektur dalam produk.

Satu-satunya nilai plus nyata dari tes UI adalah bahwa tes tersebut memungkinkan Anda untuk "melempar" pemeriksaan yang kurang lebih berguna tanpa perlu menyelam dan mempelajari kode produk itu sendiri. Yang bukan merupakan nilai tambah dalam jangka panjang. Penjelasan lebih rinci tentang mengapa ini dapat didengar dalam presentasi ini .

Solusi alternatif


Sebagai kasus yang sangat sederhana, mari pertimbangkan aplikasi yang terdiri dari formulir di mana Anda dapat memasukkan nama pengguna yang valid. Jika Anda memasukkan nama pengguna yang cocok dengan aturan - Pengguna akan dibuat dalam sistem dan dicatat dalam Database.



Kode sumber aplikasi dapat ditemukan di sini: github.com/senpay/login-form . Anda telah diperingatkan - dalam aplikasi ada banyak bug dan tidak ada alat dan kerangka kerja yang modis.

Jika Anda mencoba "melempar" lembar cek untuk aplikasi ini, Anda bisa mendapatkan sesuatu seperti:
JumlahLangkahHasil yang diharapkan
11. Masukkan nama pengguna yang valid
2. Klik tombol "Masuk"
1.
2. Pengguna baru dibuat.
21. Masukkan nama pengguna kosong
2. Klik tombol "Masuk"
1.
2. Pesan kesalahan diberikan.

Apakah ini terlihat sederhana? Secara sederhana! Bisakah saya menulis tes UI? Bisa. Contoh tes tertulis (bersama dengan kerangka tiga tingkat penuh ) dapat ditemukan di LoginFormTest.java jika Anda pergi ke label uitests di git ( git checkout uitests ):

public class LoginFormTest {

    SelenideMainPage sut = SelenideMainPage.INSTANCE;
    private static final String APPLICATION_URL = "http://localhost:4567/index";

    @BeforeClass
    public static void setUpClass() {
        final String[] args = {};
        Main.main(args);
        Configuration.browser = "firefox";
    }

    @Before
    public void setUp() {
        open(APPLICATION_URL);
    }

    @After
    public void tearDown() {
        close();
    }

    @Test
    public void shouldBeAbleToAddNewUser() {
        sut.setUserName("MyCoolNewUser");
        sut.clickSubmit();
        Assert.assertEquals("Status: user MyCoolNewUser was created", sut.getStatus());
        Assert.assertTrue(sut.getUsers().contains("Name: MyCoolNewUser"));
    }

    @Test
    public void shouldNotBeAbleToAddEmptyUseName() {
        final int numberOfUsersBeforeTheTest = sut.getUsers().size();
        sut.clickSubmit();
        Assert.assertEquals("Status: Login cannot be empty", sut.getStatus());
        Assert.assertEquals(numberOfUsersBeforeTheTest, sut.getUsers().size());
    }
}


Beberapa metrik untuk kode ini:
Runtime: ~ 12 detik (12 detik 956 milidetik terakhir kali saya menjalankan tes ini)
Kode cakupan
Kelas: 100%
Metode: 93,8% (30/32)
Baris: 97,4% (75/77 )

Sekarang mari kita asumsikan bahwa AutoTests Fungsional dapat ditulis pada tingkat "segera di bawah" UI. Teknik ini disebut tes Subkutan (β€œtes subkutan” - tes yang menguji segera di bawah tingkat logika tampilan) dan telah diusulkan oleh Martin Fowler beberapa waktu yang lalu [ 1 ].

Ketika orang berpikir tentang autotest "non-UI", seringkali mereka langsung berpikir tentang REST / SOAP atau API-nya. Tetapi API (Application Programming Interface) adalah konsep yang jauh lebih luas, tidak serta merta mempengaruhi HTTP dan protokol kelas berat lainnya.

Jika kita memilih kode produk , kita dapat menemukan sesuatu yang menarik:
public class UserApplication {

    private static IUserRepository repository = new InMemoryUserRepository();
    private static UserService service = new UserService(); {
        service.setUserRepository(repository);
    }

    public Map<String, Object> getUsersList() {
        return getUsersList("N/A");
    }

    public Map<String, Object> addUser(final String username) {
        final String status = service.addUser(username);
        final Map<String, Object> model = getUsersList(status);
        return model;
    }

    private Map<String, Object> getUsersList(String status) {
        final Map<String, Object> model = new HashMap<>();
        model.put("status", status);
        model.put("users", service.getUserInfoList());
        return model;
    }
}


Ketika kita mengklik pada UI, salah satu metode ini disebut, atau objek Pengguna baru ditambahkan, atau daftar objek Pengguna yang sudah dibuat dikembalikan. Bagaimana jika kita menggunakan metode ini secara langsung ? Bagaimanapun, ini adalah API nyata! Dan yang paling penting, REST dan API lain juga bekerja dengan prinsip yang sama - mereka menyebut metode tertentu "level pengontrol".

Dengan menggunakan metode ini secara langsung, kita dapat menulis tes yang lebih sederhana dan lebih baik:
public class UserApplicationTest {

    private UserApplication sut;

    @Before
    public void setUp() {
       sut = new UserApplication();
    }

    @Test
    public void shouldBeAbleToAddNewUser() {
        final Map<String, Object> myCoolNewUser = sut.addUser("MyCoolNewUser");
        Assert.assertEquals("user MyCoolNewUser was created", myCoolNewUser.get("status"));
        Assert.assertTrue(((List) myCoolNewUser.get("users")).contains("Name: MyCoolNewUser"));
    }

    @Test
    public void shouldNotBeAbleToAddEmptyUseName() {
        final Map<String, Object> usersBeforeTest = sut.getUsersList();
        final int numberOfUsersBeforeTheTest = ((List) usersBeforeTest.get("users")).size();
        final Map<String, Object> myCoolNewUser = sut.addUser("");
        Assert.assertEquals("Login cannot be empty", myCoolNewUser.get("status"));
        Assert.assertEquals(numberOfUsersBeforeTheTest, ((List) myCoolNewUser.get("users")).size());
    }
}


Kode ini tersedia di bawah label subktest :

git checkout subctests


Mari mencoba mengumpulkan metrik?
Waktu untuk mengeksekusi: ~ 21 milidetik
Cakupan kode :
Kelas: 77,8%
Metode: 78,1 (30/32)
Baris: 78,7 (75/77)

Kami kehilangan sedikit cakupan, tetapi kecepatan tes meningkat 600 kali !!!

Seberapa penting / signifikan hilangnya liputan dalam kasus ini? Tergantung situasinya. Kami kehilangan sedikit kode lem, yang mungkin (atau mungkin tidak) penting (saya sarankan menentukan kode mana yang hilang sebagai latihan).

Apakah kehilangan cakupan ini membenarkan pengenalan pengujian kelas berat di tingkat UI? Itu juga tergantung situasi. Kita dapat, misalnya:
  • Tambahkan satu tes UI untuk memeriksa kode lem, atau
  • Jika kami tidak mengharapkan perubahan yang sering pada kode lem - biarkan tanpa autotest, atau
  • Jika kita memiliki semacam pengujian "manual", ada kemungkinan besar bahwa masalah dengan kode lem akan diperhatikan oleh tester, atau
  • Datang dengan sesuatu yang lain (penyebaran Canary yang sama)


Akhirnya


  • Autotests fungsional tidak perlu ditulis pada tingkat UI atau REST / SOAP API. Penggunaan "tes subkutan" dalam banyak situasi akan menguji fungsi yang sama dengan kecepatan dan stabilitas yang lebih besar.
  • Salah satu kelemahan dari pendekatan ini adalah hilangnya cakupan tertentu.
  • Salah satu cara untuk menghindari kehilangan cakupan adalah " Model Tes Fitur "
  • Tetapi bahkan dengan hilangnya cakupan, peningkatan kecepatan dan stabilitas adalah signifikan.


Versi bahasa Inggris artikel tersedia di sini .

All Articles