GUI testing with DUnit
I don’t how I didn’t know this until now but it turns out there’s a whole section of DUnit designed to help you test GUI elements. What follows is small write up about how to do it. Read more…
I don’t how I didn’t know this until now but it turns out there’s a whole section of DUnit designed to help you test GUI elements. What follows is small write up about how to do it. Read more…
Craig Stuntz recently posed the question of how to build hedges into our products and processes.
If I understand the problem correctly what we want is a way to mitigate the risk of choosing the wrong technology or technique. This is to say if we do choose the wrong technology or technique (for whatever reason) how do we recover and change direction with a minimal amount of fuss and cost.
I think the biggest guiding princple we have for this is Deferred Implmentation. Deferred Implementation demands implementation agnosticism from the client code. We’ll use the User Repository code from the post Keep you code lazy and ignorant as an example. When we wrote the code that needed to look up a user by the user name the client code was ignorant of how the data got there. This was good because it meant we could focus on the business logic of logging in and leave the details of how retrieve user information for later. All we needed to supply was an API for the client code to use. Later, in the next post, we wrote the code to retrieve the user data using an ADO connection to a SQL database. The truth is we could have used any storage medium like an XML file or web service. Either way the client code didn’t change because of the implementation details in the User Repository. Doing this created a hedge in both the barrier sense and the risk mitigation sense.
TDD played a big role in getting to the deferred implementation of the User Repository. We were able to quickly put together a test bed for our ideas and keep it to be sure we didn’t inadvertently break anything. TDD also gave us a formula for the incremental improvement of the code.
TDD by itself didn’t get the job done. We needed to be conscious of separation of concerns, not mixing object creation with business logic and design patterns that help us solve these sorts of problems.
I hope this addressed the question Craig posed. If not, I hope this was at least informative.
This post is a follow up to Busting the great TDD Myth. If you haven’t already read it, please check it out.
Lets review the code from the last post:
function TLogin.GetConnectionString : string; var reg : TRegIniFile; begin reg := TRegIniFile.Create('Software\MyApp'); Result := reg.ReadString('Database', 'ConnectionString', ''); FreeAndNil(reg); end; function TLogin.Execute(const UserName, Password : string) : Boolean; var UserQuery : TADOQuery; DBUserPassword : string; begin UserQuery := TADOQuery.Create(nil); try UserQuery.ConnectionString := GetConnectionString; UserQuery.SQL.Text := 'SELECT Password FROM Users WHERE UserName = ' + QuotedStr(UserName); UserQuery.Open; DBUserPassword := UserQuery.FieldByName('Password').AsString; UserQuery.Free; Result := DBUserPassword = Password; except Result := False; end; end;
Now lets review the problems:
All of these problems can be summed up as: Too much ceremony and too little essence. What is this class trying to do? Does creating a connection to the database, querying a table and handling exceptions have much to do with the purpose of logging in to the system?
WARNING
There’s a lot going on in this article. It is intended to be humorous and slightly inflammatory. It’s also the first of a series. I intend to show you how the average Joe digs himself into a hole and then how to get out. I hope you enjoy the read.
On with the show …
You will not learn what you need to know from a TDD course/book/lecture. When you walk away you will not be able to write better software. You will not be able to test drive all your code in a fully automated test suite. Your expectations will not be met and you will quickly dump the whole concept of Test Driven Development.
It’s true.
The problem has nothing to do with TDD. The problem is that you don’t know how to write better software. TDD will not make you a coding rock star. You will not be the envy of your peers. Your face will not be printed on a t-shirt (I don’t think that’s ever happened but it would be cool).
Can’t be done, right?
We have some patterns that help us do this: MVP, Passive View, Supervising Controller, Presenter First. Rolling your own implementation of these patterns is challenging and time consuming. Additionally, we haven’t had any frameworks to make it any easier.
So that’s the problem I set out to solve. I’ve reached a point where I’ve been able create a proof of concept application with this framework I’ve been writing. I’m hoping to get some feedback from the community on this.
So what did I do?
I chose to solve the problem using the Passive View pattern. I wanted to be able to design a form with the Delphi form designer but I didn’t want to put any code in the form implementation. That left me with the problem of controlling the GUI from the controller object. Since the point of this exercise is to keep everything testable from a unit test I couldn’t have the controller interacting with the concrete GUI elements. So I came up with the following:
IView = interface
GetViewElement(const ID : string) : IViewElement;
end;
IView has the responsibility of providing access to the view elements to the controller. So the controller can interact with these IViewElement interfaces instead of the concrete GUI elements (like TButton, TEdit, etc.).
What I didn’t want is to have to use special VCL components to make this work. First, because it would be too much trouble to create a full suite of components that implemented IViewElement and second because I wanted to be able retro fit an existing application to this style of design. To accomplish this I did the following:
TGuiAdaptor = class(TInterfacedObject, IViewElement)
public
constructor Create(AControl : TControl)
end;
In GetViewElement the concrete view wraps concrete GUI elements with the TGuiAdaptor and returns it to the controller. So now we can choose to mock the view or, more appropriately to the purpose, use the concrete view for testing.
Having got this far I can now use TDD to express behavioral requirements in the form of unit tests. Have a look at the demo application included in the archive.
Why would I do this?
Be aware: This is a proof of concept release. I know there’s a great deal more work to go before this would be viable for production.
Here is the code: PassiveViewFramework.rar
Recent Comments