目前,我第一次尝试将单元测试应用于项目。出现了两个问题:
如果多个测试相互依赖,这是不好的做法吗?在下面的代码中,几个测试需要其他测试的结果为正,这是一般的最佳实践吗?
您对 SUT 所依赖的模拟对象的了解程度如何?在下面的代码中,“Router”依赖于“Route”,而“Route”又依赖于“RouteParameter”。 mock 还是不 mock ?
下面的代码是测试我的“Router”对象,该对象通过 Router::addRoute($route)
接受路由,并通过 Router::route($url) 路由 URL
.
class RouterTest extends PHPUnit_Framework_TestCase {
protected function createSimpleRoute() {
$route = new \TNT\Core\Models\Route();
$route->alias = 'alias';
$route->route = 'route';
$route->parameters = array();
return $route;
}
protected function createAlphanumericRoute() {
$route = new \TNT\Core\Models\Route();
$route->alias = 'alias';
$route->route = 'test/[id]-[name]';
$parameterId = new \TNT\Core\Models\RouteParameter();
$parameterId->alias = 'id';
$parameterId->expression = '[0-9]+';
$parameterName = new \TNT\Core\Models\RouteParameter();
$parameterName->alias = 'name';
$parameterName->expression = '[a-zA-Z0-9-]+';
$route->parameters = array($parameterId, $parameterName);
return $route;
}
public function testFilledAfterAdd() {
$router = new \TNT\Core\Helpers\Router();
$router->addRoute($this->createSimpleRoute());
$routes = $router->getAllRoutes();
$this->assertEquals(count($routes), 1);
$this->assertEquals($routes[0], $this->createSimpleRoute());
return $router;
}
/**
* @depends testFilledAfterAdd
*/
public function testOverwriteExistingRoute($router) {
$router->addRoute(clone $this->createSimpleRoute());
$this->assertEquals(count($router->getAllRoutes()), 1);
}
/**
* @depends testFilledAfterAdd
*/
public function testSimpleRouting($router) {
$this->assertEquals($router->route('route'), $this->createSimpleRoute());
}
/**
* @depends testFilledAfterAdd
*/
public function testAlphanumericRouting($router) {
$router->addRoute($this->createAlphanumericRoute());
$found = $router->route('test/123-Blaat-and-Blaat');
$data = array('id' => 123, 'name' => 'Blaat-and-Blaat');
$this->assertEquals($found->data, $data);
}
/**
* @expectedException TNT\Core\Exceptions\RouteNotFoundException
*/
public function testNonExistingRoute() {
$router = new \TNT\Core\Helpers\Router();
$router->route('not_a_route');
}
}
最佳答案
1)是的,如果测试相互依赖,这绝对是一种不好的做法。
单元测试应该以这样的方式构建:当它失败时,它会立即指向代码中的特定区域。良好的单元测试将减少您花费在调试上的时间。如果测试相互依赖,您将失去这个好处,因为您无法判断代码中的哪个错误导致测试失败。此外,这也是一场维护噩梦。如果您的“共享测试”发生变化怎么办,那么您就必须更改所有依赖的测试。
Here您可以找到一些关于如何解决交互测试问题的良好指导(整本 xUnit 测试模式书是必读的!)
2)单元测试是关于测试尽可能小的事情。
假设您有一个闹钟(C# 代码):
public class AlarmClock
{
public AlarmClock()
{
SatelliteSyncService = new SatelliteSyncService();
HardwareClient = new HardwareClient();
}
public void Execute()
{
HardwareClient.DisplayTime = SatelliteSyncService.GetTime();
// Check for active alarms
// ....
}
}
这是不可测试的。您将需要真正的卫星连接和硬件客户端来检查是否设置了正确的时间。
但是,以下内容将让您模拟 hardwareClient 和 SatelliteSyncService。
public AlarmClock(IHardwareClient hardwareClient, ISatelliteSyncService satelliteSyncService)
{
SatelliteSyncService = satelliteSyncService;
HardwareClient = hardwareClient;
}
但是,您永远不应该模拟您实际正在测试的对象(听起来很合乎逻辑,但有时我看到它正在发生)。
那么,你应该在 mock 方面走多远。您应该模拟您的测试所依赖的类的所有内容。通过这种方式,您可以完全隔离地测试您的类。而且您可以控制依赖项的结果,以便确保您的 SUT 将通过所有代码路径。
例如,让 SatelliteSyncService
抛出异常,让它返回无效时间,当然让它返回正确的时间,然后在特定时刻,这样您就可以测试闹钟是否在正确的时机。
用于创建您的路线测试数据。考虑使用Builder Pattern.这将帮助您仅设置测试成功所需的条件。它将使您的测试更具表现力并且更易于其他人阅读。它还会降低您的测试维护,因为您的依赖项较少。
我写了一个blog post关于扩展此处提到的想法的单元测试。它使用 C# 来解释概念,但它适用于所有语言。
关于php - 单元测试中的依赖关系,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8908305/