Bagaimana saya bisa menguji unit kelas yang menggunakan SimpleJdbcCall

Saya memiliki kelas yang terlihat seperti ini:

public class MyClass {

    private final SimpleJdbcCall simpleJdbcCall;

    public MyClass(final DataSource dataSource) {
        this(new JdbcTemplate(dataSource));
    }

    public MyClass(final JdbcTemplate template) {
        simpleJdbcCall = new SimpleJdbcCall(template)
            .withoutProcedureColumnMetaDataAccess()
            .withCatalogName("MY_ORACLE_PACKAGE")
            .withFunctionName("GET_VALUE")
            .withReturnValue()
            .declareParameters(
                new SqlOutParameter("RESULT", Types.VARCHAR))
            .declareParameters(
                new SqlParameter("P_VAR1_NAME", Types.VARCHAR))
            .declareParameters(
                new SqlParameter("P_VAR2_NAME", Types.VARCHAR))
            .useInParameterNames("P_VAR1_NAME", "P_VAR2_NAME");
    }

    private String getValue(final String input) {
        final SqlParameterSource params = new MapSqlParameterSource()
            .addValue("P_VAR1_NAME", input, Types.VARCHAR)
            .addValue("P_VAR2_NAME", null, Types.VARCHAR);
        return simpleJdbcCall.executeFunction(String.class, params);
    }
}

Ini berfungsi seperti yang diharapkan, tetapi saya ingin menulis unit test untuk itu dan itu membuat saya gila. Saya telah mencoba mengejek JdbcTemplate (Mockito), tetapi hal itu mengarah pada koneksi yang mengejek, metadata, dll, dan saya bingung tentang kapan pabrik pernyataan yang dapat dipanggil ikut berperan.

Saya kira saya bisa menulisnya sehingga SimpleJdbcCall diteruskan sebagai parameter ke konstruktor baru dan kemudian mengejeknya, tapi itu terasa hackish. Saya lebih suka tes ini tidak mempengaruhi kelas kecuali untuk memperbaikinya.

Saya ingin tetap menggunakan SimpleJdbcCall API ini. Itu menulis SQL untuk saya jadi saya tidak perlu mencampur SQL dan Java, tapi saya juga sangat ingin menguji hal ini tanpa harus menulis 1000 baris kode. Adakah yang bisa melihat cara yang bagus untuk menguji ini?


person Matt Malone    schedule 19.10.2015    source sumber


Jawaban (5)


Saya juga memilih untuk tidak memasukkan 15 SimpleJdbcCalls yang berbeda ke dalam repositori saya, jadi saya mengambil inti dan menambahkan ini ke metode pengaturan pengujian saya:

DatabaseMetaData metaData = mock(DatabaseMetaData.class);
Connection con = mock(Connection.class);
when(con.getMetaData()).thenReturn(metaData);
DataSource ds = mock(DataSource.class);
when(ds.getConnection()).thenReturn(con);
jdbcTemplate = mock(JdbcTemplate.class);
when(jdbcTemplate.getDataSource()).thenReturn(ds);
person Jeff E    schedule 30.01.2019

Saya pasti akan mengambil pendekatan menambahkan konstruktor untuk memungkinkan SimpleJdbcCall disuntikkan secara langsung.

MyClass(SimpleJdbcCall simpleJdbcCall) {
  this.simpleJdbcCall = simpleJdbcCall;
}

(dan mungkin memanggil konstruktor itu dari konstruktor yang saat ini memanggil new).

Ini bukan "hackish", ini hanyalah Injeksi Ketergantungan. Saya berpendapat bahwa membuat kelas dapat diuji tanpa perlu menguji cara kerja SimpleJdbcCall adalah peningkatan yang pasti.

Memanggil new di konstruktor membuat pengujian lebih sulit karena ini merupakan sambungan statis yang ketat ke kelas yang sedang dipakai.

Saya menemukan entri blog Miško Hevery di sini topik yang sangat menarik.

person Andy Turner    schedule 19.10.2015
comment
DI sebenarnya dimaksudkan untuk dependensi yang dapat dikonfigurasi, dan tidak cocok untuk menggantikan objek stateful seperti SimpleJdbcCall. Dan ketergantungan seperti ini dapat ditiru dengan baik, sehingga kode sudah dapat diuji dengan mudah. - person Rogério; 21.10.2015
comment
ketergantungan seperti ini bisa diolok-olok saja Bagaimana? (Pertanyaan asli) - person Andy Turner; 21.10.2015
comment
Perhatikan bahwa Anda dapat menyimpan konfigurasi SimpleJdbcCall di dalam konstruktor; izinkan saja pengujian untuk membuat instance di luar sehingga pengujian dapat menimpa executeFunction(). - person Aaron Digulla; 21.10.2015
comment
Dengan menggunakan pustaka mengolok-olok (sebagai lawan dari pustaka objek tiruan yang lebih mendasar). Untuk Java, kami memiliki PowerMockito dan JMockit. - person Rogério; 21.10.2015
comment
@Rogério sepertinya Anda memiliki jawaban yang valid, apakah Anda akan mempertimbangkan untuk menulisnya lebih lengkap? - person Andy Turner; 21.10.2015
comment
Saya menambahkan contoh tes dalam jawaban saya. - person Rogério; 22.10.2015

Tambahkan konstruktor tambahan:

