Showing posts with label TDD. Show all posts
Showing posts with label TDD. Show all posts

Friday, February 20, 2009

Loading objects' fields/properties automagically

There is a very nice function in the new ASP.NET MVC code that allows a Controller to load an object from values passed in from a HttpRequest (UPDATE: called UpdateFrom, see ScottGu's post). It did a best guess on matching values to object properties, but most (99%?) of the time that is all you need. Well, when I started using Domain-Driven Design (DDD) and Test-Driven Development (TDD) on the latest additions to our Community Manager.NET product I wanted to find something similar - but I couldn't find anything as terse and useful.

So I did what any good dev would, I read the sourcecode and derived my own functions to do something similar, but with the SqlDataReaders that my repositories would be getting out of SQL Server (it almost feels like the ActiveRecord pattern ...). Below are the result, two LoadToObject() functions (and a PopulateTypeException class) in my BaseRepository class that give me the functionality I need. I'm putting these out there in case someone else needs this, and in the hope that any glaringly obvious errors might get picked up by others!

One issue I had with the Microsoft version of this function was they only load public properties of an object, not public fields. Now I know that in C# you can setup properties as easily as fields, but in VB it's still a hassle. I personally like using fields for simple value storing because it is clearer to see what they do in the code and leave more space for real method calls.

The final problem I ran into was that my custom Html data type needed to implement it's own TypeConverter so that the HTML in the SQL Server ntext field could be converted to a strongly typed Html type. But that can go into another blog post if people are interested.

    ''' <summary>
''' Populates an object's public fields and properties with values from a SqlDataReader.
'''
</summary>
''' <param name="readerToLoad">The SqlDataReader that needs to be loaded into the object.</param>
''' <param name="obj">The object we want to populate.</param>
''' <param name="objectPrefix">A prefix to the object field/property names. Tries to match using "." and "_".</param>
''' <returns>The object passed in with fields/properties loaded.</returns>
''' <remarks>
''' Does a best guess as to how to match names from the SqlDataReader to the object.
'''
</remarks>
Protected Function LoadToObject(ByVal readerToLoad As SqlDataReader, ByVal obj As Object, ByVal objectPrefix As String) As Object
Dim
newColl As New System.Collections.Specialized.NameValueCollection()
Dim x As Integer

If
obj Is Nothing Then
Throw New
ArgumentNullException("obj", "Object must have a value in order to be loaded to.")
End If

For
x = 0 To (readerToLoad.FieldCount - 1)
newColl.Add(readerToLoad.GetName(x), readerToLoad.GetValue(x))
Next

Return
LoadToObject(newColl, obj, objectPrefix)
End Function

''' <summary>
''' Populates an object's public fields and properties with values from a NameValueCollection.
'''
</summary>
''' <param name="valueCollectionToLoad">The NameValueCollection that needs to be loaded into the object.</param>
''' <param name="obj">The object we want to populate.</param>
''' <param name="objectPrefix">A prefix to the object field/property names. Tries to match using "." and "_".</param>
''' <returns>The object passed in with fields/properties loaded.</returns>
''' <remarks>
''' Does a best guess as to how to match names from the NameValueCollection to the object.
'''
</remarks>
Protected Function LoadToObject(ByVal valueCollectionToLoad As System.Collections.Specialized.NameValueCollection, ByVal obj As Object, ByVal objectPrefix As String) As Object
Dim
objType As Type = obj.GetType()
Dim objName As String = objType.Name
Dim exceptionList As New StringBuilder()
Dim props As PropertyInfo() = objType.GetProperties()
Dim fields As FieldInfo() = objType.GetFields()
Dim ex As PopulateTypeException = Nothing
Dim
prop As PropertyInfo
Dim field As FieldInfo

' try writing to the object's properties
For Each prop In props
'check the key, going to be forgiving here, allowing for full declaration or just propname
Dim key As String = prop.Name

If objectPrefix <> String.Empty Then
key = objectPrefix + key
End If

If
valueCollectionToLoad(key) = Nothing Then
key = objName + "." + prop.Name
End If

If
valueCollectionToLoad(key) = Nothing Then
key = objName + "_" + prop.Name
End If

If
valueCollectionToLoad(key) <> Nothing Then
Dim
conv As TypeConverter = TypeDescriptor.GetConverter(prop.PropertyType)
Dim value As Object = valueCollectionToLoad(key)

If conv.CanConvertFrom(System.Type.GetType("System.String", True, True)) Then
Try
value = conv.ConvertFrom(valueCollectionToLoad(key))
prop.SetValue(obj, value, Nothing)
Catch e As Exception
Dim message As String = prop.Name + " is not a valid " + prop.PropertyType.Name + "; " + e.Message

If ex Is Nothing Then
ex = New PopulateTypeException("Errors occurred during object binding - review the LoadExceptions property of this exception for more details")
End If

Dim
info As New PopulateTypeException.ExceptionInfo()

info.AttemptedValue = value
info
.PropertyName = prop.Name
info
.ErrorMessage = message

ex
.LoadExceptions.Add(info)
End Try
Else
Throw New
Exception(String.Format("No type converter available for type: {0}", prop.PropertyType))
End If
End If
Next

' now try writing to the object's public fields
For Each field In fields
'check the key, going to be forgiving here, allowing for full declaration or just fieldname
Dim key As String = field.Name

If objectPrefix <> String.Empty Then
key = objectPrefix + key
End If

If
valueCollectionToLoad(key) = Nothing Then
key = objName + "." + field.Name
End If

If
valueCollectionToLoad(key) = Nothing Then
key = objName + "_" + field.Name
End If

If
valueCollectionToLoad(key) <> Nothing Then
Dim
conv As TypeConverter = TypeDescriptor.GetConverter(field.FieldType)
Dim value As Object = valueCollectionToLoad(key)

If conv.CanConvertFrom(System.Type.GetType("System.String", True, True)) Then
Try
value = conv.ConvertFrom(valueCollectionToLoad(key))
field.SetValue(obj, value)
Catch e As Exception
Dim message As String = field.Name + " is not a valid " + field.FieldType.Name + "; " + e.Message

If ex Is Nothing Then
ex = New PopulateTypeException("Errors occurred during object binding - review the LoadExceptions property of this exception for more details")
End If

Dim
info As New PopulateTypeException.ExceptionInfo()

info.AttemptedValue = value
info
.PropertyName = field.Name
info
.ErrorMessage = message

ex
.LoadExceptions.Add(info)
End Try
Else
Throw New
Exception(String.Format("No type converter available for type: {0}", field.FieldType))
End If
End If
Next

If Not
(ex Is Nothing) Then
Throw
ex
Else
Return
obj
End If
End Function

''' <summary>
''' Contains exceptions that arise during the populating of an object by the LoadToObject() method.
'''
</summary>
<Global.System.Serializable()> Public Class PopulateTypeException
Inherits Exception

Public Class ExceptionInfo
Public PropertyName As String
Public
AttemptedValue As Object
Public
ErrorMessage As String
End Class

Public
LoadExceptions As New List(Of ExceptionInfo)
Private PopulateTypeException()

Public Sub New(ByVal message As String)
MyBase.new(message)
End Sub

Public Sub New
(ByVal message As String, ByVal inner As Exception)
MyBase.new(message, inner)
End Sub

Protected Sub New
(ByVal info As System.Runtime.Serialization.SerializationInfo, ByVal context As System.Runtime.Serialization.StreamingContext)
MyBase.New(info, context)
End Sub

End Class

Tuesday, April 22, 2008

Second ALT.NET conference

Ben Scheirman has a good couple of posts on the ALT.NET open conference that ran just after the Microsoft MVP Summit. Not being an MVP I wasn't anywhere close to going, so it is nice to see this sort of photo intensive report ... looks like fun and I'm hoping Code Camp Oz 2008 can be as productive for me.

Monday, April 07, 2008

Drinking the Watir

I've been using Watir recently to help automate integration tests for some of our client projects. Moving from a Windows XP to a Windows Vista environment proved more troublesome than I had realised it would be, however I now have my test environment all working and last Friday night realised some value from the automated tests as they (very) quickly ran through a complicated series of steps to prove an important piece of client functionality after a server upgrade, thus allowing me to get home earlier than expected.

For the benefit of my fellow Elcomites I'm going to document the steps required to setup Watir on a brand new Vista machine.

Pre-Requisites

Firstly, as I pointed out previously, you can't have Office Groove 2007 installed. Also, you need to be able to run as Administrator on your local machine.

Install Ruby

You will want Ruby 1.8.5, and fortunately there is a one-click installer for Windows, which you just need to download and run. By default this installs under C:\Ruby\ and includes RubyGems (which makes installing other Ruby stuff dead easy).

Having done this, let's test whether we have a fully working Ruby installation by running irb (the interactive ruby shell). Open a command window and type the following:

C:\Users\angus>irb

In Vista this prompts you with the message "An unidentified program wants to access your computer", identifying the program as ruby.exe with the option to Cancel or Allow. Obviously we want to Allow this (note: you won't get this message if your UAC settings are different to mine). It will then open a new command window with the following text shown:

irb(main):001:0>

Just getting this far tells us that Ruby is installed. Close both the command windows you have open.

Install Watir

Installing Watir is as simple as starting another command window and then typing in the following command:

C:\Users\angus>gem install watir

We then want to do the same for the windows-pr library that provides access to various Win32 APIs and is needed in Vista to run Watir.

C:\Users\angus>gem install windows-pr

We can then open irb again and see if we can test Watir from the command-line!

irb(main):001:0> require 'watir'
=> true

This tells Ruby that we are using the Watir namespace (like Imports in VB.NET or using in C#.NET)

irb(main):002:0> include Watir
=> Object

This actually mixes in the Watir library with our code (essentially making it available to us).

irb(main):003:0> ie = Watir::IE.new
=> #<Watir::IE:0x4a50e70 @url_list=["about:blank"], @ole_object=nil, @rexmlDomobject=nil, @logger=#<Watir::DefaultLogger:0x4a50d94, @progname=nil, @logdev=#<Logger::LogDevice:0x4a50d30, @mutex=#<Logger::LogDevice::LogDeviceMutex:0x4a50d08, @mon_entering_queue=[], @mon_count=0, @mon_owner=nil, @mon_waiting_queue=[]>, @dev=#<IO:0x32d7320>, @shift_size=nil, @shift_age=nil, @filename=nil>, @formatter=nil, @default_formatter=#<Logger::Formatter:0x4a50d58 @datetime_format="%d-%b-%Y %H:%M:%S">, @level=2>, @activeObjectHighLightColor="yellow", @down_load_time=0.413, @speed=:slow, @typingspeed=0.08, @page_container=#<Watir::IE:0x4a50e70 ...>, @ie=#<WIN32OLE:0x4a50e0c>, @error_checkers=[], @defaultSleepTime=0.1>

Whoa! Did you see that Internet Explorer window open up? This is the litmus test for Watir, this line opens a new IE window and assigns a reference to it to the ie variable.

irb(main):004:0> ie.goto('http://ww.google.com.au')
=> 2.206

We can now start playing with the open window, firstly by browsing to the Google (Australia) website ...

irb(main):005:0> ie.text_field(:name, 'q').set('Elcom Technology')
=> true

... entering a search term ...

irb(main):006:0> ie.button(:name, 'btnG').click
=> 1.045

... and then actioning the search. There is a lot more that Watir allows you to do, but this is a good start!

Install Aptana Studio

Editing Ruby scripts in Notepad is a chore, and whilst Visual Studio will eventually support Ruby the current Sapphire in Steel IDE doesn't excite me (the free version is still just an alpha release). Enter Aptana Studio, which has absorbed the old RadRails IDE project and which has a stable, and free, Community edition. Amongst other features this gives us Ruby colour coding and an integrated test runner - great for Watir!

Aptana Studio is built atop the open-source Eclipse IDE for Java. This means that other Eclipse plugins are also available. It also means your machine will end up running Sun's Java. It comes with a Windows installer to make things easy and is a free download.

Once you have run the installer you will want to edit the properties of the studio exe so that it runs as administrator.

Aptana

You can now run Aptana Studio and choose to install the Ruby on Rails plugin from the start page:

Aptana Start

Create your first test script ... here is that sample we ran before, but this time the way you might typically create a test file:

##########################################################

# Integration Test Sample

##########################################################

# This demonstrates the skeleton of an integration test.

##########################################################

 

require 'rubygems'

require 'test/unit'

require 'watir'

require 'watir/testcase'

 

class IntegrationTest < Watir::TestCase

    # Runs before each test case, e.g. used to specify login details.

  def setup

    super

  end

 

  # Runs after each test case, e.g. could be used for logging, etc

  def teardown

    super

  end 

 

  # always the first test as it sets up the browser object

  def test_first

    # Create a new browser instance (running this opens IE)

    @@ie = Watir::IE.new

    @@ie.speed = :fast # comment this line if you want to more easily see what is happening

    @@ie.maximize

  end

 

  # reproduce our irb test

  def test_google_elcom

    @@ie.goto('http://www.google.com.au')

    @@ie.text_field(:name, 'q').set('Elcom Technology')

    @@ie.button(:name, 'btnG').click

  end

 

  # always the last test, closes the browser object 

  def test_end

    @@ie.close

  end

end

 

By right-clicking in that file and choosing Run As > Test::Unit Test (see below) you can run the tests within the Aptana test runner.

RunAs

That gives you a nice test result tree that shows exactly where things fall over (when they do).

You are done! Now I am pretty sure that people are going to find problems I haven't documented. If you do, then please feel free to add a comment with your difficulty and I'll update this main post with the solution.

Monday, March 10, 2008

It's the software that matters!

James Shore, creator of NUnitASP and author of The Art of Agile Development has recently announced that he's no longer supporting NUnitASP development, citing his increasing lack of interest in it and pointing interested parties to Selenium or Watir (or perhaps WatiN, although that has received some criticism from Scott Bellware, others are not so sure).

It's a shame we're not getting more choices in this area of testing, but James is right to focus on his own current projects. He has left it open for someone else to take over the project, although the way ASP.NET MVC will expose HTTPRequest as an interface might make it superfluous.

Back to the point of this post. James has a great blog with some very thoughtful articles. I found this great quote in It's the Software, Stupid! It summarises for me the problem with simply implementing Scrum and thinking that makes you Agile.
“your team is expected to self-organize and define its own practices”
“It's time we brought back the early emphasis on great engineering practices. If you're using Scrum or another agile method that doesn't include engineering practices, realize that your method is incomplete. Scrum, for example, intentionally creates an environment in which your team is expected to self-organize and define its own practices. If you aren't doing that--if you aren't talking about engineering practices, what's working, what's not, and how to improve--you're going to run into trouble someday. Probably someday soon.”

Tuesday, February 05, 2008

Interesting Links 2008-02-05

Madhav Rao has a great post summarising the unit testing tools currently available for .NET developers.

Chad Myers also has a great post explaining his development methodology, called I don't trust me.

I've been reading C.S. Lewis' The Abolition of Man, and have found a good version of it online thanks to Columbia University.

Finally, DevTopics has 101 great computer programming quotes (via Chad Myers, via DotNetKicks). Here are some of my favourites:
“The most amazing achievement of the computer software industry is its continuing cancellation of the steady and staggering gains made by the computer hardware industry.”
(Henry Petroski)

“Complexity kills. It sucks the life out of developers, it makes products difficult to plan, build and test, it introduces security challenges, and it causes end-user and administrator frustration.”
(Ray Ozzie)

“There are only two industries that refer to their customers as 'users'.”
(Edward Tufte)

“The trouble with programmers is that you can never tell what a programmer is doing until it's too late.”
(Seymour Cray)

“That's the thing about people who think they hate computers. What they really hate is lousy programmers.”
(Larry Niven)

“A hacker on a roll may be able to produce–in a period of a few months–something that a small development group (say, 7-8 people) would have a hard time getting together over a year. IBM used to report that certain programmers might be as much as 100 times as productive as other workers, or more.”
(Peter Seebach)

“The best programmers are not marginally better than merely good ones. They are an order-of-magnitude better, measured by whatever standard: conceptual creativity, speed, ingenuity of design, or problem-solving ability.”
(Randall E. Stross)

“Optimism is an occupational hazard of programming; feedback is the treatment.”
(Kent Beck)

“In software, we rarely have meaningful requirements. Even if we do, the only measure of success that matters is whether our solution solves the customer's shifting idea of what their problem is.”
(Jeff Atwood)

“Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are–by definition–not smart enough to debug it.”
(Brian Kernighan)

“As soon as we started programming, we found to our surprise that it wasn't as easy to get programs right as we had thought. Debugging had to be discovered. I can remember the exact instant when I realized that a large part of my life from then on was going to be spent in finding mistakes in my own programs.”
(Maurice Wilkes discovers debugging, 1949)

“I don't care if it works on your machine! We are not shipping your machine!”
(Vidiu Platon)

“Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.”
(Martin Golding)

Saturday, January 12, 2008

VB.NET is the illegal immigrant of Microsoft languages

I currently mostly code in VB.NET, developing ASP.NET web applications. I have in the past developed in Omnis, MS Access 2/95/2000, Lotus Notes/LotusScript, Java, VB6, VBA, PHP, JavaScript, C# and Ruby (on Rails). Some of those I did for fun, but most were because I was paid (and asked) to. I point this out not to boast, but in order to put some background around this particular rant.

[NOTE: Another accidental thing that denigrates VB is that we must put ".NET" at the end if we are talking about 'real' development, because VB as a term covers such a broad range of languages. This alone makes C# seem exclusive and more 'grown-up'. In fact C# is more properly referred to as Visual C#, or C#.NET.]

“Computer languages influence how you think about a problem, and how you think about communicating.”
The Pragmatic Programmer
Microsoft have a publicly declared agnostic view of which language a developer chooses to use within the .NET platform, so long as they use it. For this reason they have researched new languages for the CLR, and are introducing the DLR to enable IronPython, IronRuby etc.

Internally however Microsoft have a culture that prefers people using C#. In practice this means that the 'cheap' option is always to do code samples and demo code in C# and not VB (or some other .NET language), because it is simply more convenient to do so. When asked why a given example is not also in VB, this is the answer given (and the occasional plea for help). There is however a tendency to see VB developers as somehow inherently less capable that C# developers - perhaps this is a view that the 'best' work at Microsoft, or maybe it's a hangover from the "Basic" part of VB's name. Most probably it also has a lot to do identifying VB users with using a persona called 'Mort', whilst C# is Elvis and C++ Einstein (thanks usability guys).

A few months ago Bill McCarthy, a VB MVP, ranted about this issue in his post Is VB the n*gg*r of programming languages?. Leaving aside a controversial choice of words (for which he was smacked down), he had a very good point. My post's title is a reference to his point (with a more PC turn of phrase).

Bill points out that how to videos (for beginners?) are often coded in VB, but the SDKs have C# examples (for real developers?).

A more recent balancing view comes from Scott Hanselman, who asks Is rooting for Visual Basic like rooting for the Red Sox?. Leaving aside the unfortunately humorous connotations of 'rooting' to us Aussies, his point is that VB is making something of a comeback in popularity, but old-timers (like Bill?) still want to feel like underdogs. Reading the rest of Scott's blog shows that he's been exploring beyond the homely environs of C# and hanging out with people who are pragmatic enough to use whatever works.

Personally I think this last point is the one that makes the most sense. In The Pragmatic Programmer Andy Hunt and David Thomas said:
“Computer languages influence how you think about a problem, and how you think about communicating ... Designing a solution with Lisp in mind will produce different results than a solution based on C-style thinking, and vice versa. Conversely, and we think more importantly, the language of the problem domain may also suggest a programming solution.”

All of this is what has attracted me to the ALT.NET movement (see the first use of the term, the conference last year, it just had a leadership summit and a Yahoo! group). Martin Fowler weighed in and said this about ALT.NET:
“The alt.net mind-set is one that is very familiar to me. It has that mix of agile + object-orientation + patterns + TDD + DDD which is very much the school of software development that I favor. (Lacking a proper name for it, I'm inclined to call it the OOPSLA school of software development.) There is certainly a belief that there is a mainstream Microsoft orthodoxy at the moment, one that doesn't fit the OOSPLA school. And there's some frustration with that. But the point here is that it's not that the alt.net community thinks that the perceived mainstream Microsoft route should be erased, but that the Microsoft world is big enough for different approaches.”
To all this I say a big hooray! But ... it seems everyone who wants to do something with ALT.NET is a devoted fan of C# (or at least NOT a fan of VB). So every code sample, explanation or tool is aimed at C# and either requires much grokking to be used in VB, or in many cases, cannot be used with VB at all.

The answer I'm afraid is that I must use more C# if I want to become more agile, practice TDD or play with ASP.NET MVC. The fact that I get paid to work with VB.NET is not a good enough reason to stick with it. VB is the illegal immigrant of .NET languages, it snuck into .NET when it should have been told to stay away (perhaps forcibly deported) and the sooner any of us who use it realise that, the better. Shifting to a true .NET citizen like C# is clearly the only responsible thing to do - and using those Johnny-come-latelys like IronRuby or IronPython (or Boo) is a joke - they are only toy languages, not super-strong doers of great deeds like C# is.

Still, at least there is one hope on the horizon, with Silverlight assuming such importance in Microsoft's plans for the web, perhaps VBx will come to our rescue? With VB moving into the DLR perhaps VB developers can all become the new, new boys on the block, and start looking down on those poor C# types who are unable to duck-type or use Jasper. And my persona? Let's just call him Ben.

Tuesday, December 11, 2007

ASP.NET MVC is here

ScottGu has announced the CTP release of ASP.NET 3.5 Extensions. This is great news for ALT.NET and really has made my week. As regular readers might realise the key part of this release for me is the MVC (Model-View-Controller) framework for ASP.NET.
“ASP.NET MVC: This model view controller (MVC) framework for ASP.NET provides a structured model that enables a clear separation of concerns within web applications, and makes it easier to unit test your code and support a TDD workflow. It also helps provide more control over the URLs you publish in your applications, and more control over the HTML that is emitted from them.”
I think this finally gives ASP.NET developers some much needed flexibility. The guys at Microsoft are taking pains to point out this is not a replacement for the existing WebForms model, but they point to the following reasons to use the new framework:
  • It allows developers to use the MVC pattern with ASP.NET more cleanly than ever before.
  • It makes it easier to implement RESTful (Representational State Transfer) systems and to control your URLs using ASP.NET
  • The MVC pattern allows for easier unit testing of business logic behind pages (now in controllers) and therefore helps promote the use of Test-Driven Development with ASP.NET.
I have some personal reasons for looking forward to this framework that may be too politically sensitive for the ASP.NET guys to admit to:
  • This kills off the one form per page restriction, allowing for XHTML that more closely matches the functionality of the page.
  • No more viewdata is a pain, but it also means that ASP.NET developers can now more easily incorporate other client-side frameworks into their applications (e.g. Yahoo! User Interface Library, Script.aculo.us, Prototype, etc)
  • Ruby on Rails is looking a whole lot less attractive as ASP.NET MVC offers much of the same clarity of design whilst leveraging my existing ASP.NET skills.
I will probably devote a few more blog posts to exploring this interesting ASP.NET development, so feel free to let me know (via the comments) what you might be most interested in knowing about.