C++ 在设计调用外部API的类时,测试实现是否正确?

C++ 在设计调用外部API的类时,测试实现是否正确?,c++,unit-testing,mocking,tdd,C++,Unit Testing,Mocking,Tdd,我从一些原型代码开始,大致如下: // Omitted most definitions, return values checks, etc. // The real code is much bigger and uglier. serverId = socket(AD_INET, SOCK_STREAM, PROTO_ANY); setsockopt(serverId, SOL_SOCKET, SO_REUSEADDR, &reuseAddrOk, sizeof(int)); bi

我从一些原型代码开始,大致如下:

// Omitted most definitions, return values checks, etc.
// The real code is much bigger and uglier.
serverId = socket(AD_INET, SOCK_STREAM, PROTO_ANY);
setsockopt(serverId, SOL_SOCKET, SO_REUSEADDR, &reuseAddrOk, sizeof(int));
bind(serverId, &serverAddress, sizeof(serverAddress));
listen(serverId, waitQueueSize);
clientId = accept(serverId, &clientAddress, sizeof(clientAddress));
read(clientId, clientBuffer, charsToRead);
现在,我想在非常简单的类中重构这个代码提取功能(现在不想让事情变得非常通用…YAGNI)。这就是我想到的界面:

SocketServer server = SocketServer(parameters);
// SocketServer knows how to create a SocketClient...abstract factories, dependency injection, etc. etc.
SocketClient client = server.accept();
string clientMessage = client.read();
client.write(serverMessage);
例如,
SocketServer
类封装了用于创建新套接字服务器的所有样板文件:

SocketServer server = SocketServer(parameters);
然后,由于这需要调用系统API,我需要模拟它:

SocketServer server = SocketServer(systemAPI, parameters);
现在,测试此代码是否正确意味着什么?它不会产生任何我可以检查的输出(或者更好,我做所有这些都是为了封装输出,比如文件描述符)。我可以检查是否调用了模拟API的正确方法,如:

testSocketCalledWithCorrectParameters() {
    systemAPI = mock(SystemAPI).expect(once()).method("socket").with(
        SystemAPI.AF_INET,
        SystemAPI.SOCK_STREAM,
        SystemAPI.PROTO_AUTO
    );
    ServerSocket(systemAPI, parameters);
}
在这种情况下,我应该依赖于测试一个实现而不是一个接口吗?是被迫测试一个实现,而不是一个设计糟糕的接口

我能想到的所有其他测试都设定了对实现的期望:

testServerSocketIsCreatedWithCorrectDescriptor() {
    dummyDescriptor = 10;
    systemAPI = mock(SystemAPI).when("socket").return(dummyDescriptor);
    server = SocketServer(systemAPI, parameters);
    assertEquals(dummyDescriptor, server.descriptor);
}

/**
 * @expected SocketException
 */
testThrowsExceptionIfErrorCreatingSocket() {
    systemAPI = mock(SystemAPI).when("socket").return(SystemAPI.RETURN_ERROR);
    SocketServer(systemAPI, parameters);
}

// etc.

然后,我是否也应该为
socketAPI
编写单元测试,或者我是否应该想当然地认为它将是一个非常愚蠢的包装类,只需要将调用委托给外部API(因此它不需要测试)?

让我试着逐一回答

1。测试呼叫属性

这种测试只有文档价值(这是调用API的方式)。检查,如果它真的值得->也许代码中的语句足够清楚

2。testServerSocketIsCreatedWithCorrectDescriptor

对我来说,这种测试更有价值。我几乎总是编写一个testCreation,其中显示:输入参数和断言属性,例如,一辆汽车需要车轮进行构造,然后默认有4个车轮、红色和一个方向盘

3。TestThrowExceptionFerrorCreatingSocket测试异常

这种测试是最有价值的。它们定义并保护您的班级在不同情况下的行为。这里我缺少一个断言,例如抛出的异常类型。 写尽可能多的此类测试

4。测试系统API

不可以。不要在单元测试中测试系统行为。特别是不要嘲笑。 这是模块或端到端测试的一部分


希望这有帮助。

让我试着逐一回答

1。测试呼叫属性

这种测试只有文档价值(这是调用API的方式)。检查,如果它真的值得->也许代码中的语句足够清楚

2。testServerSocketIsCreatedWithCorrectDescriptor

对我来说,这种测试更有价值。我几乎总是编写一个testCreation,其中显示:输入参数和断言属性,例如,一辆汽车需要车轮进行构造,然后默认有4个车轮、红色和一个方向盘

3。TestThrowExceptionFerrorCreatingSocket测试异常

这种测试是最有价值的。它们定义并保护您的班级在不同情况下的行为。这里我缺少一个断言,例如抛出的异常类型。 写尽可能多的此类测试

4。测试系统API

不可以。不要在单元测试中测试系统行为。特别是不要嘲笑。 这是模块或端到端测试的一部分


希望这有帮助。

如果提供
Z
X
应该执行
Y
,您将如何测试它?您将调用
X
,提供
Y
,并测试
Z
的结果。如果你不能,你需要编写基本的基础设施,这样做。“测试一个实现而不是一个接口”-测试一个接口意味着什么?@OliverCharlesworth如果我有一个函数
add(a,b)
我可以测试
add(1,2)==3
add(10,1)==11
,不管这个函数是如何实现的。@EugeneSh。“允许您这样做的基本基础设施”指的是测试框架功能,如“预期此方法将被调用n次”,或SUT的不同设计?如果提供
Z
X
应该执行
Y
,您将如何测试它?您将调用
X
,提供
Y
,并测试
Z
的结果。如果你不能,你需要编写基本的基础设施,这样做。“测试一个实现而不是一个接口”-测试一个接口意味着什么?@OliverCharlesworth如果我有一个函数
add(a,b)
我可以测试
add(1,2)==3
add(10,1)==11
,不管这个函数是如何实现的。@EugeneSh。“允许您这样做的基本基础设施”指的是测试框架功能,如“预期此方法将被调用n次”,或SUT的不同设计?谢谢您的回答。“测试呼叫属性”是什么意思?测试模拟的某些方法被调用了一定的次数?在案例2中,您认为只出于测试目的而公开通常隐藏的属性(作为实现的一部分)值得吗?同意更多地关注测试异常情况。是的,我的意思是测试在模拟中调用某些方法,以及传递哪些参数。在案例2中:您的意思是仅出于测试目的显示server.descriptor,这不是一个好主意。我的小贴士:听你的测试,它们经常暴露出严重的设计缺陷。如果测试很难而且令人毛骨悚然-那是你的设计,而不是测试的错误-谢谢你的回答。“测试呼叫属性”是什么意思?测试模拟的某些方法被调用了一定的次数?在案例2中,您认为公开正常的属性值得吗