Living life and Make it Better

life, learn, contribute

Endy Muhardin

Software Developer berdomisili di Jabodetabek, berkutat di lingkungan open source, terutama Java dan Linux.

Cost of Quality

Mumpung sedang hangat membahas testing, coba kita dengarkan apa pendapat manajemen.

Programmer [P] : Bos, kata Mr. Endy, kita harus bikin unit test, integration test, coverage test dsb Manager [M] : Stop, banyak sekali testnya? Memangnya kamu gak bisa bikin kode yang bener, sampe ditest segitu banyaknya? [P] : Ya bisa bos, tapi .. [M] : Ya sudah kalo bisa coding sana yang bener, test nanti saja waktu sudah delivery. Kita sudah mepet deadline ini. Kita dibayar bukan untuk coding unit test. [P] : (kecewa, cuma bisa gigit mouse) Ya deh bos …

Umumnya, begitulah tanggapan pihak manajemen dalam menyikapi test. Test dianggap sebagai kegiatan yang (walaupun) penting, prioritasnya ada di kelas ekonomi. Saya sendiri, walaupun merupakan penggemar unit testing dan percaya akan manfaatnya, kadang merasa tidak pede untuk berargumen dengan manajemen tentang urusan ini.

Sampai suatu hari, ada training di kantor tentang Software Engineering. Instrukturnya adalah konsultan manajemen yang ahli di bidang ISO dan CMMI.

Biasanya, saya rada skeptis dengan yang namanya konsultan manajemen. Baik itu konsultan pemasaran, konsultan ISO, atau bidang manajemen yang lainnya. Kebanyakan dari mereka (tidak semua, tapi kebanyakan) biasanya cuma bisnis jargon saja. Cari uang dari menciptakan jargon aneh, menjelaskan jargon tersebut, dan menyuruh kita menggunakan jargon tersebut. Selebihnya nasihatnya mirip-mirip ramalan bintang, nasihat yang buram dan bisa ditafsirkan apa saja.

Tapi kali ini, sang konsultan mengetengahkan jargon yang cukup menarik, yaitu Cost of Quality. Berikut penjelasannya.

Biasanya, dalam mengerjakan project software, kegiatan apa yang ada di project schedule kita? Umumnya seperti ini:

  1. Analisa : membuat spesifikasi requirement user
  2. Desain : desain tampilan dan arsitektur aplikasi
  3. Coding
  4. User Testing
  5. Implementasi : migrasi data, training user, tuning
  6. Garansi
  7. Makan-makan (kalo ada profit)

Si konsultan bilang, memang itu semua (1-6) adalah kegiatan utama. Tanpa kegiatan itu, project tidak bisa diutamakan. Tapi, dengan 6 aktifitas tersebut, kecil peluangnya kita akan sampai ke aktivitas #7, yaitu makan-makan.

“Kenapa begitu ?”, demikian para peserta training bertanya dengan antusias, nampaknya jelas kalau urusannya menyangkut perut (tidak jadi makan-makan) orang ngantuk bisa langsung jadi segar.

“Ya sebab di sana belum dianggarkan Biaya Kualitas (Cost of Quality), nantinya kualitas rendah, banyak perbaikan, sehingga profit terancam tergusur”, jawab instruktur.

Nah, sekarang apa saja biaya kualitas itu? Diantaranya adalah:

  1. Training : memastikan pekerjaan tidak dikerjakan secara trial and error

  2. Review : mendeteksi kesalahan sedini mungkin. Kesalahan desain bisa diperbaiki dalam beberapa halaman dokumen desain. Tetapi jika dibiarkan sampai coding, banyak sekali effort untuk debug dan bugfix yang harus dikeluarkan. Misal: mengganti iBatis dengan Hibernate di dokumen desain cukup dengan Find and Replace. Mengganti iBatis dengan Hibernate di kode program? Bayangkan sendiri.

  3. Rework : harus ada anggaran untuk melakukan perbaikan dokumen, bugfix, dsb

  4. Audit : sebelum hasil pekerjaan dideliver ke client, harus diperiksa dulu secara internal. Audit proses juga dilakukan untuk memastikan semua Cost of Quality sudah diperhitungkan dan dijalankan.

  5. Testing : testing sebanyak mungkin dan seawal mungkin.