/*test*/ MyClass(final SimpleJdbcCall call) {
    simpleJdbcCall = call
        .withoutProcedureColumnMetaDataAccess()
        .withCatalogName("MY_ORACLE_PACKAGE")
        .withFunctionName("GET_VALUE")
        .withReturnValue()
        .declareParameters(
            new SqlOutParameter("RESULT", Types.VARCHAR))
        .declareParameters(
            new SqlParameter("P_VAR1_NAME", Types.VARCHAR))
        .declareParameters(
            new SqlParameter("P_VAR2_NAME", Types.VARCHAR))
        .useInParameterNames("P_VAR1_NAME", "P_VAR2_NAME");
}

Yang ini adalah paket privat, sehingga kelas lain dalam paket yang sama (=tes) dapat memanggilnya. Dengan cara ini, pengujian dapat membuat sebuah instance yang telah executeFunction() ditimpa. Anda dapat mengembalikan hasil palsu dalam metode atau menguji keadaan objek.

Itu berarti kode Anda masih mengonfigurasi objek; pengujian hanya melewati "POJO" yang diisi oleh kode yang diuji.

Dengan cara ini, Anda tidak perlu menulis banyak kode - implementasi default akan melakukan sebagian besar pekerjaan untuk Anda.

Alternatifnya, izinkan untuk memanggil konstruktor dengan antarmuka SimpleJdbcCallOperations yang berarti Anda memerlukan kerangka tiruan yang kuat atau menulis banyak kode pelat ketel.

Alternatif lain: Gunakan driver JDBC tiruan. Ini biasanya sulit diatur, menyebabkan kegagalan pengujian palsu, ketika pengujian gagal, Anda sering kali tidak mengetahui alasannya, ...

Atau database dalam memori. Mereka datang dengan banyak masalah (Anda perlu memuat data pengujian yang perlu Anda buat dan pelihara).

Itu sebabnya saya mencoba menghindari bolak-balik melalui lapisan JDBC bila saya bisa. Asumsikan JDBC dan database berfungsi - orang lain telah menguji kode ini. Jika Anda melakukannya lagi, Anda hanya membuang-buang waktu.

Terkait:

person Aaron Digulla    schedule 21.10.2015

Rekomendasi pertama saya adalah tidak mengujinya secara unit; tulis pengujian integrasi, yang sebenarnya menjalankan fungsi yang disimpan dalam database Oracle (tetapi kembalikan transaksinya).

Jika tidak, Anda dapat meniru kelas SimpleJdbcCall, dengan kode yang sedang diuji apa adanya, dengan menggunakan PowerMockito atau JMockit.

Contoh tes dengan JMockit:

@Mocked DataSource ds;
@Mocked SimpleJdbcCall dbCall;

@Test
public void verifyDbCall() {
    String value = new MyClass(ds).getValue("some input");

    // assert on value

    new Verifications() {{
        SqlParameterSource params;
        dbCall.executeFunction(String.class, params = withCapture());
        // JUnit asserts on `params`
    }};
}
person Rogério    schedule 21.10.2015
comment
Komentar umum: Tes integrasi sering kali berbau kode (tidak tahu apa yang mereka lakukan) - Jika mereka mengetahuinya, mereka dapat menulis tes unit yang sederhana, kecil dan efisien. Karena tidak, mereka hanya menguji sesuatu dan banyak hal tetapi pada akhirnya, tidak ada yang bisa mengatakan apa yang diuji dan apa yang tidak. - person Aaron Digulla; 22.10.2015
comment
Saya harus mengaku tidak terbiasa dengan JMockit: bagaimana dbCall dalam contoh Anda terkait dengan MyClass? - person Andy Turner; 22.10.2015
comment
@AaronDigulla Itu tidak masuk akal. Tidak ada yang menganjurkan melakukan hanya pengujian unit seperti yang Anda sarankan, karena mereka tidak pandai menemukan bug. Konsensus umum, AFAIK, adalah melakukan keduanya pengujian unit dan beberapa jenis pengujian integrasi. Meskipun demikian, secara pribadi, saya lebih suka melakukan tes integrasi saja, karena tes ini telah bekerja sangat baik bagi saya di beberapa proyek. - person Rogério; 22.10.2015
comment
@AndyTurner Instance dbCall yang ditugaskan ke bidang tiruan adalah perwakilan dari semua SimpleJdbcCall instance yang digunakan selama pengujian; dengan demikian, ini dapat digunakan saat merekam dan/atau memverifikasi ekspektasi yang akan cocok dengan pemanggilan pada instance lain. - person Rogério; 22.10.2015
comment
@Rogério: Orang yang berbeda membuat pengalaman yang berbeda. Saya telah melihat banyak proyek di mana data produksi disalin ke dalam pengujian dan manajemen menganggap ini sudah cukup. Jadi saya mencoba memperingatkan orang-orang bahwa Anda perlu memahami apa yang Anda lakukan; menjalankan banyak pengujian pada banyak data saja tidaklah cukup. - person Aaron Digulla; 22.10.2015

Saya melakukannya menggunakan http://www.jmock.org/

Konfigurasi XML -

<bean id="simpleJDBCCall" class="org.springframework.jdbc.core.simple.SimpleJdbcCall">
    <property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>

Berkas Java -

@Autowired
private SimpleJdbcCall jdbcCall;

Kelas Tes -

simpleJDBCCall = mockingContext.mock(SimpleJdbcCall.class);
mockingContext.checking(new Expectations() {
        { 
            oneOf(simpleJDBCCall).withSchemaName("test");
            will(returnValue(simpleJDBCCall));
            oneOf(simpleJDBCCall).withCatalogName("test");
            will(returnValue(simpleJDBCCall));
            oneOf(simpleJDBCCall).withProcedureName(ProcedureNames.TEST);
            will(returnValue(simpleJDBCCall));
            oneOf(simpleJDBCCall).execute(5);
            will(returnValue(testMap));
        }
person Java_Alert    schedule 08.11.2016