用于测试目的的模型的重构

时间:2010-09-24 14:17:03

标签: php refactoring model phpunit doctrine-orm

我想重构我的模型,所以我可以正确地为它编写单元测试。但我有一些依赖。任何人都可以指出我正确的方向如何删除这些依赖项?

class Customer
{
    public function save(array $data, $id = NULL)
    {
        // create or update
        if (empty($id)) {
            $customer = new \Entities\Customer();
        } else {
            $customer = $this->findById((int) $id);
        }

        $birthday = new DateTime();
        list($day, $month, $year) = explode("/", $data['birthday']);
        $birthday->setDate($year, $month, $day);

        $customer->setFirstName($data['firstName']);
        $customer->setLastName($data['lastName']);
        $customer->setCompany($data['company']);
        $languageModel = new Application_Model_Language();
        $customer->setLanguage($languageModel->findById($data['language']));
        $resellerShopModel = new Application_Model_ResellerShop();
        $customer->setResellerShop($resellerShopModel->findById($data['resellerShop']));
        $customer->setEmail($data['email']);
        $customer->setPhone($data['phone']);
        $customer->setGender($data['gender']);
        $customer->setBirthday($birthday);
        $customer->setType($data['type']);
        $customerSectorModel = new Application_Model_CustomerSector();
        $customer->setSector($customerSectorModel->findById($data['sector']));
        $customerReferenceModel = new Application_Model_CustomerReference();
        $customer->setReference($customerReferenceModel->findById($data['reference']));
        if (isset($data['password'])) {
            $customer->setPassword($this->_hashPassword($data['password']));
        }

        return $customer;
    }
}

2 个答案:

答案 0 :(得分:2)

我猜你的依赖项是函数体中的构造函数调用。我不介意在单元测试中有3种方法可以替换它们:

  1. 创建一个mock library,其中实现了所有不同的类,并且运行测试包括模拟库而不是真实模块。
  2. 将外部类的一组默认参数添加到函数声明中。最后,您的新函数声明看起来像public function save(array $data, $id = NULL, $newCustomer=\Entities\Customer(), $newLangModel = Application_Model_Language, ...)。同样在函数体中,您可以使用变量来创建实际对象,例如$customer = new $newCustomer()。在测试代​​码中,您可以通过模拟类覆盖每个依赖的类。
  3. 您不为每个类添加参数,但创建两个factories:一个创建当前对象,另一个创建模拟对象。在函数内部,您只能从工厂请求新对象。

    如果有许多不同的地方需要拦截施工,这种方法很有用。如果只有一个功能需要更改,则工厂过度工程。

答案 1 :(得分:0)

回顾一下你的代码之后,看来这个类充当了\ Entities \ Customer类的工厂和存储库(这不是很明显,所以你可以考虑重命名以更明确地表达你的意图)。我也不同意这个名为save的函数,因为它只返回一个需要持久保存的对象,但这更具语义性。

使用现在的代码,我看到了需要的两个不同的测试。

1)测试您的\ Entities \ Customer类。

验证您的每个getter和setter是否正常工作,或者至少知道任何具有业务逻辑的getter和setter。例如,如果您设置了ResellerShop,请确保获得正确/有效的ResellerShow对象。 或多或少,您需要测试您的业务逻辑。获取/设置基本字段(例如姓名)并不需要他们自己的测试(除非100%的代码覆盖率是一个目标,我认为不是)。

2)测试(Factory \ Repository)类。

确保您显示的类正确创建(或失败),具体取决于传入阵列的数据。例如,如果未传入必填字段,则它应该失败,并且它不应返回客户实体对象。

这个想法是为了分离关注点。您的Entity类应该包含您的业务逻辑,并且应该与对象实例化代码分开测试。