Hey everyone in Dayton, I’ll be speaking on the SOLID principles next Wednesday (Oct. 28) at 6pm at the Dayton .NET Developers Group. Hope to see some of you there.
Quality assurance testing is an important part of any software project, but QA is especially important on an agile project. This is because in an agile world, we are striving for higher quality, quicker turnaround times, and more frequent releases. This requires that QA is in step with the rest of the team.
On many of the projects that I have been on, the relationship between QA and the rest of the team is decidedly un-agile. Instead it has worked something like this:
- BA writes the technical specifications
- Developer starts working on the feature, and might discuss the feature with the BA to iron out the details
- Developer completes the feature and throws it over the wall to QA
- QA begins to test the feature
There are many problems with doing things this way.
- The QA team has not been included on any discussions about the feature, so they might not be privy to some important information that came out in dicussions between developers and BAs
- The developers have developed the feature without having a test plan from QA, so they don’t really know if QA will find bugs or not
- There was no discussion about how QA should test the feature, so QA might not know how to write a test plan
- It might take time for QA to write the test plan and test the feature, and by that time, the developers might be several days into the next feature
- This leads to situations where developers give features to QA without being sure that the feature is completely working (they just didn’t think of something)
- This leads to an “us vs. them” mentality between developers and QA people
This is not agile, this is more akin to the “Scrummerfall” methodologies that many projects fall prey to. The main problem I see with this is that there is no definition of “done”. If a developer doesn’t know what it takes for a feature to be complete, how can you expect them to complete a feature without any bugs?
I would argue that QA should be involved much earlier in the process. Before a developer writes code for a feature, they should have:
- Detailed technical specifications from a BA, including a list of acceptance criteria
- A test plan from QA outlining how QA will test the feature
- No outstanding questions on what the feature should do (or at the very least, a plan to get the answers within a short time, if it’s a minor question)
Now we have a true definition of what “done” is. Not only that, QA has been involved in discussions much earlier on in the process. QA will be able to discuss with the development team how they will test the feature and they can point out edge cases, potential problem areas, etc. This way, when a developer finishes with a feature, there should be no bugs, because the developer will go through the QA test plan to ensure that everything is working from a functional perspective (along with writing unit tests for their code to ensure that their code is working from a technical perspective).
This feels more like agile to me. Agile is more about building quality into the software development process instead of trying to verify quality through brute force testing. QA has now taken on a new, better role in the process.
- Preventing bugs instead of finding bugs
- Quality assurance, not quality control
- Collaboration with the development team, instead of trying to keep developers under control
- Removing the proverbial “wall” between QA and development
This may require QA to work differently than they have in the past. QA testers will need to be more involved in the software development process, with many of the skills that a BA would need. They might need to learn how to use automated testing tools, which may require some basic programming skills. QA testers will need work alongside BAs and developers while a feature is being developed, writing and refining test plans as you go. Sure, there is still going to be some manual clicking-through-the-app testing, but there should be much less of that now.
To me, this makes much more sense. Now the entire team is working together to build quality into the application. This means less bugs, better team morale, and faster delivery of software. And who doesn’t need more of that?
In my last post, I mentioned that I think that hand rolled data access layers using stored procedures are a bad thing, and one of my co-workers asked me to elaborate.
The main reason that I don’t like hand rolled data access layers is that by writing your own DAL, you are basically reimplementing a solved problem. Think about all of the things you have worry about if you write your own DAL:
- Creating the CRUD stored procs
- Writing ADO.NET code to call the stored procs
- Figuring out which entities are dirty and need to be saved, and which properties have changed
- What relationships do you load when you load an entity from the database? How do you do lazy loading of relationships?
- Writing tests to test all of this custom code that you have to write
There are numerous object-relational mapping (ORM) tools out there which take care of all of this. NHibernate is my favorite (more specifically, Fluent NHibernate). NHibernate has been around for several years and is very mature. There are ORMs from Microsoft (LINQ to SQL, Entity Framework), and numerous other ORMs out there.
These ORMs have thousands of people using them, so they have been well tested and have proven over time to be very effective at what they do. Countless hours have gone into the development, design, and testing of these ORMs.
Think about what you are trying to do by writing your own hand rolled data access layer. Who are you to think that you can do a better job in a short amount of time than the people who developed these mature solutions over several years? Data access is a very complicated thing to try and implement, and some of the most painful code that I’ve ever had to deal with was found in someone’s hand rolled data access layer.
Here’s the thing — a large percentage of your data access is vanilla loading and saving of objects from the database. In these cases, performance is not a concern and you do not need any special queries in order to optimize the loading and saving of these objects.
For a very small percentage of your data access, performance may be a concern and you may need to use custom stored procedures. So in these cases, you can bypass the ORM and write your stored procedures and custom code in order to optimize the loading and saving of these specific objects.
If you use stored procedures and custom code, you have more control over things. This also comes with a cost (longer time to develop). If you are accepting this cost for cases where you don’t need to optimize the data access, I would say that you’ve wasted time on premature optimization, not to mention that you’ve probably had to spend time implementing all of that data access code.
I would rather use an ORM that has implemented everything that is difficult about data access. Now all I have to tell it how to map to the database (which isn’t that hard) and tell it to do things for me. With lazy loading, it won’t load entity relationships (e.g. child collections on an entity object) unless it needs to. It knows when objects are dirty and if they need to be saved or not. I have an easy hook so that I can validate entities when they are saved.
The other day I configured a cache in NHibernate in 15 minutes. With very little code I was able to set NHibernate up so that it will cache entities that don’t ever change in memory so that it doesn’t have to get to the database to get them every time. There were numerous examples on the internet telling me how to do this, and I’m extremely confident that it’s going to work (I didn’t have to write any of the difficult code for caching because NHibernate has solved that problem).
I want to write code that solves business problems. Thankfully other people have written libraries that will help me do that. So I’m going to use them.
I finally broke down, set aside some time, and learned Ruby on Rails. My previous experience had been the Ruby Koans, random discussions with people, and a couple quick demos. So basically I knew Ruby, but not Rails.
I started by reading through Rails For .NET Developers, which was an excellent book. Normally I don’t read a lot of tech books (I’m more of a blog guy), but when I’m learning something new, I like having something that will walk me through it. This book did an excellent job of it.
So I had a basic web site with a couple pages and I started building it. I used RSpec for my testing framework and I wrote my tests first, of course.
I was surprised at how similar Rails development is to ASP.NET MVC. Certainly the language is different, but you’re using the same MVC pattern, you’re writing tests for the same things, and you’re creating views in pretty much the same way. I knew that the .NET community had borrowed a lot from Rails, but I got a first hand view of it. I was glad to know that my world wasn’t going to be turned upside down.
The main thing that I noticed is that with Ruby on Rails, it’s really easy to hit the ground running. You have most of what you need out of the box with Rails, and you can easily install more gems to provide extra functionality. It’s all so easy and there is very little friction.
I’ve been doing .NET for awhile now, and I feel that I have a pretty good understanding of it. I’ve learned a lot of tricks and I use a lot of open source projects and other Microsoft libraries to add functionality to .NET, similar to how you add gems in Rails. I written code myself that does database migrations (like rake db:migrate) and code that runs builds (like rake… I’ve even used rake in .NET). But there several significant differences — first, I had to know about the open source projects (and I suspect that many people don’t know about some of them, or worse, aren’t allowed to use them). Second, I have to download them and set them up myself (and know how to do it). Third, I had to write some of the code from scratch.
I think the point I’m trying to make in that last paragraph is that someone who is new to .NET could not grab ASP.NET MVC and have everything they need to create a really good, frictionless, ASP.NET MVC app. When I did my Rails app, I already had my ORM, database migrations, build runner, and my project structure was set up with a lot of code generated for me.
The other benefit to this is that most Rails developers will most likely be doing things the same way. If you do Rails, you’re going to be using an MVC framework, you’re going to use ActiveRecord as your ORM, you’re going to use rake, you’re going to have the same project structure, and on and on. In the .NET world, there are so many different solutions out there. Not that they are all bad, but consistency makes it easier to find someone else who does things your way. In the .NET world, a large majority of developers are still using stored procedures to do all of their data access, and if you are using an ORM, there are many different ones that you might be using. Again, this isn’t necessarily a bad thing (although I would argue that hand rolled data access layers using stored procs are a bad thing), but I feel like there is value in consistency.
I feel that Rails development is very tempting to many developers in the .NET community. This isn’t all because of the language, it’s also because ideas and practices like TDD, the MVC pattern, and good design practices are much more widely accepted in the Ruby community. There is a lot of good in the .NET community, and I still think that .NET is a great platform for building all kinds of applications. But I feel like things like TDD, ORMs, the SOLID principles, etc. are used by less then half of .NET developers out there. Once you start using things like ORMs and TDD, it’s really difficult to join a project that doesn’t use these things and be productive.
So the moral of the story is to use whatever tool you can find to best do the job. Ruby on Rails is something I wouldn’t mind having in my toolbox.
One question that I hear from people who are new to TDD or writing tests is, “How much longer is my feature going to take if I write tests?” This is a valid question. We all have deadlines, so if we have to add something extra to our development process, it better be worth it.
I don’t think I could say it better than this post did, so I’ll just let you read it for yourself.
A lot happens from the time that a business owner envisions an idea in their mind and the time that the idea becomes software. The trick is getting through the whole software development process without losing the original idea.
Have you ever played that party game when you go around the circle whispering the same phrase and when you get to the end the phrase is completely different than when it started? Software development ends up like that a lot (especially when the business owner is changing the original idea!).
The problem is that we all speak a different languages. Developers speak one language, BAs another, PMs another, DBAs another, users another, business people another, executives another, and so on. So a large part of our job is learning how to translate what these people want into developer language (code), and doing it so that, in the end, the software speaks to them in their language.
Here’s a simple example:
Executive: “When we hire new employees, we need to make sure that they have a computer ready for them when they start.”
Business analyst: “User will enter the number of new employees on the screen. The system will check the inventory and make sure that we have enough machines, monitors, keyboards, and mice for the new employees. Each employee should have two monitors.”
Now it’s our turn. As developers, we have to do several translations in the process of writing the software. The DBA (or you) may have to design the database schema. You (the developer) have to write the code.
Remember, the goal is to not lose the original idea. That means that each time we “translate” the original idea, we need to do it little bits at a time.
Usually business analysts don’t give you specs that are written exactly how you want them. This is not a knock on BAs, but they speak a different language than us developers. What we really want from them is a set of acceptance criteria. In other words, we need to know what we have to do in order to complete the feature. Not only that, we need to know how we are going to test our feature so that we know that it’s working.
So let’s take the business analyst’s specs and translate them into acceptance criteria:
Given a stash of unused hardware
When a user enters the number of new employees
Then it should verify that we have one machine for each new employee
Then it should verify that we have two monitors for each new employee
Then it should verify that we have one keyboard for each new employee
Then it should verify that we have one mouse for each new employee
When we don’t have enough machines for new employees
Then we need to order new machines so that we have one for each new employee
When we don’t have enough monitors for new employees
Then we need to order new monitors so that we have two for each new employee
When we don’t have enough keyboards for new employees
Then we need to order new keyboards so that we have one for each new employee
When we don’t have enough mice for new employees
Then we need to order new mice so that we have one for each new employee
We’re not saying anything drastically different from what the business analyst said. But the way that we wrote it is important. Notice the use of the words given, when, then. Here’s the next translation:
public class Given_a_stash_of_unused_hardware
{
}
public class When_a_user_enters_the_number_of_new_employees
: Given_a_stash_of_unused_hardware
{
[Test]
public void Then_it_should_verify_that_we_have_one_machine_for_each_new_employee()
{
}
[Test]
public void Then_it_should_verify_that_we_have_two_monitors_for_each_new_employee()
{
}
[Test]
public void Then_it_should_verify_that_we_have_one_keyboard_for_each_new_employee()
{
}
[Test]
public void Then_it_should_verify_that_we_have_one_mouse_for_each_new_employee()
{
}
}
public class When_we_don't_have_enough_machines_for_new_employees
: Given_a_stash_of_unused_hardware
{
[Test]
public void Then_we_need_to_order_new_machines_so_that_we_have_one_for_each_new_employee()
{
}
}
public class When_we_don't_have_enough_monitors_for_new_employees
: Given_a_stash_of_unused_hardware
{
[Test]
public void Then_we_need_to_order_new_monitors_so_that_we_have_two_for_each_new_employee()
{
}
}
public class When_we_don't_have_enough_keyboards_for_new_employees
: Given_a_stash_of_unused_hardware
{
[Test]
public void Then_we_need_to_order_new_keyboards_so_that_we_have_one_for_each_new_employee()
{
}
}
public class When_we_don't_have_enough_mice_for_new_employees
: Given_a_stash_of_unused_hardware
{
[Test]
public void Then_we_need_to_order_new_mice_so_that_we_have_one_for_each_new_employee()
{
}
}
This is how you do behavior driven development. We took our acceptance criteria and wrote them out as code in the form of unit tests. These unit tests will prove that our code is working (when we get to that point) and it will also act as our documentation of what the code is supposed to do.
The reason that this is important is that we’re still in the middle of doing our translation. A lot of people take the tech specs and immediately start writing implementation code. But by doing that, you skip a step in the translation, and when you do that, you risk losing some of the original intent of the feature. This is one of the reason why writing tests before you write implementation code is important. First of all, if you write your tests first, then when they’re passing, you know that you’re done. Second, you’ve written out what the feature is supposed to do — nothing more, nothing less. This is going to help us implement the feature without losing the original intent of the person who thought it up.
Now, yes now, you can go write the implementation code. By focusing on what the end product should be and since you wrote your tests first, this part should be easy. It’s much easier to achieve your goal when you know what the goal is!