CodeGym /Java Blogu /Rastgele /Birim testi hakkında her şey: teknikler, kavramlar, uygul...
John Squirrels
Seviye
San Francisco

Birim testi hakkında her şey: teknikler, kavramlar, uygulama

grupta yayınlandı
Bugün testlerle dolu olmayan bir uygulama bulamayacaksınız, bu nedenle bu konu acemi geliştiriciler için her zamankinden daha alakalı olacak: testler olmadan başarılı olamazsınız. Prensip olarak ne tür testlerin kullanıldığını ele alalım ve ardından birim testi hakkında bilinmesi gereken her şeyi ayrıntılı olarak inceleyeceğiz. Birim testi hakkında her şey: teknikler, kavramlar, uygulama - 1

test türleri

test nedir? Wikipedia'ya göre: "Yazılım testi, ilgilenilen bir veya daha fazla özelliği değerlendirmek için bir yazılım bileşeninin veya sistem bileşeninin yürütülmesini içerir." Diğer bir deyişle, belirli durumlarda sistemimizin doğruluğunun kontrol edilmesidir. Peki, genel olarak ne tür testler olduğunu görelim:
  • Birim testi — Amacı, sistemin her bir modülünü ayrı ayrı kontrol etmek olan testler. Bu testler sistemin en küçük atomik parçalarına, örneğin modüllere uygulanmalıdır.
  • Sistem testi — Uygulamanın daha büyük bir parçasının veya bir bütün olarak sistemin çalışmasını kontrol etmek için üst düzey test.
  • Regresyon testi — Yeni özelliklerin veya hata düzeltmelerinin uygulamanın mevcut işlevselliğini etkileyip etkilemediğini veya eski hataları ortaya çıkarıp çıkarmadığını kontrol etmek için kullanılan test.
  • İşlevsel test — Uygulamanın bir bölümünün teknik özelliklerde, kullanıcı hikayelerinde vb. belirtilen gereksinimleri karşılayıp karşılamadığının kontrol edilmesi.

    İşlevsel test türleri:

    • Beyaz kutu testi — Sistemin dahili uygulamasını bilirken, uygulamanın bir bölümünün gereksinimleri karşılayıp karşılamadığının kontrol edilmesi;
    • Kara kutu testi — Sistemin dahili uygulamasını bilmeden uygulamanın bir bölümünün gereksinimleri karşılayıp karşılamadığını kontrol etme.

  • Performans testi — Sistemin veya sistem parçasının belirli bir yük altında nasıl performans gösterdiğini belirlemek için yazılan testler.
  • Yük testi — Standart yükler altında sistemin kararlılığını kontrol etmek ve uygulamanın hala doğru şekilde çalıştığı maksimum yükü bulmak için tasarlanmış testler.
  • Stres testi — Standart olmayan yükler altında uygulamanın performansını kontrol etmek ve sistem arızasından önce maksimum yükü belirlemek için tasarlanmış test.
  • Güvenlik testi — Sistemin güvenliğini kontrol etmek için kullanılan testler (bilgisayar korsanlarından, virüslerden, gizli verilere yetkisiz erişimden ve diğer keyifli saldırılardan).
  • Yerelleştirme testi — Uygulamanın yerelleştirme testleri.
  • Kullanılabilirlik testi — Kullanılabilirliği, anlaşılabilirliği, çekiciliği ve öğrenilebilirliği kontrol etmeyi amaçlayan test.
