I've used both WP_Query and get_posts. On one of my sidebar templates, I use the following loop to display posts from a particular category by using custom fields with a key of 'category_to_load' which contains the category slug or category name. The real difference comes in the implementation of either method.
The get_posts method looks like so in some of my templates:
<?php
global $post;
$blog_posts = get_posts($q_string);
foreach($blog_posts as $post) :
setup_postdata($post);
?>
<div class="blog_post">
<div class="title">
<h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
<span class="date"><?php the_time('F j, Y'); ?> by <?php the_author(); ?></span>
</div>
<?php the_excerpt(); ?>
</div>
<?php endforeach; ?>
Where the WP_Query implementation looks like this:
$blog_posts = new WP_Query('showposts=15');
while ($blog_posts->have_posts()) : $blog_posts->the_post(); ?>
<div <?php post_class() ?> id="post-<?php the_ID(); ?>" class="blog_post">
<div class="title">
<h2><a href="<?php the_permalink() ?>" rel="bookmark" title="Permanent Link to <?php the_title_attribute(); ?>"><?php the_title(); ?></a></h2>
<span class="date"><?php the_time('F jS, Y') ?> <!-- by <?php the_author() ?> --></span>
</div>
<div class="entry">
<?php the_content(); ?>
</div>
<p class="postmetadata"><?php the_tags('Tags: ', ', ', '<br />'); ?> Posted in <?php the_category(', ') ?> | <?php edit_post_link('Edit', '', ' | '); ?> <?php comments_popup_link('No Comments »', '1 Comment »', '% Comments »'); ?></p>
</div>
<?php endwhile; ?>
The main difference being that you don't have to reset the global $post variable and you also don't have to set up the post data by calling setup_postdata($post) on each post object when you use WP_query. You can also use the lovely have_posts() function on the WP_Query function, which is not available using get_posts().
You shouldn't use the query_posts() function unless you really mean to because it modifies the main loop for the page. See the docs. So if you're building a special page to display your blog on, then calling query_posts may mess up the page's loop, so you should use WP_Query.
That's just my two cents. My ultimate suggestion, your first choice should be WP_Query.
-Chris