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.
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(a, b)
a + b
end
def test_sum_smelly
assert_equal 5 + 3, sum(5, 3)
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 8, sum(5, 3)
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