如何避免课堂自用

ahm*_*hri 9 java unit-testing design-patterns mocking mockito

我有以下课程:

public class MyClass
{

    public void deleteOrganization(Organization organization)
    {
        /*Delete organization*/

        /*Delete related users*/
        for (User user : organization.getUsers()) {
            deleteUser(user);
        }
    }

    public void deleteUser(User user)
    {
        /*Delete user logic*/
    }
}
Run Code Online (Sandbox Code Playgroud)

此类表示自我使用,因为其公共方法deleteOrganization使用其他公共方法deleteUser.在我的例子中,这个类是遗留代码,我开始添加单元测试.所以我首先针对第一种方法添加了单元测试deleteOrganization,最后确定该测试已经扩展到也测试了该deleteUser方法.

问题

问题是这个测试不再是孤立的(它应该只测试deleteOrganization方法).我不得不处理与deleteUser方法相关的不同条件,以便通过测试,这大大增加了测试的复杂性.

解决方案是监视测试中的类和存根deleteUser 方法:

@Test
public void shouldDeleteOrganization()
{
    MyClass spy = spy(new MyClass());

    // avoid invoking the method
    doNothing().when(spy).deleteUser(any(User.class));

    // invoke method under test
    spy.deleteOrganization(new Organization());
}
Run Code Online (Sandbox Code Playgroud)

新问题

虽然之前的解决方案解决了这个问题,但不推荐使用该spy方法的javadoc :

像往常一样,您将阅读部分模拟警告:面向对象编程通过将复杂性划分为单独的特定SRPy对象来解决复杂性问题.部分模拟如何适应这种范式?嗯,它只是没有...部分模拟通常意味着复杂性已被移动到同一对象上的不同方法.在大多数情况下,这不是您想要设计应用程序的方式.

deleteOrganization方法的复杂性已经转移到deleteUser方法,这是由类自用引起的.除了In most cases, this is not the way you want to design your application声明之外,不建议使用此解决方案的事实表明存在代码异味,并且确实需要重构来改进此代码.

如何删除这个自用?是否有可以应用的设计模式或重构技术?

dka*_*zel 1

合同里是不是deleteOrganization()User那个组织里的所有s也会被删除?
如果确实如此,那么您必须至少保留部分删除用户逻辑,deleteOrganization()因为您的类的客户端可能依赖于该功能。

在讨论选项之前,我还要指出,每个删除方法都是非最终的public,并且类和方法都不是最终的。这将使某人扩展该类并覆盖可能危险的方法。

解决方案 1 - 删除组织仍需删除其用户

考虑到我对首要危险的评论,我们删除了实际删除用户的部分deleteUser()。我假设公共方法deleteUser会执行额外的验证,也许还会执行deleteOrganization().

public void deleteOrganization(Organization organization)
{
    /*Delete organization*/

    /*Delete related users*/
    for (User user : organization.getUsers()) {
        privateDeleteUser(user);
    }
}

private void privateDeleteUser(User user){
   //actual delete logic here, without anything delete organization doesn't need
}


public void deleteUser(User user)
{
    //do validation
   privateDeleteUser(user);
  //perform any extra business locic
}
Run Code Online (Sandbox Code Playgroud)

deleteUser()这会重新使用执行删除操作的实际代码,并避免子类更改为不同行为的危险。

解决方案 2 - 不需要在 deleteOrganization() 方法中删除用户

如果我们不需要一直从组织中删除用户,我们可以从deleteOrganization() 中删除这部分代码。在我们也需要删除用户的少数地方,我们可以像现在的deleteOrganization 一样执行循环。由于它使用公共方法,因此只有任何客户端都可以调用它们。我们也可以将逻辑提取到一个Service类中。

public void DeleteOrganizationService(){

   private MyClass myClass;
   ...

   public void delete(Organization organization)
   {
       myClass.deleteOrganization(organization);
        /*Delete related users*/
        for (User user : organization.getUsers()) {
            myClass.deleteUser(user);
        }
    }

 }
Run Code Online (Sandbox Code Playgroud)

这是更新的MyClass

public void deleteOrganization(Organization organization)
{
    /*Delete organization only does not modify users*/
}

public void deleteUser(User user)
{
    /*same as before*/
}
Run Code Online (Sandbox Code Playgroud)