26 Ekim 2018

TEST KODLARINDA ANTI-PATTERN’LER (TEST CODE SMELLS) – 2 – SMELL TİPLERİ

Bir önceki yazımızda Test Smell konusuna bir giriş yapmıştık. Bu yazıda ise test smell türlerinden bahsedeceğiz.

Bir önceki yazımızda Test Smell konusuna bir giriş yapmıştık. Bu yazıda ise test smell türlerinden bahsedeceğiz.

Akademide ve endüstride test smell konusu ile ilgili yapılan çalışmalar ve tartışmalarda 200 civarında farklı isimde smellden bahsedilmiş. Aşağıda bunların temsili bir kataloğu verilmiştir (139 tanesi). Belli ana başlıklar altında kategorize edilmiş bu kataloğu inceleyebilirsiniz.

Testin Çalışması ve Davranışı

Test Amacı ve Mantığı

Kararsız Çalışması

Güvenilirlik

Performans

Diğer

Bir çok şeyi test etme

Bir çok uniteyi test etme

Diğer

Flaky test

Fragile test

Slow test

Manual Intervention

Eager test (The Test It All, Split Personality)

Test envy

Conditional test logic (Guarded Test)

Unrepeatable test

Brittle test

Long Running Test

The Loudmouth (Transcripting Test)

Assertion roulette

Indirect esting

Nested Conditional

Indeterministic test

The Sleeper

The Slow Poke

Frequently opening and closing browsers in Selenium tests

Semantic test bugs

The stranger

Lazy test

Erratic test

Buggy Test

 

Unnecessary Navigation in Selenium tests

Silent Horror

 

The Liar

Time Sensitive Tests

Order Dependent Tests

 

Stinky Synchronization Syndrome in GUI tests

Testing Happy Path only

 

Testing private methods (X-Ray Specs)

Time Bomb

Sensitive Equality

  

Get really clever and use random numbers in your tests

 

The Inspector

 

HeisenTests

  

Hide feature and end-to-end tests inside unit tests

 

Overspecification

    

Test scope overlap

 

Split Logic

    

Tests of different behaviors

 

Inheariting from the TestCase class

    

The Free Ride (Piggyback)

  

 

Test Tasarımı ile İlgili

Test adımlarındaki sorunlar

Mock ve Stub ile ilgili olanlar

Ürün kodu ile ilişkili olanlar

Test patternlerinin kullanmama

Testin setup aşamasındaki sorunlar

Assertionlar ile ilgili sorunlar

Tear down aşamasındaki sorunlar

Exception handling ile ilgili sorunlar

Not using page-objects patterns in Selenium tests

General fixture

No assertions

Sloppy Worker (Wet Floor)

Catching Unexpected Exceptions

Is Mockito Working Fine?

Test logic in production code

Mixing asserts with exercise

Test Maverick

Lying Test

Not Idempotent (Interacting Test With High Dependency)

The Secret Catcher (The Silent Catcher)

Mockito any() vs. isA()

Ugly mirror

 

Vague Header Setup

Assertionless Test

Teardown Only Test

The Greedy

Mock Happy (Mock-Overkill, The Mockery)

For Testers only

 

Excessive Setup

The Line Hitter

 

Expecting Exceptions Anywhere

Using more than one mock for a test

Obsolete tests

 

Refused Bequest

Inappropriate assertions

 

Expecting a Specific Exception

Mocking everyhing

Embedding implementation detail in tests

 

Excessive Inline Setup

Redundant assertions

   

Fire and Forget (also called Plate-Spinning)

 

Fragile Fixture

Inadequate assertions

   

Second Class Citizens

 

Inappropriately Shared Fixture

Under-the-carpet failing Assertion

    
 

The Cuckoo

Commented Code in the Test

    
 

The Mother Hen

Missing Verdict (in alt branches)

    
 

Setup Sermon

Tests That Can't Fail

    
  

ASSERTing obvious stuff

    
  

Obsolete Assertions

    

 