Dalam project software, kita mengenal fenomena bola salju. Tadinya kecil, menggelinding menjadi besar sehingga dapat menggulung satu desa (seperti di film kartun). Di software sama. Kita ingin mendeteksi kesalahan sedini mungkin, kalau bisa di tahap analisa semua kesalahan sudah dieliminasi, sehingga tidak merembet ke kode program. Jadi, perlu ada review dan audit untuk memastikan kesalahan tidak ditemukan di fase-fase akhir. Karena memperbaiki kesalahan di awal (requirement dan design) jauh lebih murah daripada di akhir (kode program, materi user training, user manual, dan script migrasi data).

Baiklah, cost of quality itu penting. Sekarang bagaimana cara menjelaskan pada manajemen mengenai tambahan biaya tersebut?

Mari kita rekonstruksi dialog Programmer dan Manager.

Programmer [P] : Bos, kata Mr. Endy, kita harus bikin unit test, integration test, coverage test … Manager [M] : Stop, banyak sekali testnya? Memangnya kamu gak bisa bikin kode yang bener, sampe ditest segitu banyaknya? [P] : Ya bisa bos, tapi .. [M] : Ya sudah kalo bisa coding sana yang bener, test nanti saja waktu sudah delivery. Kita sudah mepet deadline ini. Kita dibayar bukan untuk coding unit test. [P] : Memangnya kalo boleh tau, seperti apa jadwalnya Bos? [M] : Kamu programmer mau tau saja. Tapi gpp, karena ini percakapan boongan baiklah akan saya kasi tau:

  1. Analisa : 2 minggu
  2. Desain : 1 minggu
  3. Coding : 4 minggu
  4. User Testing dan Bugfix : 4 minggu
  5. Implementasi : 4 minggu
  6. Garansi : 8 minggu Total : 23 minggu

[P] : Kenapa User Testing dan Implementasi lama sekali Bos? Sama lamanya dengan Coding? [M] : Ya itu karena kamu dan teman-temanmu kerjanya salah melulu, jadi di User Testing dan Implementasi banyak masalah.

[P] : Bagaimana kalau saya usul jadwal seperti ini :

  1. Analisa : 2 minggu
  2. Review dan rework : 1 minggu
  3. Desain : 1 minggu
  4. Review dan rework : 1 minggu
  5. Training : 1 minggu
  6. Coding + Unit Testing: 6 minggu
  7. User Testing dan Bugfix: 2 minggu
  8. Implementasi : 1 minggu
  9. Garansi : 8 minggu Total : 23 minggu Sama kan Bos, total waktunya. Saya yakin dengan tambahan aktifitas review, rework, dan training, kita akan bisa tangkap sebagian besar bug sebelum User Testing. Dengan demikian, bug yang terlihat sama user tinggal sedikit karena sudah kita temukan duluan. Kalau sebelum coding kita ditraining dulu, codingnya bisa lebih cepat dan bener Bos, sehingga bugnya lebih sedikit. Mudah-mudahan client jadi puas.

[M] : Wah pintar juga kamu, di rumah dikasi makan apa sama Emaknya bisa pintar gini? Baiklah akan saya coba usul kamu.

Demikianlah, cost of quality itu penting. Lebih penting lagi adalah cara kita menjual pada manajemen, supaya kegiatan testing dan training tidak dianggap sebagai kegiatan ‘buang-buang uang’ dan ‘tidak dibayar client’. Sebenarnya, client membayar untuk mendapat hasil yang berkualitas dan memuaskan. Secara tidak langsung, mereka membayar kita untuk melakukan testing.

Referensi : Software Quality at Top Speed


Ruthless Testing 2

Pada artikel sebelumnya, kita telah membahas tentang penggunaan unit test dan integration test sederhana menggunakan DBUnit. Pada artikel kali ini, kita akan membahas tentang coverage testing

