2014-09-06 168 views
2

在我的应用程序中,我们的设计中有两层:API和操作。单元测试 - 我应该如何测试这种设计?

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

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

2. APIs由用户执行:它们接收参数,然后根据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会怎么样?那么没有测试会失败。

非常感谢。

回答

2

您的设计很常见(也是如此),所以我有点惊讶这个问题不断出现。

有两种类型的测试,你需要在这里:

  1. 集成测试 - 确保与该API调用启动,并与业务层做它的工作结束时,流程工作正常
  2. 单元测试 - 测试每个API层中的类,以及作为操作层的

集成测试是相当自解释性的(如果没有,让我知道,我会详细说明),所以我猜你指的是单元测试。两个不同的图层需要进行不同的测试。

的操作层:

在这里,我们尝试检查,这样做实际工作的类工作。这意味着你应该实例化你正在测试的类,为它提供输入,并检查它提供的输出是否符合你的期望。

假设你有一类这个排序:

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层:

在这里,我们试图检查类使用权来自操作层的类以正确的顺序执行正确的操作。为了做到这一点,你应该使用mock,并使用嘲笑的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); 
    } 

} 
+0

为什么没有评论投票呢? – eitanfar 2014-10-12 05:41:25

1

一般来说,好像你需要两层测试。

首先,您需要测试基本构建块 - 操作。例如,你应该用一个有效的标记测试authenticateUserOperation,使用一个无效的标记和一个NULL

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

2

我觉得这里有矛盾。 看来你有

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

从这一点,我会说你需要自从 开始测试*操作本身可能正确执行。仅仅进行API测试可能会导致多个客户端/测试失败,造成单个错误 *即使操作测试可能通过,API也可能不会调用所需的(和已实现的)操作。

所以:

  1. 试验操作 - 验证所述可重复使用块
  2. 试验的API - 验证逻辑内部到的API +检查是否调用正确的操作(可以使用嘲笑)。您唐't复制/测试这里的操作内部的逻辑。
  3. 我通常不会测试依赖注入 - 因为不太可能有人伪造合作者。 (除非是渗透测试仪)。尽管如此,你也可以为它写一个测试。
1

据我了解你的第一个问题,你不可能只通过单元测试来覆盖这个(就像你'标记'它)。也许你必须考虑使用Pairwise testing为你的测试逻辑创建。这将支持对操作& API之间的交互的依赖性。

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

enter image description here

但我不认为这是你正在寻找的唯一案例。所以我可以建议你在测试代码中包含Decorator design pattern,并且它的概念为API-操作关系动态地为测试对象附加额外的职责。

1

您应该执行两套测试在这里。单元测试和集成测试

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

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

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