Bagaimana cara menguji kode yang tidak dapat disuntikkan?

13

Jadi saya memiliki kode berikut yang digunakan di seluruh sistem saya. Kami sedang menulis tes unit secara retrospektif (lebih baik terlambat daripada tidak pernah argumen saya), tapi saya tidak melihat bagaimana ini akan diuji?

public function validate($value, Constraint $constraint)
{
    $searchEntity = EmailAlertToSearchAdapter::adapt($value);

    $queryBuilder = SearcherFactory::getSearchDirector($searchEntity->getKeywords());
    $adapter = new SearchEntityToQueryAdapter($queryBuilder, $searchEntity);
    $query = $adapter->setupBuilder()->build();

    $totalCount = $this->advertType->count($query);

    if ($totalCount >= self::MAXIMUM_MATCHING_ADS) {
        $this->context->addViolation(
            $constraint->message
        );
    }
}

Secara konseptual ini harus berlaku untuk bahasa apa pun, tapi saya menggunakan PHP. Kode hanya membangun objek permintaan ElasticSearch, berdasarkan pada Searchobjek, yang pada gilirannya dibangun dari suatu EmailAlertobjek. Ini Searchdan EmailAlertitu hanya POPO.

Masalah saya adalah bahwa saya tidak melihat bagaimana saya bisa mengejek keluar SearcherFactory(yang menggunakan metode statis), maupun SearchEntityToQueryAdapter, yang membutuhkan hasil dari SearcherFactory::getSearchDirector dan yang Searchmisalnya. Bagaimana cara menyuntikkan sesuatu yang dibangun dari hasil dalam suatu metode? Mungkin ada beberapa pola desain yang tidak saya sadari?

Terima kasih atas bantuannya!

iLikeBreakfast
sumber
@DocBrown sedang digunakan di dalam $this->context->addViolationpanggilan, di dalam if.
iLikeBreakfast
1
Pasti buta, maaf.
Doc Brown
Jadi semua :: adalah statika?
Ewan
Ya, di PHP ::untuk metode statis.
Andy
@ Ewan ya, ::memanggil metode statis di kelas.
iLikeBreakfast

Jawaban:

11

Ada beberapa kemungkinan, bagaimana cara mengejek staticmetode dalam PHP, solusi terbaik yang saya gunakan adalah perpustakaan AspectMock , yang dapat ditarik melalui komposer (cara mengejek metode statis cukup dapat dipahami dari dokumentasi).

Namun, ini adalah perbaikan menit terakhir untuk masalah yang harus diperbaiki dengan cara yang berbeda.

Jika Anda masih ingin menguji unit lapisan yang bertanggung jawab untuk mengubah query, ada cara yang cukup cepat bagaimana melakukannya.

Saya mengasumsikan sekarang validatemetode ini adalah bagian dari beberapa kelas, perbaikan yang sangat cepat, yang tidak mengharuskan Anda untuk mengubah semua panggilan statis Anda untuk panggilan contoh, adalah membangun kelas yang bertindak sebagai proxy untuk metode statis Anda dan menyuntikkan proxy ini ke dalam kelas yang sebelumnya menggunakan metode statis.

class EmailAlertToSearchAdapterProxy
{
    public function adapt($value)
    {
        return EmailAlertToSearchAdapter::adapt($value);
    }
}

class SearcherFactoryProxy
{
    public function getSearchDirector(array $keywords)
    {
        return SearcherFactory::getSearchDirector($keywords);
    }
}

class ClassWithValidateMethod
{
    private $emailProxy;
    private $searcherProxy;

    public function __construct(
        EmailAlertToSearchAdapterProxy $emailProxy,
        SearcherFactoryProxy $searcherProxy
    )
    {
        $this->emailProxy = $emailProxy;
        $this->searcherProxy = $searcherProxy;
    }