Sebagai technical leader, project manager -atau apapun nama jabatannya- yang bertugas mengawasi segerombolan programmer, bagaimana cara kita memastikan bahwa mereka membuat unit test untuk setiap kode yang ditulis?

Cara manual tentu saja dengan memeriksa unit test satu-persatu dan membandingkannya dengan kode program aplikasi. Kita cocokkan tiap unit test dengan pasangan kode yang ditest. Dengan demikian, kita tahu mana kode yang sudah ditest mana yang belum.

Sayangnya, cara ini tidak otomatis. Sehingga kalau nanti dihadapkan pada deadline ketat, dipastikan kegiatan ini akan segera diabaikan. Oleh karena itu kita harus mengotomasi pemeriksaan test ini sehingga di tengah tekanan jadwal, kita tetap bisa memastikan unit test dibuat dengan benar.

Ada beberapa tools yang beredar di pasaran untuk mengotomasi pemeriksaan ini, diantaranya:

Semua tools di atas melakukan ‘coverage testing’, yaitu pengetesan apakah unit test yang dibuat telah meng-cover semua kode yang ditest. Jika unit testnya terlalu sedikit, maka coverage testing akan failed.

Pada contoh kali ini, kita akan menggunakan Cobertura. Cobertura mampu memeriksa coverage terhadap baris kode dan percabangan. Jadi kalau ada percabangan (if-else, switch) yang belum ditest, cobertura akan melaporkan. Kalau ada baris kode yang belum terjangkau unit test, cobertura juga akan memperingatkan.

Sebagai supervisor, kita dapat menetapkan aturan pada project. Misalnya, “Untuk project ini, saya ingin test meng-cover minimal 70% dari seluruh baris dan 90% dari seluruh percabangan”.

Bagaimana cara Cobertura melakukan keajaiban ini? Jawabannya, melalui mekanisme yang disebut code instrumentation. Cobertura akan memodifikasi bytecode (*.class) yang dihasilkan oleh proses kompilasi. Bytecode yang dihasilkan javac akan ditambah dengan kode-kode khusus untuk menghitung dan mendeteksi invokasi. Dengan demikian, pada saat unit test dijalankan, Cobertura akan mengetahui unit test yang mana menjalankan kode program yang mana. Bytecode yang akan diproses oleh Cobertura harus dikompile dengan flag -debug.

Dengan demikian, untuk menjalankan Cobertura, kita membutuhkan dua kali tahap kompilasi. Kompilasi pertama mengubah *.java menjadi *.class. Kompilasi kedua menambahkan instrumen di *.class.

Berikut adalah Ant target untuk melakukan instrumentasi.

<target name="instrument-cobertura" depends="compile-cobertura">        
        <!--
                Instrument the application classes, writing the
                instrumented classes into ${build.instrumented.dir}.
        -->        
        <cobertura-instrument todir="${compile.cobertura}">			
			<includeClasses regex=".*" />
			<excludeClasses regex=".*\Test.*" />  
			<instrumentationClasspath>
				<pathelement location="${compile.debug}" />
			</instrumentationClasspath>
        </cobertura-instrument>
    </target>

Selanjutnya, kita menjalankan unit test seperti biasa (melalui Ant target). Bedanya adalah, kita menggunakan hasil kompilasi Cobertura. Ant target untuk menjalankan unit test adalah sebagai berikut:

<target name="unit-test" depends="instrument-cobertura">
        <junit haltonfailure="false" fork="yes">            
            <classpath location="${compile.cobertura}"/>
            <classpath refid="cobertura-classpath"/>
            <formatter type="xml"/>
            <batchtest todir="${junit.result}">
                <fileset dir="${compile.debug}" includes="**/*Test.class"/>
            </batchtest>
        </junit>
    </target>

Perhatikan referensi ke ${compile.cobertura} yang diletakkan di atas referensi cobertura-classpath. Ini mengisyaratkan bahwa class hasil instrumentasi Cobertura diload lebih dulu daripada class yang dikompilasi secara normal.

Terakhir, kita buat Ant target untuk memeriksa coverage dan menghasilkan HTML report.