Test kodu ile ilgili olanlar

Dependencies

Kod tekrarı

Karmaşık ve anlaşılması zor

Kodlama ile ilgili geçerliği kanıtlanmış pratiklere (best practice) uyulmaması

Testler arasındaki bağımlılık

Dış bağımlılıklar

Test redundancy

Long test

Bad naming

Coupling between test methods

Dependencies on test containers

 

Complex test

Goto Statement

Order Dependent Tests

Mystery guest

 

Obscure test

Magic Numbers (not 0,0.0,1,1.0)

Lack of Cohesion of test methods (LCOTM)

Inefficient Tests

 

God Test Class

Deactivation On Another Level

The Peeping Tom

Resource Optimism

 

Obscure Test

Missing Activation

The Uninvited Guests)

Test Run War

 

Verbose Test

Missing Deactivation

Interacting Tests

Resource Leak

 

Indirect Test

Unreachable Default

Interacting Test Suites

The Local Hero

 

Long Parameter List

Duplicate Alt Branches

Lonely Test

The Operating System Evangelist

 

Large test Module

Fully Parameterized Template

Generous Leftovers

Hidden Dependency

 

Hard to maintain

Long Parameter List (6+ parameters)

Chain Gang

The Sequencer

 

The Giant

Stop In Function

 

Tooling details in test case

 

The One

No Comments

 

Counting on Spies

 

Overly Dry Tests

Messy Tests

 

Over-referencing

 

Biz bunların sadece farklı kategorilerde, en önemli olanlarından bahsedeceğiz. Aşağıdaki tabloda en çok tartışılan test smell tiplerinden oluşan bir liste ve  açıklamaları yer almaktadır.

 

Test-smell İsmi

Açıklama

Duplicate test code

Bir test metodunun başka bir test metodu ile aynı şeyi test etmesi veya kendi içinde tekrarlayan satırların bulunması.

Long / complex / verbose / obscure test

Testin ilk bakışta kolayca anlaşılamadığı, içerisinde çok fazla kod satırının bulunduğu test.

Eager test

Bir test metodunda çok fazla fonksiyonun test edilmesi.

Fragile/brittle test

Test edilen sistemin testi etkilemeyen bir bölümünün değiştirilmesi sonucunda derlenemez veya çalışamaz duruma gelmesi.

Flaky test

Aynı şartlarda bir testin bir geçip bir kalması durumu. Flaky testler, geliştiricilere ciddi zorluklar çıkartabilirler çünkü bu testlerin geçmemesi durumu, her zaman kaynak kodda sorun olduğu anlamına gelmemektedir.

General fixture

Testin sorgulaması gereken fonksiyonellik yerine daha geniş bir fikstüre referans vermesi ya da build etmesi. (bkz: Test Fixture)

Bad naming

Test metod adının anlamlı olmaması, anlaşılabilirliğinin düşük olması.

Slow test

Çalışması çok uzun süren test.

Assertion roulette

Aynı test metodunun birden fazla assertion içermesi ve testin başarısız olması durumunda hangi assertion’ın buna sebep olduğunu bulmanın zorlaşması.

Assertionless test

İçinde hiç assertion bulunmayan test.

Mystery guest

Testi inceleyen kişinin fikstür ile doğrulama mantığı arasındaki neden sonuç ilişkisini, bir kısmının test metodunun dışında yapılması sebebiyle görememesi.

 

Test smell’lerin daha iyi anlaşılması için tabloda yer alan bazı test smell tipleri için aşağıda verilen örneklerini inceleyebilirsiniz. 

Bu örnekte sarı ile işaretlenmiş satırlar her iki test metodunda da bulunuyor ve bu durum Duplicate Test Code olarak nitelendiriliyor. Buradaki sorun her iki metodda oluşturulan objenin Setup metoduna eklenmesi ile çözülebilir.

/** @test */

public function newly_registered_user_should_be_active()