    public function validate($value, Constraint $constraint)
    {
        $searchEntity = $this->emailProxy->adapt($value);

        $queryBuilder = $this->searcherProxy->getSearchDirector($searchEntity->getKeywords());
        $adapter = new SearchEntityToQueryAdapter($queryBuilder, $searchEntity);
        $query = $adapter->setupBuilder()->build();

        $totalCount = $this->advertType->count($query);

        if ($totalCount >= self::MAXIMUM_MATCHING_ADS) {
            $this->context->addViolation(
                $constraint->message
            );
        }
    }
}
Andy
sumber
Ini sempurna! Bahkan tidak memikirkan proxy. Terima kasih!
iLikeBreakfast
2
Saya percaya Michael Feather menyebut ini sebagai teknik "Wrap Static" dalam bukunya "Bekerja Efektif dengan Legacy Code".
RubberDuck
1
@RubberDuck Saya tidak sepenuhnya yakin itu disebut proxy, jujur ​​saja. Itulah yang saya sebut selama saya ingat menggunakannya, nama Pak Feather mungkin lebih cocok, saya belum membaca buku itu.
Andy
1
Kelas itu sendiri tentu saja merupakan "proxy". Teknik pemecahan ketergantungan disebut "wrap static" IIRC. Saya sangat merekomendasikan buku ini. Penuh permata seperti yang Anda berikan di sini.
RubberDuck
5
Jika pekerjaan Anda melibatkan menambahkan tes unit ke kode, maka "bekerja dengan kode lawas" adalah buku yang sangat disarankan. Definisinya tentang "kode lama" adalah "kode tanpa unit test", seluruh buku sebenarnya adalah strategi untuk menambahkan tes unit ke kode yang belum teruji.
Eterm
4

Pertama, saya akan menyarankan untuk membaginya menjadi metode yang terpisah:

public function validate($value, Constraint $constraint)
{
    $totalCount = QueryTotal($value);
    ShowMessageWhenTotalExceedsMaximum($totalCount,$constraint);
}

private function QueryTotal($value)
{
    $searchEntity = EmailAlertToSearchAdapter::adapt($value);

    $queryBuilder = SearcherFactory::getSearchDirector($searchEntity->getKeywords());
    $adapter = new SearchEntityToQueryAdapter($queryBuilder, $searchEntity);
    $query = $adapter->setupBuilder()->build();

    return $this->advertType->count($query);
}

private function ShowMessageWhenTotalExceedsMaximum($totalCount,$constraint)
{
    if ($totalCount >= self::MAXIMUM_MATCHING_ADS) {
        $this->context->addViolation(
            $constraint->message
        );
    }
}

Ini membuat Anda berada dalam situasi di mana Anda dapat mempertimbangkan untuk menjadikan kedua metode baru ini sebagai uji publik dan unit QueryTotaldan secara ShowMessageWhenTotalExceedsMaximumindividual. Opsi yang layak di sini sebenarnya bukan untuk menguji unit QueryTotalsama sekali, karena pada dasarnya Anda hanya akan menguji ElasticSearch. Menulis unit test ShowMessageWhenTotalExceedsMaximumharus mudah dan jauh lebih masuk akal, karena itu sebenarnya akan menguji logika bisnis Anda.

Namun, jika Anda lebih suka menguji "validasi" secara langsung, pertimbangkan untuk meneruskan fungsi kueri itu sendiri sebagai parameter ke "validasi" (dengan nilai default $this->QueryTotal), ini akan memungkinkan Anda untuk mengejek fungsi kueri. Saya tidak yakin apakah saya mendapatkan sintaks PHP yang benar, jadi jika saya tidak melakukannya, silakan baca ini sebagai "Pseudo code":

public function validate($value, Constraint $constraint, $queryFunc=$this->QueryTotal)
{
    $totalCount =  $queryFunc($value);
    ShowMessageWhenTotalExceedsMaximum($totalCount,$constraint);
}
Doc Brown
sumber
Saya suka ide itu, tetapi saya ingin menjaga kode lebih berorientasi objek daripada melewati metode seperti ini.
iLikeBreakfast
@ iLikeBreakfast sebenarnya pendekatan ini bagus terlepas dari hal lain. Suatu metode harus sesingkat mungkin dan melakukan satu hal dan satu hal dengan baik (Paman Bob, Kode Bersih ). Ini membuatnya lebih mudah dibaca, lebih mudah dipahami, dan lebih mudah untuk diuji.