<target name="coverage-test" depends="unit-test">
        <cobertura-check datafile="cobertura.ser" 
                         branchrate="70" 
                         linerate="90"
                         haltonfailure="false"
        />
        <cobertura-report datafile="cobertura.ser"
                          srcdir="src" destdir="${cobertura.result}"
        />
    </target>

Perhatikan nilai batas baris (linerate) dan percabangan (branchrate) yang harus ditest. Nilainya sudah disesuaikan dengan aturan yang kita tetapkan di atas, yaitu sebesar 70% dan 90%.

Misalnya kita memiliki class DayCounter.java sebagai berikut:

public class DayCounter {
    public int numDays(int month, int year){
        switch (month) {
            case 1: 
            case 3:
            case 5: 
            case 7: 
            case 8:
            case 10:
            case 12: return 31;
            case 4: 
            case 6:
            case 9: 
            case 11: return 30;
            case 2: 
                if (year%4 == 0) {
                    return 29;
                } else {
                    return 28;
                }
            default: return 0;
        }
    }
} 

Dan unit testnya DayCounterTest.java sebagai berikut:

package tutorial.testframework;

import junit.framework.TestCase;

public class DayCounterTest extends TestCase {
    public void testNumDays() {
        DayCounter d = new DayCounter();
        assertEquals(31, d.numDays(12,2001));        
        assertEquals(30, d.numDays(11,2001));
        assertEquals(28, d.numDays(2,2001));
        assertEquals(29, d.numDays(2,2000));
        assertEquals(0, d.numDays(21,2000));
    }
}

Akan menghasilkan coverage yang bagus, karena semua baris sudah ditest. Berikut adalah hasil coverage testnya:

Cobertura All Pass

Kita juga bisa melihat coverage detail dari class DayCounter.java:

Cobertura Class Detail Pass

Bila kita non-aktifkan beberapa test, seperti ini:

package tutorial.testframework;

import junit.framework.TestCase;

public class DayCounterTest extends TestCase {
    public void testNumDays() {
        DayCounter d = new DayCounter();
        assertEquals(31, d.numDays(12,2001));        
        assertEquals(30, d.numDays(11,2001));
        //assertEquals(28, d.numDays(2,2001));
        //assertEquals(29, d.numDays(2,2000));
        //assertEquals(0, d.numDays(21,2000));
    }
}

Maka kode kita tidak akan lolos test, karena hasil coverage totalnya seperti ini:

Cobertura All Failed

dan detail classnya seperti ini

Cobertura Class Detail Failed

Bagaimana? Mudah dan cepat kan? Dengan cara ini, kita dapat mendeteksi tingkat kemalasan programmer dalam membuat unit test. Pada artikel selanjutnya, kita akan membahas tentang code-compliance test. Test ini sering juga disebut code-review.


Plugin untuk Archive

Tadinya cuma pengen tampilan arsip seperti punyanya mas Andry. Tapi sayangnya dia pakai aplikasi blog lain yang beda dengan saya punya. Jadi solusinya dia kemungkinan besar tidak bisa diimplement di sini.

Tadinya mau coding sendiri, browsing ke situs dokumentasi Wordpress, malah ketemu plugin Extended Live Archives, berikut cara instalasinya.

Setelah sedikit berkutat dengan file CSS, tadaaa…. silahkan lihat sendiri. Kalau anda melihat halaman kosong, tunggu sejenak, tandanya dia sedang loading. Nanti setelah loading, ada rangkuman arsip canggih berteknologi AJAX.

Cool … :D

Update: Sesuai saran mas Andry, saya coba instal SRG Clean Archives. Hasilnya bisa dilihat di sini.


Ruthless Testing 1

Sebelum menyerahkan aplikasi ke client, sudahkah Anda melakukan testing? 100% programmer pasti menjawab dengan kompak, “SUDAAAAHHHH !!!”

Nah, kalau begitu, pertanyaannya, apa saja yang sudah ditest?

Jawaban orang biasanya bervariasi, tergantung nilai project, jumlah orang yang tersedia, dan juga tergantung tingkat kegaptekan si programmer terhadap teknologi testing yang tersedia.