{

    $user = User::register(

        UserId::generate(),

        new DateOfBirth("30 years ago"),

        new NationalInsuranceNumber("QQ 12 34 56 A")

    );

 

    $this->assertTrue($user->isActive());

}

 

/** @test */

public function newly_registered_user_should_not_be_premium()

{

    $user = User::register(

        UserId::generate(),

        new DateOfBirth("30 years ago"),

        new NationalInsuranceNumber("QQ 12 34 56 A")

    );

 

    $this->assertFalse($user->isPremium());

}

 Eager Test smell’i ise aşağıdaki örnekte görebiliyoruz. Geliştirici aynı sınıfa ait farklı metodları aynı test metodunda test etmiş. Bu durum test metodunun ne amaçlı kullanıldığına dair bir karışıklığa sebep oluyor.

 

@Test

    public void NmeaSentence_GPGSA_ReadValidValues(){

 

        NmeaSentence nmeaSentence = new NmeaSentence("$GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39");

        assertThat("GPGSA - read PDOP", nmeaSentence.getLatestPdop(), is("2.5"));

        assertThat("GPGSA - read HDOP", nmeaSentence.getLatestHdop(), is("1.3"));

        assertThat("GPGSA - read VDOP", nmeaSentence.getLatestVdop(), is("2.1"));

    }

 Benzer şekilde aynı test metodunda birden fazla assertion kullanılması, Assertion Roulette olarak adlandırılan test smell’e sebep oluyor. Bu durum testin başarısız olması durumunda hangi assertion’ın buna sebep olduğunu bulmanın zorlaşmasına sebep oluyor.  Bu örnekte 3 farklı assertion metodu kullanılmış ve herbiri farklı bir durumu test ediyor.

@MediumTest

    public void testCloneNonBareRepoFromLocalTestServer() throws Exception {

        Clone cloneOp = new Clone(false, integrationGitServerURIFor("small-repo.early.git"), helper().newFolder());

 

        Repository repo = executeAndWaitFor(cloneOp);

 

        assertThat(repo, hasGitObject("ba1f63e4430bff267d112b1e8afc1d6294db0ccc"));

 

        File readmeFile = new File(repo.getWorkTree(), "README");

        assertThat(readmeFile, exists());

        assertThat(readmeFile, ofLength(12));

    }

Test kodlarında yer alan bekleme adımları ilk başta çok ciddi bir zamansal uzamaya yol açmayacak gibi görünse de bu tip testlerin sayısı çok fazla ise veya çok fazla tekrar edilemsi gerekiyorsa tüm testlerin çalışması çok uzun sürecektir. (Slow Test)

   private static final int TWO_SECONDS = 3000;

 

   public void testWasInitialized_Async() throws InterruptedException {

      // Setup:

      RequestHandlerThread sut = new RequestHandlerThread();

      // Exercise:

      sut.start();

      // Verify:

      Thread.sleep(TWO_SECONDS);

      assertTrue(sut.initializedSuccessfully());

   }

 

   public void testHandleOneRequest_Async() throws InterruptedException {

      // Setup:

      RequestHandlerThread sut = new RequestHandlerThread();

      sut.start();

      // Exercise:

      enqueRequest(makeSimpleRequest());

      // Verify:

      Thread.sleep(TWO_SECONDS);

      assertEquals(1, sut.getNumberOfRequestsCompleted());

      assertResponseEquals(makeSimpleResponse(), getResponse());

   }

}

 

Testin ilk bakışta kolayca anlaşılamadığı, içerisinde çok fazla kod satırının bulunduğu test koduna bir örnek olarak yaklaşık 1500 satırlık bir test metodunu linkten görebilirsiniz. Bu tip test kodları da Long/Complex/Verbose/Obscure Test olarak adlandırılıyor.

