2011-02-05 53 views
2

我在测试方面很新,所以请让我知道,如果我在任何时候完全错误的方向进行。话虽如此,假设我想测试下面的函数foo。如何测试输出依赖于另一个函数的函数?

int foo(int i) { 
    //Lots of code here 

    i = bar(); 

    //Some more changes to i happen here, conditional on what bar returned 

    return i; 
} 

在这个例子中,foo和bar都是自己编写的函数,我已经测试过了。

由于foo的输出是以bar的输出为条件的,我假设为了测试foo,我需要创建一个模拟条。为了做到这一点,并假设bar的定义保存在与foo不同的源文件中,我可以创建一个新的源文件,包含该文件,而不是发现bar的实际定义的源文件,并将模拟在该文件中的酒吧。

int bar(void) { 
    return HARD_CODED_VALUE; 
} 

然而,存在2个问题与该方法:

1),会发生什么,如果杆返回多个值(例如作为错误代码或实际值)和I需要确保FOO正确地反应对于每种可能性?我无法为栏创建多个定义。我曾经想过的一个想法是在bar中创建一个静态int,然后每次调用bar时都会增加它。然后我只是有一个条件这个int,多次调用栏,从而返回多个值。不过,我不确定是否引入更复杂的逻辑成模拟功能是很好的做法,或者有更好的方式来实现这一点:如果酒吧是相同的源文件中foo

int bar(void) { 
    static int i = 0; 

    i++; 
    if(i == 1) { 
    return HARD_CODED_VALUE_1 
    } 
    else if(i == 2) { 
    return HARD_CODED_VALUE_2 
    } 
    else { 
    fprintf(stderr, "You called bar too many times\n"); 
    exit(1); 
    } 
} 

2)会发生什么?我不能重新定义酒吧,也不能在不改变我的源代码的情况下分开foo和bar,这将是一个真正的痛苦。

+1

foo函数可以在没有模拟条功能的情况下进行单元测试吗?如果是这样,只需创建一个涵盖foo和bar的组合功能的单元测试。 – 2011-02-05 12:46:50

回答

3

那么,围绕这个问题有几种解决方法。

  • 你可以使用预处理挂钩换出bar()UNITTEST标志设置:

    #ifdef UNITTEST 
    return mockBar(); 
    #else 
    return bar(); 
    #endif 
    
  • 你可以模拟依赖注入,并且需要一个指向bar()作为参数传递给函数。我并不是说这在实践中是一个好主意,但你可以做到。

    void foo(void (*bar)()) { 
    

我敢肯定还有其他的,但是这只是2是来到了我的头顶......

+0

预处理器挂钩对我来说是最好的方式,我想。 – 2011-03-31 22:09:35

0

有用于嘲讽库。这些库通常找到解决这些问题的方法。先进的库将允许您在测试中配置什么bar()应该在测试的每个点返回。

我不确定他们是否会处理bar()foo()位于同一个源文件中的情况,但他们可能会这样做。在这种情况下,我认为bar()foo()是同一单元的一部分,但这是一个完全不同的论点。

这里是一个来自GoogleMock的C++代码片段(source)。它创建一个模拟乌龟对象,画家应该调用一次PenDown方法,当它执行PenDown方法时将返回500。如果画家不叫PenDown,那么测试就会失败。

#include "path/to/mock-turtle.h" 
#include "gmock/gmock.h" 
#include "gtest/gtest.h" 
using ::testing::AtLeast;      // #1 

TEST(PainterTest, CanDrawSomething) { 
    MockTurtle turtle;       // #2 
    EXPECT_CALL(turtle, PenDown())    // #3 
     .WillOnce(Return(500)); 

    Painter painter(&turtle);     // #4 

    EXPECT_TRUE(painter.DrawCircle(0, 0, 10)); 
}            // #5 

int main(int argc, char** argv) { 
    // The following line must be executed to initialize Google Mock 
    // (and Google Test) before running the tests. 
    ::testing::InitGoogleMock(&argc, argv); 
    return RUN_ALL_TESTS(); 
} 

当然这个特定的图书馆正在使用你可能会或可能不会做的OOP。我猜想还有其他库也用于非OOP。

+0

Heya Pace,感谢您的回复,但我只是想强调一下,我认为C和C++的测试是非常不同的,我完全使用C,根据我的问题标签。 – 2011-02-05 12:55:42

1

你想要做的是用一个存根返回已知值来替代被调用的函数。当使用外部依赖关系(即数据库或网络代码)时也是如此。用C有两个可使用的“接缝”,允许你执行替代(使用的术语从修改代码的工作):

  • 使用预处理器命令与宏,例如更换功能体

    #ifdef TEST 
    #define bar(x) { if (x) then y; else z; } 
    #endif 
    
  • 将bar(x)移动到单独的库中,然后维护该库的两个版本。第一个是你的生产代码,第二个是包含bar(x)的测试存根的测试库。

第三种选择是使用依赖注入,通过重构所述杆(x)的呼叫到一个函数指针参数作为ircmaxell证明。

void foo(void (*bar)()) 

我已经尝试了这些方法与非OO C++代码,并发现第一个是迄今为止最有用的。第二个引入了一个非常艰难的可维护性问题(相同库的多个版本和需要维护的功能),而后者显然对代码的可读性和可理解性有负面影响。

的预处理指令,在另一方面,可以非常局部化和备用定义可以被分离成如果测试了仅包含一个头文件,即

#ifdef TEST 
    #include "subsystem_unittest.h" 
#endif 
0

是bar()的尴尬依赖性?使用bar的实际实现对foo进行单元测试有问题吗?

如果没有,那么我没有看到问题。你不必嘲笑一切。

相关问题