Testing minimal yang dilakukan biasanya adalah user testing. Jadi, ada user (atau programmer yang pura-pura jadi user) mencoba menggunakan aplikasi yang (katanya sih) sudah selesai. Biasanya fenomena ini akan berakhir dengan periode bugfix yang lebih lama daripada periode coding itu sendiri.

Jadi, bagaimana tipsnya supaya user testing berjalan dengan lancar, minim bug, kalaupun ada, bisa diselesaikan dalam bilangan hari?

Jawabannya adalah melaksanakan “ruthless testing”. Artinya testing secara kejam. Testing yang berlapis-lapis sehingga kalau ada programmer bandel yang membuat percabangan if terlalu dalam, akan langsung ketahuan.

Bagaimana itu? Kita akan lihat sebentar lagi.

Ada berapa jenis testing? Hmm .. menurut saya ada:

  1. Unit testing
  2. Integration testing
  3. Coverage testing
  4. Code compliance testing
  5. User/functional testing
  6. Performance testing

Semua test ini harus dijalankan tanpa kecuali.

What the !@#$? Banyak sekali? Apakah semua harus dijalankan? Wah, bisa-bisa kerjaan kita cuma ngurusin test doang? Bagaimana ini?

Hmm.. jangan khawatir, semua bisa dijalankan dengan investasi minimal di awal project. Tips saya adalah:

  1. Alokasikan pembuatan test bersama-sama dengan construction. Jangan setelah construction. Jadi, wahai Project Managers, kalau merencanakan construction, tambahkan alokasi waktu untuk membuat test.

  2. Semua test harus otomatis dan bisa dijalankan dengan sekali perintah. Di project saya, semua test dijalankan cukup dengan:

ant full-test

Baiklah, mari kita lihat satu persatu secara lebih detail.

Unit Testing

Ini adalah testing di level paling detail dari aplikasi. Artinya, testing di level method/function. Misalnya saya punya kode seperti ini:

public class DayCounter{
  /**
  * menghitung jumlah hari dalam bulan dan tahun yang diminta. 
  * Misalnya, bila month = 2 dan year = 2000, method ini akan menghasilkan 29.
  * @param month bulan yang akan dihitung
  * @param year tahun yang akan dihitung
  * @return jumlah hari dalam bulan dan tahun bersangkutan
  */
  public int numDays(int month, int year) {
    return 29;
  }
}

Coba lihat contoh kode di atas? Salah bukan? Apapun yang kita masukkan, hasilnya pasti 29. Mari kita buktikan dengan unit test bahwa kode di atas salah.

public class DayCounter extends junit.framework.TestCase{
  public void testNumDays() {
    DayCounter d = new DayCounter();
    assertEquals(31, d.numDays(1,2000)); 
  }
}

Pada waktu dijalankan, unit test tersebut akan menghasilkan tulisan FAILED

Di Java, unit test dapat dilakukan secara otomatis dengan menggunakan JUnit.

Integration Test

Sebagian besar aplikasi yang kita buat akan menggunakan database. Untuk itu, kita perlu mengetes apakah kode program kita dapat berinteraksi dengan database sesuai harapan. Mirip dengan JUnit, kita membuat unit test yang mengakses database. Misalnya, berikut kode programnya:

public class DatabaseAccess{
  /**
  * memeriksa username dan password dalam database. 
  * Method ini akan mencocokkan username dan password yang diberikan 
  * dan yang terdaftar di database.
  * Apabila cocok, akan menghasilkan true, dan apabila tidak cocok akan menghasilkan false.
  * @param username 
  * @param password
  * @return true kalau cocok, false kalau tidak cocok
  */
  public boolean checkLogin(String username, String password) {
    String sql = "SELECT * FROM user WHERE username=? AND password=?"
    // dst kode akses database
  }
}

Dan ini adalah kode untuk mengetesnya.

public class DatabaseAccess extends junit.framework.TestCase{
  public void testCheckLogin() {
    DatabaseAccess db = new DatabaseAccess();
    
    // asumsikan ada username endy dan password secret di database
    assertTrue(db.checkLogin("endy", "secret"));

    // coba dengan user sembarang, harusnya akan menghasilkan false
    assertFalse(db.checkLogin("nobody", "gakada"));
  }
}