Bir çok kaynakta farklı smell tiplerinden bahsediliyor. Örneğin StackOverflow.com’da açılan bir konuda 70 birim testi anti-patterninden oluşan bir katalog oluşturulmuş. Bunlardan bazıları; (1)Second Class Citizens: ürün kodu kadar iyi refactor edilmemiş, içinde test kodunun bakımını zorlaştıracak çok fazla tekrarlayan kod (duplicate code) barındıran test. (2) The Free Ride / Piggyback: Yeni gelen bir özelliği test etmek için yeni bir test metodu yazmak yerine, var olan bir test durumuna ekleme yaparak test etmeye çalışmak. (3) Happy Path: Sınırları yada özel durumları test etmeden en olağan senaryo (Happy Path) üzerinde giden test. (4) The Local Hero: Testin çalışması için yazıldığı geliştirme ortamına özgün bir şeye bağımlı olması. (5) The Hidden Dependency: The Local Hero’ya benzer bir smelldir. Çalışmadan önce bazı verilerin belli biryerde oluşmuş olması gereken, eğer oluşmazsa başarısız olacak test. (6) Chain Gang: birkaç testin belli bir sırada çalışmak zorunda olduğu durum, örneğin bir test global değişkenleri yada veritabanı verilerini değiştirecek , diğer test bu verilere göre çalışacak. (7) The Silent Catcher: Geliştiricinin beklediğinden farklı bir exception olsa bile farklı exceptionlar atıldığında da geçen test. (8) The Test It All: Eager Test’in farklı bir ismi diyebiliriz, bu tip test smell ile sıklıkla karşılaşıldığı ve test mühendileri için bunları yönetmenin tam anlamıyla bir kabus olduğu düşünülmektedir.

Farklı kaynaklarda aynı test smell’ler için farklı isimlendirmeler kullanılmış olabiliyor.  Örneğin“Testing anti-patterns: how to fail with 100% test coverage” isimli makalede “The ugly mirror” isimli (Tautological test olarak da bilinen) bir smell üzerinde durulmuş. Ugly Mirror, test kodunun test ettiği kaynak koda çok benzer olduğu durumdur. Aşağıda basit bir örneği verilmiştir. Genelde test kodları daha karmaşık olacağı için tautological testi farketmek daha zor olacaktır. Burada sum fonksiyonu 2 parametre alıp değerleri toplayan bir fonksiyon. test_sum_smelly ise bu fonksiyonu test eden test metodu.

def sum(ab)

b

end

 

def test_sum_smelly

assert_equal 5 3sum(53)

end

Bu smell’i düzeltmek için beklenen sonucu (expected outcome) yazdığınız yerde (5+3) hesaplamanın sonucunu(8) yazmalısınız.

def test_sum_healthy

assert_equal 8sum(53)

end

 

Yine bir çok kaynakta bir test smell’in başka bir tanesine de sebep olabileceği ifade edilmektedir. Örneğin Eager Test, Assertion Roulette’e sebep olabiliyor. 

Proven Test Hizmetleri ekipleri , Entegre Test Yönetimi Yaklaşımı, Bağımsız Test ve Dışkaynak Hizmetleri ile müşterilerinin yazılım ve bilişim alanındaki ürün ve hizmetlerinin kalitesini arttırmak için alternatif çözümler sağlamaktadır. Proven Blog sayfalarında, test smell’lerin nasıl engellenebileceği, nasıl tespit edilip düzeltilebileceği ile ilgili bilgiler vermeye devam edeceğiz.  Test smellerin detaylı sınıflandırması için bu linkten (https://goo.gl/1ZrL65) tüm sınıflandırmaya ulaşabilirsiniz.

Konuyla ilgili daha detaylı bilgiye www.proven.com.tr üzerinden veya [email protected] adresine e-posta atarak ulaşabilirsiniz.


Barış KÜÇÜK

Kaynaklar

[1]   V. Garousi B. Küçük "Smells in software test code: A survey of knowledge in industry and academia" Journal of Systems and Software vol. 138 2018. 

[2]   V. Garousi and B. Küçük, "The Entire Classification of Test Smells" in https://goo.gl/1ZrL65, Last accessed: Sept. 2018