单元测试 - 我应该如何测试这种设计?

时间:2014-09-06 18:33:55

标签: unit-testing

在我的应用程序中,我们的设计有两层:API和操作。

1. 操作实现代码的“真实”逻辑,例如:验证用户,检索书籍信息,通知用户他的书已被查看。

许多API都可以使用相同的操作。

2. API 由用户执行:他们接收参数,然后根据API的逻辑执行各种操作。

例如:ViewBookAPI:

class BookApis
{
/**
    * authenticateUserOperation, retreiveBookOperation, informUserBookViewOperation
    * are injected to this class. (Dependency Injection)
*/  
public function viewBookApi($bookId, $accessToken)
{
    $internalUserId = $this->authenticateUserOperation($accessToken);

    $book = $this->retrieveBookOperation($bookId, $internalUserId);

    $this->informUserBookWasViewedOperation($book->getOwnerUserId(), $bookId);

        return $book->getContent();
    }
}

我该如何测试这个设计?

1.如果我测试API,那么我将不得不对使用相同操作的API重复相同的测试。

2.如果我测试操作,我所要做的就是验证API是否正确使用了这些操作。

但是如果将错误的对象注入API会怎样?那么没有测试会失败。

非常感谢。

5 个答案:

答案 0 :(得分:2)

我在这里感到矛盾。 好像你有

  • 操作=可重复使用的逻辑块
  • API =委托操作+调用
  • 操作的脚本

从那以后,我会说你需要测试两者 *操作本身可能正确实现。只是进行API测试可能会导致单个错误的多个客户端/测试失败 * API可能不会调用必需(和实现)操作......即使操作测试可能会通过。

所以:

  1. 测试操作 - 验证可重用块
  2. 测试API - 验证API内部的逻辑+检查是否调用了正确的操作(可以使用模拟)。你重复/测试操作中的逻辑。
  3. 我通常不测试依赖注入 - 因为有人假装合作者不太可能。 (除非它是一个渗透测试员)。你也可以写一个测试。

答案 1 :(得分:2)

你的设计非常普遍(理所当然),所以我对这个问题不断出现感到有些惊讶。

您需要两种类型的测试:

  1. 集成测试 - 确保以API调用开始并以Operations层执行其作业结束的流程正常运行
  2. 单元测试 - 测试API层的每个类以及操作层
  3. 集成测试非常自我解释(如果没有,请告诉我,我会详细说明),所以我猜你是指单元测试。需要对两个不同的层进行不同的测试。

    操作层:

    在这里,您要检查执行实际工作的类是否正常工作。这意味着您应该实例化您正在测试的类,输入它并检查它提供的输出是否符合您的期望。

    假设您有这类课程:

    public class OperationA {
        public int multiply(int x, int y) {
            return x * y;
        }
    }
    

    检查它是否符合您的预期意味着编写一个测试,例如(测试用例本身只是一个例子,不要太认真):

    public class OperationATest {
        @Test
        public void testMultiplyZeroByAnyNumberResultsInZero() {
            OperationA op = new OperationA();
    
            assertEquals(0, op.multiply(0, 0));
            assertEquals(0, op.multiply(10, 0));
            assertEquals(0, op.multiply(-10, 0));
            ...
        }
    
        @Test
        public void testMultiplyNegativeByNegativeResultsInPositive() {
            ...
        }
    
        ...
    }
    

    API层:

    在这里,您尝试检查类是否正在使用Operations层中的正确类,按正确的顺序执行正确的操作。为此,您应该使用模拟,并使用模拟的verify操作。

    假设您有这类课程:

    public class API_A {
    
        private OperationA op;
    
        public API_A(OperationA op) {
            this.op = op;
        }
    
        public int multiplyTwice(int a, int b, int c) {
            int x = op.multiply(a, b);
            int y = op.multiply(x, c);
    
            return y;
        }
    }
    

    检查它是否符合您的预期(使用Mockito语法)编写测试,例如:

    public class API_A_Test {
        @Test
        public void testMultiplyTwiceMultipliesTheFirstNumberByTheSecondAndThenByTheThird() {
            OperationA op = mock(OperationA.class); 
            when(op.multiply(12, -5)).thenReturn(60);
            API_A api = new API_A(op);
    
            api.multiply(12, -5, 0);
    
            verify(op).multiply(12, -5);
            verify(op).multiply(-60, 0);
        }
    
    }
    

答案 2 :(得分:1)

一般来说,您似乎需要两层测试。

首先,您需要测试基本构建块 - 操作。例如,您应该使用有效令牌,使用无效令牌和authenticateUserOperation来测试NULL

一旦您测试了所有操作,您就可以继续测试API的逻辑。这里的想法不是将测试代码加倍,而是仅测试 业务逻辑。这可以通过具有已知行为的mockinginjecting操作来实现,并检查API如何处理它们。 例如,您可以创建一个总是失败的模拟authenticateUserOperation,并在使用它时测试viewBookApi返回NULL(或抛出异常)。这样,您只测试API如何处理操作结果而不重复测试。

答案 3 :(得分:1)

据我了解您的第一个问题,您无法通过单元测试(例如您标记为'它)来解决此问题。也许您必须考虑使用Pairwise testing来创建测试逻辑。这将支持对运营和运营之间的交互的依赖。的API

在涉及DI之后,将错误对象注入API ,这使得您的单元测试更容易一些。由于组件松散耦合,您可以使用虚拟模拟类来充当所需组件。通过这样做 - 您的代码将非常灵活。也许这个图表可以帮助你:

enter image description here

但我不认为这是你正在寻找的唯一案例。因此,我可以建议您在测试代码中包含Decorator design pattern,并通过它的概念为API-Operations关系动态地附加测试对象的其他职责。

答案 4 :(得分:1)

你应该在这里进行两组测试。单元测试和集成测试

1)单元测试 - 应为每个操作方法编写这组测试,以确保特定方法按预期工作。现在方法可以有不同的路径(if else / exception)。您应该编写多个单元测试来验证每个路径。由于它是一个单元测试,你应该模拟所有外部调用(调用发生在其他类中),这将确保你在该函数中完成的所有事情都表现正常。由于你在api类中没有任何逻辑,所以不需要为它们编写单元测试

2)集成测试 - 编写集成测试以进行实际的api调用。这些测试将验证您的端到端场景(api,操作)。

如果你有任何持久性/存储库类,你也应该为它们编写单独的测试。