Perhatikan bahwa kedua test di atas mengasumsikan bahwa kita punya database, terisi data endy:secret, dan tidak ada record nobody:gakada di dalam tabel user. Berarti, setiap dijalankan, kita harus memastikan asumsi di atas terpenuhi. Lalu, bagaimana cara menjalankannya secara otomatis? Gampang, gunakan DBUnit.

Dengan DBUnit, kita dapat me-reset kondisi database sesuai keinginan setiap kali test akan dijalankan. Caranya, kita harus buat test data, yaitu satu record berisi data endy:test dalam tabel user. Test data ini disimpan dalam file checkLogin.xml

<dataset>
  <user id='1' 
        username='endy'
        password='secret'
  />
</dataset>

Sebelum test dimulai, pastikan database direset. Tambahkan method berikut di kode test:

public class DatabaseAccess extends junit.framework.TestCase{
  public void testCheckLogin() {
    // reset database
    prepareDatabase();
    
    DatabaseAccess db = new DatabaseAccess();
    
    // asumsikan ada username endy dan password secret di database
    assertTrue(db.checkLogin("endy", "secret"));

    // coba dengan user sembarang, harusnya akan menghasilkan false
    assertFalse(db.checkLogin("nobody", "gakada"));
  }

  private void prepareDatabase(){
    DatabaseOperation.CLEAN_INSERT.execute(dbConn, getTestData());
  }

  private IDataSet getTestData(){
    return new FlatXmlDataSet(new FileInputStream("checkLogin.xml"));
  }
}

Nah, DBUnit akan dengan senang hati me-reset isi database development Anda. Pastikan test tersebut dijalankan di DATABASE DEVELOPMENT, dan bukan DATABASE PRODUCTION.

Sementara cukup sekian. Pada artikel selanjutnya, kita akan bahas tentang:

  1. Coverage Testing
  2. Code compliance testing
  3. User/functional testing
  4. Performance testing Dan sesuai janji saya, semua test otomatis dan sekali pencet.

Game Oldies

Berangkat dari membaca blog teman yang mengulas tentang Transport Tycoon Deluxe yang sekarang ada versi open sourcenya, saya jadi teringat koleksi game oldies saya.

Jaman saya SMU dulu, sekitar tahun 1994-1997, game favorit saya dan teman-teman adalah UFO-Enemy Unknown. Game ini berkisah tentang penyerbuan alien ke bumi. Dengan fitur yang cukup variatif, seperti kejar-kejaran pesawat alien, penyerbuan ke markas musuh, mempertahankan markas dari serbuan alien, dan riset teknologi, game ini terasa sangat canggih pada masa itu.

Pada waktu mulai bermain, teknologi pasukan kita sangat minim. Seiring dengan seringnya bertempur, kita mulai mengkoleksi persenjataan alien. Persenjataan ini kemudian diteliti. Setelah penelitian selesai, kita bisa menggunakan senjata tersebut.

Bukan cuma senjata, mayat dan pesawat alien juga diteliti, sehingga kalau kita sudah main cukup lama, semua teknologi alien bisa kita gunakan.

Game yang sudah lama punah ini, tetap bisa dimainkan di masa kini dengan cara download dari abandonia.

Sekuelnya juga ada, yaitu Terror From The Deep dan Apocalypse.

Selain game itu, kami dulu juga suka memainkan The Incredible Machine 2. Ini adalah game puzzle, di mana kita disuruh mengkombinasikan alat-alat yang tersedia untuk memecahkan masalah yang diberikan. Alat-alat yang disediakan bermacam-macam, mulai dari balon zeppelin, anti gravitasi, motor listrik, blender, dan sebagainya. Sangat bermanfaat untuk mendongkrak kreativitas adik-adik kita di SD.

Untuk memainkan game berbasis DOS ini, kita membutuhkan DOS Emulator. Saya biasa menggunakan DosBox dengan front-end D-Fend.

Dengan dos emulator ini, kita bisa juga menjalankan game yang bahkan lebih tua lagi seperti Digger.