Bunların hepsi kulağa hoş geliyor, ancak pratikte nasıl çalışıyor? Basit! Mike Cohn'un test piramidini kullanıyoruz: Birim testi hakkında her şey: teknikler, kavramlar, uygulama - 2Bu, piramidin basitleştirilmiş bir versiyonudur: şimdi daha da küçük parçalara bölünmüştür. Ama bugün fazla sofistike olmayacağız. En basit versiyonu ele alacağız.
  1. Birim — Bu bölüm, uygulamanın farklı katmanlarında uygulanan birim testlerini ifade eder. Uygulama mantığının bölünebilir en küçük birimini test ederler. Örneğin, sınıflar, ancak çoğu zaman yöntemler. Bu testler genellikle test edilen şeyi herhangi bir dış mantıktan mümkün olduğunca izole etmeye çalışır. Yani, uygulamanın geri kalanının beklendiği gibi çalıştığı yanılsamasını yaratmaya çalışırlar.

    Küçük parçaları test ettikleri ve çok hafif oldukları, çok fazla kaynak tüketmedikleri (yani RAM ve zaman) için bu testlerden her zaman çok sayıda olmalıdır (diğer tüm türlerden daha fazla).

  2. Entegrasyon — Bu bölüm entegrasyon testiyle ilgilidir. Bu test, sistemin daha büyük parçalarını kontrol eder. Yani, ya birkaç mantık parçasını (birkaç yöntem ya da sınıf) birleştirir ya da harici bir bileşenle etkileşimin doğruluğunu kontrol eder. Bu testler genellikle birim testlerden daha küçüktür çünkü daha ağırdırlar.

    Bir entegrasyon testi örneği, bir veritabanına bağlanmak ve onunla çalışmak için yöntemlerin çalışmasının doğruluğunu kontrol etmek olabilir.

  3. UI — Bu bölüm, kullanıcı arayüzünün çalışmasını kontrol eden testleri ifade eder. Mantığı uygulamanın tüm seviyelerinde içerirler, bu nedenle uçtan uca testler olarak da adlandırılırlar. Kural olarak, çok daha azı vardır, çünkü en zahmetlidirler ve en gerekli (kullanılan) yolları kontrol etmeleri gerekir.

    Yukarıdaki resimde, üçgenin farklı bölümlerinin boyut olarak değiştiğini görüyoruz: gerçek çalışmadaki farklı test türlerinin sayısında yaklaşık olarak aynı oranlar var.

    Bugün en yaygın testlere, birim testlerine daha yakından bakacağız, çünkü kendine saygısı olan tüm Java geliştiricileri bunları temel düzeyde kullanabilmelidir.

Birim testindeki temel kavramlar

Test kapsamı (kod kapsamı), bir uygulamanın ne kadar iyi test edildiğinin ana ölçülerinden biridir. Bu, testlerin kapsadığı kodun yüzdesidir (%0-100). Uygulamada, çoğu hedef olarak bu yüzdeyi takip ediyor. Bu benim katılmadığım bir şey, çünkü bu, testlerin ihtiyaç duyulmadıkları yerde uygulanmaya başlanması anlamına geliyor. Örneğin, hizmetimizde ek mantık olmadan standart CRUD (oluştur/al/güncelle/sil) işlemlerimiz olduğunu varsayalım. Bu yöntemler, işi havuzla çalışan katmana devreden tamamen aracılardır. Bu durumda, verilen yöntemin bir DAO yöntemini çağırıp çağırmadığı dışında test edecek hiçbir şeyimiz yok, ama bu bir şaka. Test kapsamını değerlendirmek için genellikle ek araçlar kullanılır: JaCoCo, Cobertura, Clover, Emma, ​​vb. Bu konuyla ilgili daha ayrıntılı bir çalışma için, TDD, test odaklı geliştirme anlamına gelir. Bu yaklaşımda, başka bir şey yapmadan önce belirli kodu kontrol edecek bir test yazarsınız. Bunun bir kara kutu testi olduğu ortaya çıkıyor: girdinin ne olduğunu ve çıktının ne olması gerektiğini biliyoruz. Bu, kod tekrarından kaçınmayı mümkün kılar. Test güdümlü geliştirme, uygulamanızdaki her bir işlevsellik parçası için testler tasarlamak ve geliştirmekle başlar. TDD yaklaşımında, önce kodun davranışını tanımlayan ve test eden bir test oluşturuyoruz. TDD'nin temel amacı, kodunuzu daha anlaşılır, daha basit ve hatasız hale getirmektir. Birim testi hakkında her şey: teknikler, kavramlar, uygulama - 3Yaklaşım aşağıdakilerden oluşur:
  • Testimizi yazıyoruz.
  • Testi çalıştırıyoruz. Şaşırtıcı olmayan bir şekilde, gerekli mantığı henüz uygulamadığımız için başarısız oluyor.
  • Testin geçmesine neden olan kodu ekleyin (testi tekrar çalıştırıyoruz).
  • Kodu refactor ediyoruz.
