下面的代码值得单元测试吗?

时间:2011-01-20 11:07:55

标签: delphi unit-testing tdd

我是单元测试的新手。我不知道是否值得对下面的代码进行单元测试。这是用Delphi编写的示例方法:

function TCoreAudio.CreateAudioClient: IAudioClient;
var
  MMDeviceEnumerator: IMMDeviceEnumerator;
  MMDevice: IMMDevice;
  MixFormat: PWaveFormatEx;
  AudioClient: IAudioClient;
  HR: HResult;
begin
  Result := nil;

  if CheckWin32Version(6, 0) then // The Core Audio APIs were introduced in Windows Vista.
  begin
    HR := GetInstance().CoCreateInstance(CLSID_MMDeviceEnumerator, nil, CLSCTX_ALL,
      IMMDeviceEnumerator, MMDeviceEnumerator);
    if Failed(HR) then
      Exit;
    HR := MMDeviceEnumerator.GetDefaultAudioEndpoint(eRender, eConsole, MMDevice);
    if Failed(HR) then
      Exit;
    HR := MMDevice.Activate(IAudioClient, CLSCTX_ALL, nil, AudioClient);
    if Failed(HR) then
      Exit;
    HR := AudioClient.GetMixFormat(MixFormat);
    if Failed(HR) then
      Exit;
    HR := AudioClient.Initialize(AUDCLNT_SHAREMODE_SHARED, 0, 0, 0, MixFormat, nil);
    CoTaskMemFree(MixFormat);
    if Failed(HR) then
      Exit;

    Result := AudioClient;
  end;
end;

该方法值得单元测试吗?如果是,它的哪些部分需要测试?

谢谢。

4 个答案:

答案 0 :(得分:6)

您面临的问题是如何测试它而不是它是否应该进行测试。

这是许多COM调用的包装,它可能由于许多不同的原因而失败。那些可能的COM故障条件是测试此例程的最重要方面。但是你不能轻易激起COM例程失败。为了测试这些COM故障模式,你需要使用一个模拟器,这是你从哪里跳过。

答案 1 :(得分:3)

单元测试通常是自下而上的方法。因此,您将开始对函数中使用的类进行单元测试。在确保单元测试涵盖所有这些类之后,您可以为函数CreateAudioClient创建单元测试。这个函数的单元测试可能非常简单,如下所示:

AudioClient := CreateAudioClient;
CheckNotNil (AudioClient);

请注意,您通常会对类的接口进行单元测试,而不是对函数或过程的主体进行单元测试。

希望有所帮助。

这个问题是否值得,取决于许多因素:

  • 为它构建单元测试有多容易?会有多大的努力?
  • 这部分有多重要?根据这部分的重要性,答案可能总是“是的,绝对值得单元测试” - 或者不是。
  • 改变的可能性有多大?如果您认为此方法可能在将来某处发生变化,那么添加单元测试可以避免以后引入错误。

答案 2 :(得分:3)

当单元测试作为应用程序和第三方API(甚至系统API)之间接口的类时,您要测试该类是否正确调用并响应API。如果没有某种方法来感知传递给API的内容并返回适当的响应,则无法做到这一点。

在您的情况下,您正在进行一系列调用以获取IAudioClient。我会说你做得太多了。函数中的多个条件是一个条件太多(我想我只是把自己与那个混淆了)。我会把它分成几个你可以单独测试的函数。

function TCoreAudio.CreateAudioClient: IAudioClient;
var
  MMDeviceEnumerator: IMMDeviceEnumerator;
  MMDevice: IMMDevice;
  MixFormat: PWaveFormatEx;
  AudioClient: IAudioClient;
begin
  Result := nil;
  if IsVista then
    try
      MMDeviceEnumerator := GetMMDeviceEumerator;
      MMDevice := GetMMDevice(MMDeviceEnumerator);
      AudioClient := GetAudioClient(MMDevice);
      MixFormat := GetMixFormat(AudioClient);
      InitializeAudioClient(AudioClient, MixFormat);
      Result := AudioClient;
    except
      //Handle exception
    end;
end;

function TCoreAudio.IsVista: boolean;
begin
  Result := CheckWin32Version(6, 0);
end;

function TCoreAudio.GetMMDeviceEnumerator: IMMDeviceEnumerator;
begin
    HR := GetInstance().CoCreateInstance(CLSID_MMDeviceEnumerator, nil, CLSCTX_ALL,
      IMMDeviceEnumerator, Result);
    if Failed(HR) then
      raise Exception.Create('Failed to create device enumerator');
end;

function TCoreAudio.GetMMDevice(ADeviceEnumerator: IMMDeviceEnumerator): IMMDevice;
begin
    HR := MMDeviceEnumerator.GetDefaultAudioEndpoint(eRender, eConsole, Result);
    if Failed(HR) then
      raise Exception.Create('Failed to retrieve device');
end;

function TCoreAudio.GetAudioClient(ADevice: IMMDevice): IAudioClient;
begin 
    HR := MMDevice.Activate(IAudioClient, CLSCTX_ALL, nil, Result);
    if Failed(HR) then
      raise Exception.Create('Failed to retrieve audio client');
end;

function TCoreAudio.GetMixFormat(AAudioClient: IAudioClient): PWaveFormatEx
begin
    HR := AudioClient.GetMixFormat(Result);
    if Failed(HR) then
      raise Exception.Create('Failed to retrieve mix format');
end;

procedure TCoreAudio.InitializeAudioClient(AAudioClient: IAudioClient, AMixFormat: PWaveFormatEx);
begin
    HR := AudioClient.Initialize(AUDCLNT_SHAREMODE_SHARED, 0, 0, 0, AMixFormat, nil);
    CoTaskMemFree(MixFormat);
    if Failed(HR) then
      raise Exception.Create('Audio client failed to initialize');
end;

现在,您可以为每个函数提供模拟/伪/存根,确保使用适当的参数调用API并强制失败条件以确保您的生产代码正确处理它们。

您无需询问是否应测试生产代码。答案永远是肯定的。 (警告:无耻的自我推销)我最近在blog上写了这篇文章。有时即使是最无害的所有语句,赋值语句也无法正常工作。

实际上,现在它已经打破了它的开始看起来像一个新的创造阶级只是渴望爆发。

答案 3 :(得分:0)

这真的取决于你认为对这种方法做出改变的频率......如果你已经对它的单位进行了一些测试,那么是的,否则它真的取决于你,但我不确定它是什么一个好主意只能从整个单元测试这个方法,因为它从其他单元调用其他方法。

最重要的是,这取决于你如何选择这样做,最好的方法是在整个项目中添加测试,否则我在“部分测试”中看不到任何价值。