How can I test private methods with DUnit?

风格不统一 提交于 2019-11-30 11:13:55

You don't need to make them public. Protected will do. Then you can subtype the class for unit testing and surface the protected methods. Example:

type
  TAuth = class(TDataModule)
  protected
    procedure MethodIWantToUnitTest;
  public
    procedure PublicMethod;
  end;

Now you can subtype it for your unit test:

interface

uses
  TestFramework, Classes, AuthDM;

type
  // Test methods for class TAuthDM
  TestAuthDM = class(TTestCase)
     // stuff
  end;

  TAuthDMTester = class(TAuthDM)
  public
    procedure MethodIWantToUnitTestMadePublic;
  end;

implementation

procedure TAuthDMTester.MethodIWantToUnitTestMadePublic;
begin
  MethodIWantToUnitTest;
end;

However, if the methods you want to unit test are doing things so intimately with the data module that it is not safe to have them anything but private, then you should really consider refactoring the methods in order to segregate the code which needs to be unit tested and the code which accesses the innards of the data module.

It is a little hacky, but I think this is the simplest and clearer approach. Use this conditional compilation directive:

  {$IfNDef TEST}
  private
  {$EndIf}

Your unit test project must define TEST in project → conditional defines. Without a visibility specification, they become published.

Beware: if the private visibility isn't the first one in the class declaration, it will get the previous definition. A safer way, but more verbose and less clear, would be:

  private
  {$IfDef TEST}
  public
  {$EndIf}

This has a lot of advantages over the subclassing or other approaches:

  • No extra complexity: no extra classes in your code.
  • Nobody can "mistakenly" subclass and override your class: you preserve your architecture.
  • When you say a method is protected, you somewhat expect that it will be overridden. You are telling this for who is reading your code. A protected method that shouldn't be overridden will confuse your code readers, breaking my first programming principle: "Code must be written to be read by other human beings."
  • DUnit is in their own unit, not included everywhere.
  • You don't touch messy RTTI.

I think it is a clearer solution, and better than the selected answer.

When I use this, I also configure the test project to put the build objects in a different directory of the main project. This prevents the binaries with the TEST directive to mix with the other code.

I recommend the "XUnit Test Patterns" book by Gerard Meszaros:

Test-Specific Subclass

Question: How can we make code testable when we need to access private state of the SUT?

Answer: Add methods that expose the state or behavior needed by the test to a subclass of the SUT.

... If the system under test (SUT) was not designed specifically to be testable, we may find that the test cannot get access to state that it must initialize or verify at some point in the test.

The article also explains when to use it and which risks it carries.

Put the DUnit code within your unit. You can then access anything you like.

In general, when I get in this situation, I often realize that I'm violating the single responsibility principle. Of course I know nothing about your specific case, but MAYBE, that private methods should be in their own class. The TAuth would than have a reference to this new class in it's private section.

mjn

With Extended RTTI (Delphi 2010 and newer), invoking private methods via RTTI is another option. This solution is also the top-rated answer in How do I test a class that has private methods, fields or inner classes?

{$IFNDEF UNITEST}
private
{$ENDIF}

Simple solution, which hardly is a hack. I frequently need to test private methods and this technique adds as little complication as possible.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!