TDD, test otomasyonu piramidindeki en küçük yapı taşları oldukları için birim testlerine dayanır. Birim testleri ile herhangi bir sınıfın iş mantığını test edebiliriz. BDD, davranış odaklı gelişim anlamına gelir. Bu yaklaşım TDD'ye dayanmaktadır. Daha spesifik olarak, geliştirmeye dahil olan herkes için sistem davranışını açıklayan basit dil örnekleri kullanır. Esas olarak test uzmanlarını ve iş analistlerini etkilediği için bu terime girmeyeceğiz. Test senaryosu, test edilen kodu kontrol etmek için gereken adımları, belirli koşulları ve parametreleri açıklayan bir senaryodur. Bir test fikstürü, test edilen yöntemin başarılı bir şekilde çalışması için gerekli duruma sahip olacak şekilde test ortamını ayarlayan koddur. Önceden tanımlanmış bir dizi nesne ve bunların belirli koşullar altındaki davranışlarıdır.

test aşamaları

Bir test üç aşamadan oluşur:
  • Test verilerini (fikstürler) belirtin.
  • Test edilen kodu çalıştırın (test edilen yöntemi çağırın).
  • Sonuçları doğrulayın ve beklenen sonuçlarla karşılaştırın.
Birim testi hakkında her şey: teknikler, kavramlar, uygulama - 4Test modülerliğini sağlamak için uygulamanın diğer katmanlarından izole etmeniz gerekir. Bu taslaklar, alaylar ve casuslar kullanılarak yapılabilir. Taklitler, özelleştirilebilen nesnelerdir (örneğin, her test için uyarlanmış). Yöntem çağrılarından ne beklediğimizi, yani beklenen yanıtları belirtmemize izin verirler. Beklediğimizi aldığımızı doğrulamak için sahte nesneler kullanırız. Saplamalar, test sırasında çağrılara sabit kodlanmış bir yanıt sağlar. Ayrıca aramayla ilgili bilgileri (örneğin parametreler veya arama sayısı) saklayabilirler. Bunlara bazen casus denir. Bazen insanlar saplama ve alay terimlerini karıştırırlar: Aradaki fark, bir saplamanın hiçbir şeyi denetlememesidir; yalnızca belirli bir durumu simüle eder. Sahte, beklentileri olan bir nesnedir. Örneğin, belirli bir yöntemin belirli sayıda çağrılması gerekir. Başka bir deyişle,

Test ortamları

Yani, şimdi konuya. Java için kullanılabilen birkaç test ortamı (çerçevesi) vardır. Bunlardan en popüler olanları JUnit ve TestNG'dir. Buradaki incelememiz için şunları kullanıyoruz: Birim testi hakkında her şey: teknikler, kavramlar, uygulama - 5Bir JUnit testi, yalnızca test için kullanılan bir sınıftaki bir yöntemdir. Sınıf genellikle test ettiği sınıfla aynı şekilde adlandırılır ve sonuna "Test" eklenir. Örneğin, CarService -> CarServiceTest. Maven yapı sistemi, bu tür sınıfları otomatik olarak test kapsamına dahil eder. Aslında bu sınıfa test sınıfı denir. Temel ek açıklamaları kısaca gözden geçirelim:

  • @Test, yöntemin bir test olduğunu belirtir (temel olarak, bu notla işaretlenen bir yöntem bir birim testidir).
  • @Before, her testten önce yürütülecek bir yöntemi belirtir. Örneğin, bir sınıfı test verileriyle doldurmak, giriş verilerini okumak vb.
  • @After, her testten sonra çağrılacak bir yöntemi işaretlemek için kullanılır (örn. verileri silmek veya varsayılan değerleri geri yüklemek için).
  • @BeforeClass, @Before'a benzer bir yöntemin üzerine yerleştirilir. Ancak böyle bir yöntem, verilen sınıf için tüm testlerden önce yalnızca bir kez çağrılır ve bu nedenle statik olmalıdır. Bir test veritabanını döndürmek gibi daha yoğun kaynak gerektiren işlemleri gerçekleştirmek için kullanılır.
  • @AfterClass, @BeforeClass'ın tersidir: verilen sınıf için bir kez, ancak yalnızca tüm testlerden sonra yürütülür. Örneğin, kalıcı kaynakları temizlemek veya bir veri tabanından bağlantıyı kesmek için kullanılır.
  • @Ignore, bir yöntemin devre dışı bırakıldığını ve genel test çalıştırması sırasında yoksayılacağını belirtir. Bu, çeşitli durumlarda kullanılır, örneğin, temel yöntem değiştirilmişse ve değişikliklere uyum sağlamak için test henüz yeniden çalışılmamışsa. Bu gibi durumlarda, bir açıklama eklemek de arzu edilir, örneğin @Ignore("Bazı açıklamalar").
  • @Test(beklenen = İstisna.sınıf) negatif testler için kullanılır. Bunlar, bir hata durumunda yöntemin nasıl davrandığını doğrulayan testlerdir, yani test, yöntemin bir tür istisna atmasını bekler. Böyle bir yöntem, @Test ek açıklamasıyla belirtilir, ancak hangi hatanın yakalanacağı belirtilir.
  • @Test(timeout = 100), yöntemin en fazla 100 milisaniye içinde yürütüldüğünü kontrol eder.
  • @Mock, sahte bir nesne atamak için bir alanın üzerinde kullanılır (bu, JUnit ek açıklaması değildir, bunun yerine Mockito'dan gelir). Gerektiğinde, modelin belirli bir durum için davranışını doğrudan test yönteminde ayarlarız.
  • @RunWith(MockitoJUnitRunner.class) bir sınıfın üstüne yerleştirilir. Bu ek açıklama, JUnit'e sınıftaki testleri başlatmasını söyler. Bunlar dahil olmak üzere çeşitli koşucular vardır: MockitoJUnitRunner, JUnitPlatform ve SpringRunner. JUnit 5'te, @RunWith ek açıklaması, daha güçlü @ExtendWith ek açıklamasıyla değiştirilmiştir.
Sonuçları karşılaştırmak için kullanılan bazı yöntemlere bir göz atalım:

  • iddiaEquals(Nesne bekleniyor, Nesne gerçekleri) — iletilen nesnelerin eşit olup olmadığını kontrol eder.
  • iddiaTrue(boolean flag) — iletilen değerin doğru olup olmadığını kontrol eder.
  • iddiaFalse(boolean flag) — iletilen değerin yanlış olup olmadığını kontrol eder.
  • iddiaNull(Object object) — iletilen nesnenin boş olup olmadığını kontrol eder.
  • iddiaSame(Object firstObject, Object secondObject) — iletilen değerlerin aynı nesneye atıfta bulunup bulunmadığını kontrol eder.
  • iddia That(T t, Eşleştirici eşleştirici) — t'nin eşleştiricide belirtilen koşulu karşılayıp karşılamadığını kontrol eder.
AssertJ ayrıca yararlı bir karşılaştırma yöntemi de sağlar: assertThat(firstObject).isEqualTo(secondObject) . Burada temel yöntemlerden bahsetmiştim - diğerleri yukarıdakilerin varyasyonlarıdır.

pratikte test

Şimdi yukarıdaki malzemeye belirli bir örnekle bakalım. Bir hizmetin güncelleme yöntemini test edeceğiz. Varsayılanı kullandığımız için DAO katmanını dikkate almayacağız. Testler için bir başlatıcı ekleyelim:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <version>2.2.2.RELEASE</version>
   <scope>test</scope>
</dependency>
Ve burada servis sınıfımız var:

@Service
@RequiredArgsConstructor
public class RobotServiceImpl implements RobotService {
   private final RobotDAO robotDAO;

   @Override
   public Robot update(Long id, Robot robot) {
       Robot found = robotDAO.findById(id);
       return robotDAO.update(Robot.builder()
               .id(id)
               .name(robot.getName() != null ? robot.getName() : found.getName())
               .cpu(robot.getCpu() != null ? robot.getCpu() : found.getCpu())
               .producer(robot.getProducer() != null ? robot.getProducer() : found.getProducer())
               .build());
   }
}
Satır 8 — güncellenen nesneyi veritabanından çekin. Satır 9-14 — oluşturucu aracılığıyla bir nesne oluşturun. Gelen nesnenin bir alanı varsa, ayarlayın. Değilse, veritabanında olanı bırakacağız. Şimdi testimize bakın:

@RunWith(MockitoJUnitRunner.class)
public class RobotServiceImplTest {
   @Mock
   private RobotDAO robotDAO;

   private RobotServiceImpl robotService;

   private static Robot testRobot;

   @BeforeClass
   public static void prepareTestData() {
       testRobot = Robot
               .builder()
               .id(123L)
               .name("testRobotMolly")
               .cpu("Intel Core i7-9700K")
               .producer("China")
               .build();
   }

   @Before
   public void init() {
       robotService = new RobotServiceImpl(robotDAO);
   }
Hat 1 — Runner'ımız. 4. Satır - sahte bir hizmet kullanarak hizmeti DAO katmanından izole ediyoruz. Satır 11 — sınıf için bir test varlığı (kobay olarak kullanacağımız) belirledik. Satır 22 — test edeceğimiz hizmet nesnesini ayarladık.

@Test
public void updateTest() {
   when(robotDAO.findById(any(Long.class))).thenReturn(testRobot);
   when(robotDAO.update(any(Robot.class))).then(returnsFirstArg());
   Robot robotForUpdate = Robot
           .builder()
           .name("Vally")
           .cpu("AMD Ryzen 7 2700X")
           .build();

   Robot resultRobot = robotService.update(123L, robotForUpdate);

   assertNotNull(resultRobot);
   assertSame(resultRobot.getId(),testRobot.getId());
   assertThat(resultRobot.getName()).isEqualTo(robotForUpdate.getName());
   assertTrue(resultRobot.getCpu().equals(robotForUpdate.getCpu()));
   assertEquals(resultRobot.getProducer(),testRobot.getProducer());
}
Burada testin üç net bölümü olduğunu görüyoruz: 3-9. Satırlar — fikstürleri belirtiyor. Satır 11 — test edilen kodu çalıştırıyor. Satır 13-17 — sonuçların kontrol edilmesi. Daha ayrıntılı olarak: Satır 3-4 — DAO modeli için davranışı ayarlayın. Satır 5 — standartımızın üstüne güncelleyeceğimiz örneği ayarlayın. Satır 11 - yöntemi kullanın ve ortaya çıkan örneği alın. Satır 13 — boş olmadığını kontrol edin. Satır 14 — sonucun kimliğini ve verilen yöntem argümanlarını karşılaştırın. Satır 15 — adın güncellenip güncellenmediğini kontrol edin. Satır 16 — CPU sonucuna bakın. Satır 17 — örnekte bu alanı belirtmedik, bu yüzden aynı kalmalıdır. Bu durumu burada kontrol ediyoruz. Çalıştıralım:Birim testi hakkında her şey: teknikler, kavramlar, uygulama - 6Test yeşil! Rahat bir nefes alabiliriz :) Özetle, test etmek kodun kalitesini artırır ve geliştirme sürecini daha esnek ve güvenilir hale getirir. Yüzlerce sınıf dosyası içeren bir yazılımı yeniden tasarlamanın ne kadar çaba gerektirdiğini hayal edin. Tüm bu sınıflar için yazılmış birim testlerimiz olduğunda, güvenle yeniden düzenleme yapabiliriz. Ve en önemlisi, geliştirme sırasında hataları kolayca bulmamıza yardımcı olur. Beyler ve bayanlar, bugün sahip olduğum tek şey bu. Bana bir beğeni ver ve bir yorum bırak :)
Yorumlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION