diff --git a/pcd-loader.cabal b/pcd-loader.cabal
--- a/pcd-loader.cabal
+++ b/pcd-loader.cabal
@@ -1,5 +1,5 @@
 name:                pcd-loader
-version:             0.2.3.2
+version:             0.3.0.0
 synopsis:            PCD file loader.
 description:         Parser for PCD (point cloud data) formats.  See
                      <http://pointclouds.org/documentation/tutorials/pcd_file_format.php>
@@ -12,17 +12,18 @@
 category:            Robotics,Graphics
 build-type:          Simple
 cabal-version:       >=1.10
+extra-source-files:  tests/SampleLoad.hs, tests/SampleData.hs
 
 source-repository head
   type: git
   location: git://github.com/acowley/pcd-loader.git
 
 library
-  exposed-modules:  PCD.Data, PCD.Header, PCD.Internal.Types, 
-                    PCD.Internal.StorableFieldType
+  exposed-modules:  PCD.Data, PCD.Header, PCD.Conversion, PCD.Internal.Types, 
+                    PCD.Internal.StorableFieldType, PCD.Internal.AsciiParsers
   ghc-options:      -O2 -Wall
   hs-source-dirs:   src
-  build-depends:    base >= 4.6 && < 5, text, mtl, lens, vector, bytestring, 
+  build-depends:    base >= 4.5 && < 5, text, mtl, lens, vector, bytestring, 
                     attoparsec, binary, deepseq, linear
   default-language: Haskell2010
 
@@ -30,6 +31,15 @@
   hs-source-dirs:   src/executable
   main-is:          Main.hs       
   ghc-options:      -O2 -Wall
-  build-depends:    base >= 4.6 && < 5, pcd-loader    
+  build-depends:    base >= 4.5 && < 5, pcd-loader    
   default-language: Haskell2010
 
+test-suite tests
+  type: exitcode-stdio-1.0
+  hs-source-dirs: tests
+  main-is: SampleLoad.hs
+  ghc-options: -Wall -O2
+  default-language: Haskell2010
+  build-depends: base >= 4.5 && < 5,
+                 test-framework, test-framework-hunit, HUnit,
+                 vector, lens, text, directory, string-qq, pcd-loader
diff --git a/src/PCD/Conversion.hs b/src/PCD/Conversion.hs
new file mode 100644
--- /dev/null
+++ b/src/PCD/Conversion.hs
@@ -0,0 +1,31 @@
+-- |Facility to convert an ASCII PCD file to a Binary one.
+module PCD.Conversion where
+import Control.Lens ((^.), (.~))
+import Control.Monad (when)
+import qualified Data.Text.IO as T
+import qualified Data.Vector as B
+import Foreign.Marshal.Alloc (allocaBytes)
+import System.IO (IOMode(..), openFile, hClose, hPutBuf, withBinaryFile)
+
+import PCD.Header
+import qualified PCD.Internal.AsciiParsers as A
+import PCD.Internal.StorableFieldType (pokeBinaryPoints)
+
+-- |@asciiToBinary inputFile outputFile@ converts a PCD file from
+-- ASCII to Binary.
+asciiToBinary :: FilePath -> FilePath -> IO ()
+asciiToBinary i o = do h <- openFile i ReadMode
+                       (pcdh,_) <- readHeader h
+                       print pcdh
+                       when (pcdh^.format /= ASCII)
+                            (error "Input PCD is already binary!")
+                       let numBytes = totalBinarySize pcdh
+                       putStrLn $ "Expecting to generate "++show numBytes++" bytes"
+                       v <- A.readPointsDefault pcdh h
+                       putStrLn $ "Parsed "++show (B.length v)++" ASCII points"
+                       hClose h
+                       T.writeFile o (writeHeader (format .~ Binary $ pcdh))
+                       withBinaryFile o AppendMode $ \h' ->
+                         allocaBytes numBytes $ \ptr ->
+                           pokeBinaryPoints ptr v >>
+                           hPutBuf h' ptr numBytes
diff --git a/src/PCD/Data.hs b/src/PCD/Data.hs
--- a/src/PCD/Data.hs
+++ b/src/PCD/Data.hs
@@ -1,21 +1,20 @@
-{-# LANGUAGE ScopedTypeVariables, BangPatterns #-}
--- |Parser for PCD (point cloud data) files. Also provides a facility
--- for converting from ASCII to binary formatted point data.
-module PCD.Data (FieldType(..), unsafeUnwrap, loadAllFields, 
-                 loadXyzw, loadXyz, asciiToBinary, saveBinaryPcd, 
-                 projectBinaryFields, mkSimpleHeader, mkHeaderXYZ) where
+{-# LANGUAGE ScopedTypeVariables #-}
+-- |Parser for PCD (point cloud data) files.
+module PCD.Data (-- * Accessing fields
+                 FieldType(..), unsafeUnwrap, 
+                 -- * Loading PCD data
+                 loadFieldsByName, loadFlexiblePoints, loadXyzw, loadXyz, 
+                 -- * Saving PCD data
+                 saveBinaryPcd, projectBinaryFields,
+                 -- * PCD header creation
+                 mkSimpleHeader, mkHeaderXYZ) where
 import Control.Applicative
 import Control.DeepSeq
 import Control.Lens ((.~), (^.))
-import Control.Monad (when)
-import Data.Attoparsec.Text hiding (I)
 import qualified Data.Attoparsec.Text.Lazy as ATL
 import Data.Text (Text)
-import qualified Data.Text.Lazy.IO as TL
 import qualified Data.Text.IO as T
 import qualified Data.Vector as B
-import qualified Data.Vector.Generic as G
-import qualified Data.Vector.Generic.Mutable as GM
 import qualified Data.Vector.Storable as V
 import qualified Data.Vector.Storable.Mutable as VM
 import Foreign.Marshal.Alloc (allocaBytes)
@@ -23,35 +22,14 @@
 import System.IO (Handle, openFile, hClose, 
                   IOMode(..), withBinaryFile, hPutBuf, hGetBuf)
 import PCD.Header
+import qualified PCD.Internal.AsciiParsers as A
 import PCD.Internal.StorableFieldType
 import PCD.Internal.Types
 
--- |Read point data using a user-supplied ASCII point parser.
-readAsciiPoints :: (G.Vector v a) => 
-                   Header -> Handle -> ATL.Parser a -> IO (v a)
-readAsciiPoints pcd h p = aux <$> TL.hGetContents h
-  where n = fromIntegral $ pcd^.points
-        aux t0 = G.create $
-                 do v <- GM.new n
-                    let write = GM.write v
-                        go !i !t
-                          | i == n = return v
-                          | otherwise = case ATL.parse p t of
-                                          ATL.Done !t' !pt -> write i pt >> 
-                                                              go (i+1) t'
-                                          ATL.Fail _ _ msg -> error msg
-                    go 0 t0
-
--- |Load points of unknown dimension into a boxed vector with a list
--- of 'FieldType' as the point representation.
-readAsciiPointsDefault :: Header -> Handle -> IO (B.Vector (B.Vector FieldType))
-readAsciiPointsDefault pcd h = readAsciiPoints pcd h $ 
-                               B.fromList <$> pointParser pcd
-
 -- |Read back 'Storable' points saved as binary data.
-readHomogenousBinaryPoints :: forall a. Storable a => 
+readStorableBinaryPoints :: forall a. Storable a => 
                               Header -> Handle -> IO (Either String (Vector a))
-readHomogenousBinaryPoints pcd h
+readStorableBinaryPoints pcd h
   | ptSize /= sz = return . Left $ 
                    "Deserialization type is not the same size as the points "++
                    "described by this file. The PCD file dicates "++
@@ -71,20 +49,9 @@
 readPointData :: Storable a => 
                  Header -> Handle -> ATL.Parser a -> 
                  IO (Either String (Vector a))
-readPointData hd h pa 
-  | hd^.format == ASCII = readAsciiPoints hd h pa >>= return . Right
-  | otherwise = readHomogenousBinaryPoints hd h
-
--- |Parse 3D points serialized in ASCII.
-readXYZ_ascii :: Fractional a => ATL.Parser (V3 a)
-readXYZ_ascii = (\[x,y,z] -> V3 x y z) <$> 
-                count 3 ((realToFrac <$> double) <* skipSpace)
-
--- |Parse 4D points serialized to ASCII. This is useful for points
--- with X,Y,Z, and RGB fields each represented by a single float.
-readXYZW_ascii :: Fractional a => ATL.Parser (V4 a)
-readXYZW_ascii = (\[x,y,z,w] -> V4 x y z w) <$>
-                 count 4 ((realToFrac <$> double) <* skipSpace)
+readPointData header handle parser
+  | header^.format == ASCII = Right <$> A.readPoints header handle parser
+  | otherwise = readStorableBinaryPoints header handle
 
 -- |Use an existing PCD header to save binary point data to a
 -- file. The supplied header is used as-is, except that its format is
@@ -99,26 +66,12 @@
      withBinaryFile outputFile AppendMode $ \h ->
        V.unsafeWith pts (flip (hPutBuf h) sz)
 
--- |@asciiToBinary inputFile outputFile@ converts a PCD file from
--- ASCII to Binary.
-asciiToBinary :: FilePath -> FilePath -> IO ()
-asciiToBinary i o = do h <- openFile i ReadMode
-                       (pcdh,_) <- readHeader h
-                       pcdh `deepseq` print pcdh
-                       when (pcdh^.format /= ASCII)
-                            (error "Input PCD is already binary!")
-                       let numBytes = totalBinarySize pcdh
-                       putStrLn $ "Expecting to generate "++show numBytes++" bytes"
-                       v <- readAsciiPointsDefault pcdh h
-                       putStrLn $ "Parsed "++show (B.length v)++" ASCII points"
-                       hClose h
-                       T.writeFile o (writeHeader (format .~ Binary $ pcdh))
-                       withBinaryFile o AppendMode $ \h' ->
-                         allocaBytes numBytes $ \ptr ->
-                           pokeBinaryPoints ptr v >>
-                           hPutBuf h' ptr numBytes
-
--- |Save a binary PCD file including only the named fields.
+-- |Save a binary PCD file including only the named fields. This is
+-- useful when you have a PCD file that has more fields for each point
+-- than you care about. For instance, you may wish to extract just the
+-- \"x\", \"y\", and \"z\" fields for each point. This can be
+-- accomplished using, @projectBinaryFields [\"x\", \"y\", \"z\"]
+-- inputFile outputFile@.
 projectBinaryFields :: [Text] -> FilePath -> FilePath -> IO ()
 projectBinaryFields fs i o = 
   do h <- openFile i ReadMode
@@ -142,7 +95,10 @@
 -- |Load points stored in a PCD file into a 'Vector'. This requires a
 -- 'Storable' instance for the type used to represent a point. If the
 -- point is a monotyped collection of fields, consider using
--- 'Linear.V2.V2' or 'Linear.V3.V3' to represent points.
+-- 'Linear.V2', 'Linear.V3', or 'Linear.V4' to represent points. When
+-- using a representation from the @linear@ package, you may wish to
+-- use 'loadXyz' or 'loadXyzw' which can handle ASCII and Binary
+-- serializations of 3D or 4D points.
 loadPoints :: Storable a => ATL.Parser a -> FilePath -> IO (Vector a)
 loadPoints parser pcdFile = do h <- openFile pcdFile ReadMode
                                (pcdh,_) <- readHeader h
@@ -153,7 +109,7 @@
 -- |Read a PCD file consisting of floating point XYZ coordinates for
 -- each point.
 loadXyz :: (Fractional a, Storable a) => FilePath -> IO (Vector (V3 a))
-loadXyz = loadPoints readXYZ_ascii
+loadXyz = loadPoints A.readXYZ
 {-# SPECIALIZE loadXyz :: FilePath -> IO (Vector (V3 Float)) #-}
 {-# SPECIALIZE loadXyz :: FilePath -> IO (Vector (V3 Double)) #-}
 
@@ -161,26 +117,25 @@
 -- each point (where the final \"W\" field may be an RGB triple
 -- encoded as a float).
 loadXyzw :: (Fractional a, Storable a) => FilePath -> IO (Vector (V4 a))
-loadXyzw = loadPoints readXYZW_ascii
+loadXyzw = loadPoints A.readXYZW
 {-# SPECIALIZE loadXyzw :: FilePath -> IO (Vector (V4 Float)) #-}
 {-# SPECIALIZE loadXyzw :: FilePath -> IO (Vector (V4 Double)) #-}
 
+-- |Load a 'B.Vector' of points, each represented as a 'B.Vector' of
+-- 'FieldType' fields. If you wish to use field names to access to the
+-- data, consider using 'loadFieldsByName'.
 loadFlexiblePoints :: Header -> Handle -> IO (B.Vector (B.Vector FieldType))
 loadFlexiblePoints pcdh h
   | pcdh ^. format == Binary = parseBinaryPoints pcdh h
-  | otherwise = readAsciiPointsDefault pcdh h
+  | otherwise = A.readPointsDefault pcdh h
 
 -- |Parse every field of every point in a PCD file. Returns a function
 -- that may be used to project out a named field.
-loadAllFields :: FilePath -> IO (Text -> B.Vector FieldType)
-loadAllFields f = do h <- openFile f ReadMode
-                     (pcdh,_) <- readHeader h
-                     (mkProjector pcdh <$>
-                       if pcdh ^. format == ASCII
-                         then readAsciiPoints pcdh h 
-                                              (B.fromList <$> pointParser pcdh)
-                         else parseBinaryPoints pcdh h)
-                       <* hClose h
+loadFieldsByName :: FilePath -> IO (Text -> B.Vector FieldType)
+loadFieldsByName f = do h <- openFile f ReadMode
+                        (pcdh,_) <- readHeader h
+                        (mkProjector pcdh <$> loadFlexiblePoints pcdh h) 
+                          <* hClose h
   where mkProjector :: Header -> B.Vector (B.Vector FieldType) -> 
                        (Text -> B.Vector FieldType)
         mkProjector h pts = let fieldNames = B.fromList $ h ^. fields
diff --git a/src/PCD/Header.hs b/src/PCD/Header.hs
--- a/src/PCD/Header.hs
+++ b/src/PCD/Header.hs
@@ -124,7 +124,8 @@
 -- |Make a PCD header for a monotyped vector point
 -- type. @mkSimpleHeader fields (type,sz) n@ prepares a 'Header' for
 -- @n@ points with field names @fields@, field type given by @type@,
--- and field size given by @sz@. Example to save 1000 3D points:
+-- and field size given by @sz@. Example to save 1000 3D points using
+-- a single-precision floating point number (4 bytes) for each field:
 --
 -- > mkSimpleHeader ["x","y","z"] (F,4) 1000
 mkSimpleHeader :: [Text] -> (DimType,Int) -> Int -> Header
diff --git a/src/PCD/Internal/AsciiParsers.hs b/src/PCD/Internal/AsciiParsers.hs
new file mode 100644
--- /dev/null
+++ b/src/PCD/Internal/AsciiParsers.hs
@@ -0,0 +1,46 @@
+{-# LANGUAGE BangPatterns #-}
+module PCD.Internal.AsciiParsers where
+import Control.Applicative
+import Control.Lens ((^.))
+import Data.Attoparsec.Text (double, count, skipSpace)
+import qualified Data.Attoparsec.Text.Lazy as ATL
+import qualified Data.Text.Lazy.IO as TL
+import qualified Data.Vector as B
+import qualified Data.Vector.Generic as G
+import qualified Data.Vector.Generic.Mutable as GM
+import System.IO (Handle)
+
+import PCD.Header (Header, FieldType, pointParser, points)
+import PCD.Internal.Types (V3(..), V4(..))
+
+-- |Read point data using a user-supplied ASCII point parser.
+readPoints :: (G.Vector v a) => 
+                   Header -> Handle -> ATL.Parser a -> IO (v a)
+readPoints pcd h p = aux <$> TL.hGetContents h
+  where n = fromIntegral $ pcd^.points
+        aux t0 = G.create $
+                 do v <- GM.new n
+                    let write = GM.write v
+                        go !i !t
+                          | i == n = return v
+                          | otherwise = case ATL.parse p t of
+                                          ATL.Done !t' !pt -> write i pt >> 
+                                                              go (i+1) t'
+                                          ATL.Fail _ _ msg -> error msg
+                    go 0 t0
+
+-- |Load points of arbitrary dimension into a boxed vector with a
+-- 'B.Vector' of 'FieldType' as the point representation.
+readPointsDefault :: Header -> Handle -> IO (B.Vector (B.Vector FieldType))
+readPointsDefault pcd h = readPoints pcd h $ B.fromList <$> pointParser pcd
+
+-- |Parse 3D points serialized in ASCII.
+readXYZ :: Fractional a => ATL.Parser (V3 a)
+readXYZ = (\[x,y,z] -> V3 x y z) <$> 
+          count 3 ((realToFrac <$> double) <* skipSpace)
+
+-- |Parse 4D points serialized to ASCII. This is useful for points
+-- with X,Y,Z, and RGB fields each represented by a single float.
+readXYZW :: Fractional a => ATL.Parser (V4 a)
+readXYZW = (\[x,y,z,w] -> V4 x y z w) <$>
+           count 4 ((realToFrac <$> double) <* skipSpace)
diff --git a/src/executable/Main.hs b/src/executable/Main.hs
--- a/src/executable/Main.hs
+++ b/src/executable/Main.hs
@@ -1,7 +1,8 @@
 {-# LANGUAGE OverloadedStrings #-}
 module Main (main) where
 import System.Environment (getArgs)
-import PCD.Data (asciiToBinary, projectBinaryFields)
+import PCD.Conversion (asciiToBinary)
+import PCD.Data (projectBinaryFields)
 
 data Args = Args { _inputFile  :: FilePath
                  , _outputFile :: FilePath
diff --git a/tests/SampleData.hs b/tests/SampleData.hs
new file mode 100644
--- /dev/null
+++ b/tests/SampleData.hs
@@ -0,0 +1,232 @@
+{-# LANGUAGE OverloadedStrings, QuasiQuotes #-}
+-- |Sample PCD file from http://pointclouds.org/documentation/tutorials/pcd_file_format.php
+module SampleData where
+import Data.String.QQ
+import Data.Text (Text)
+
+sampleData :: Text
+sampleData = [s|
+# .PCD v.7 - Point Cloud Data file format
+VERSION .7
+FIELDS x y z rgb
+SIZE 4 4 4 4
+TYPE F F F F
+COUNT 1 1 1 1
+WIDTH 213
+HEIGHT 1
+VIEWPOINT 0 0 0 1 0 0 0
+POINTS 213
+DATA ascii
+0.93773 0.33763 0 4.2108e+06
+0.90805 0.35641 0 4.2108e+06
+0.81915 0.32 0 4.2108e+06
+0.97192 0.278 0 4.2108e+06
+0.944 0.29474 0 4.2108e+06
+0.98111 0.24247 0 4.2108e+06
+0.93655 0.26143 0 4.2108e+06
+0.91631 0.27442 0 4.2108e+06
+0.81921 0.29315 0 4.2108e+06
+0.90701 0.24109 0 4.2108e+06
+0.83239 0.23398 0 4.2108e+06
+0.99185 0.2116 0 4.2108e+06
+0.89264 0.21174 0 4.2108e+06
+0.85082 0.21212 0 4.2108e+06
+0.81044 0.32222 0 4.2108e+06
+0.74459 0.32192 0 4.2108e+06
+0.69927 0.32278 0 4.2108e+06
+0.8102 0.29315 0 4.2108e+06
+0.75504 0.29765 0 4.2108e+06
+0.8102 0.24399 0 4.2108e+06
+0.74995 0.24723 0 4.2108e+06
+0.68049 0.29768 0 4.2108e+06
+0.66509 0.29002 0 4.2108e+06
+0.69441 0.2526 0 4.2108e+06
+0.62807 0.22187 0 4.2108e+06
+0.58706 0.32199 0 4.2108e+06
+0.52125 0.31955 0 4.2108e+06
+0.49351 0.32282 0 4.2108e+06
+0.44313 0.32169 0 4.2108e+06
+0.58678 0.2929 0 4.2108e+06
+0.53436 0.29164 0 4.2108e+06
+0.59308 0.24134 0 4.2108e+06
+0.5357 0.2444 0 4.2108e+06
+0.50043 0.31235 0 4.2108e+06
+0.44107 0.29711 0 4.2108e+06
+0.50727 0.22193 0 4.2108e+06
+0.43957 0.23976 0 4.2108e+06
+0.8105 0.21112 0 4.2108e+06
+0.73555 0.2114 0 4.2108e+06
+0.69907 0.21082 0 4.2108e+06
+0.63327 0.21154 0 4.2108e+06
+0.59165 0.21201 0 4.2108e+06
+0.52477 0.21491 0 4.2108e+06
+0.49375 0.21006 0 4.2108e+06
+0.4384 0.19632 0 4.2108e+06
+0.43425 0.16052 0 4.2108e+06
+0.3787 0.32173 0 4.2108e+06
+0.33444 0.3216 0 4.2108e+06
+0.23815 0.32199 0 4.808e+06
+0.3788 0.29315 0 4.2108e+06
+0.33058 0.31073 0 4.2108e+06
+0.3788 0.24399 0 4.2108e+06
+0.30249 0.29189 0 4.2108e+06
+0.23492 0.29446 0 4.808e+06
+0.29465 0.24399 0 4.2108e+06
+0.23514 0.24172 0 4.808e+06
+0.18836 0.32277 0 4.808e+06
+0.15992 0.32176 0 4.808e+06
+0.08642 0.32181 0 4.808e+06
+0.039994 0.32283 0 4.808e+06
+0.20039 0.31211 0 4.808e+06
+0.1417 0.29506 0 4.808e+06
+0.20921 0.22332 0 4.808e+06
+0.13884 0.24227 0 4.808e+06
+0.085123 0.29441 0 4.808e+06
+0.048446 0.31279 0 4.808e+06
+0.086957 0.24399 0 4.808e+06
+0.3788 0.21189 0 4.2108e+06
+0.29465 0.19323 0 4.2108e+06
+0.23755 0.19348 0 4.808e+06
+0.29463 0.16054 0 4.2108e+06
+0.23776 0.16054 0 4.808e+06
+0.19016 0.21038 0 4.808e+06
+0.15704 0.21245 0 4.808e+06
+0.08678 0.21169 0 4.808e+06
+0.012746 0.32168 0 4.808e+06
+-0.075715 0.32095 0 4.808e+06
+-0.10622 0.32304 0 4.808e+06
+-0.16391 0.32118 0 4.808e+06
+0.00088411 0.29487 0 4.808e+06
+-0.057568 0.29457 0 4.808e+06
+-0.0034333 0.24399 0 4.808e+06
+-0.055185 0.24185 0 4.808e+06
+-0.10983 0.31352 0 4.808e+06
+-0.15082 0.29453 0 4.808e+06
+-0.11534 0.22049 0 4.808e+06
+-0.15155 0.24381 0 4.808e+06
+-0.1912 0.32173 0 4.808e+06
+-0.281 0.3185 0 4.808e+06
+-0.30791 0.32307 0 4.808e+06
+-0.33854 0.32148 0 4.808e+06
+-0.21248 0.29805 0 4.808e+06
+-0.26372 0.29905 0 4.808e+06
+-0.22562 0.24399 0 4.808e+06
+-0.25035 0.2371 0 4.808e+06
+-0.29941 0.31191 0 4.808e+06
+-0.35845 0.2954 0 4.808e+06
+-0.29231 0.22236 0 4.808e+06
+-0.36101 0.24172 0 4.808e+06
+-0.0034393 0.21129 0 4.808e+06
+-0.07306 0.21304 0 4.808e+06
+-0.10579 0.2099 0 4.808e+06
+-0.13642 0.21411 0 4.808e+06
+-0.22562 0.19323 0 4.808e+06
+-0.24439 0.19799 0 4.808e+06
+-0.22591 0.16041 0 4.808e+06
+-0.23466 0.16082 0 4.808e+06
+-0.3077 0.20998 0 4.808e+06
+-0.3413 0.21239 0 4.808e+06
+-0.40551 0.32178 0 4.2108e+06
+-0.50568 0.3218 0 4.2108e+06
+-0.41732 0.30844 0 4.2108e+06
+-0.44237 0.28859 0 4.2108e+06
+-0.41591 0.22004 0 4.2108e+06
+-0.44803 0.24236 0 4.2108e+06
+-0.50623 0.29315 0 4.2108e+06
+-0.50916 0.24296 0 4.2108e+06
+-0.57019 0.22334 0 4.2108e+06
+-0.59611 0.32199 0 4.2108e+06
+-0.65104 0.32199 0 4.2108e+06
+-0.72566 0.32129 0 4.2108e+06
+-0.75538 0.32301 0 4.2108e+06
+-0.59653 0.29315 0 4.2108e+06
+-0.65063 0.29315 0 4.2108e+06
+-0.59478 0.24245 0 4.2108e+06
+-0.65063 0.24399 0 4.2108e+06
+-0.70618 0.29525 0 4.2108e+06
+-0.76203 0.31284 0 4.2108e+06
+-0.70302 0.24183 0 4.2108e+06
+-0.77062 0.22133 0 4.2108e+06
+-0.41545 0.21099 0 4.2108e+06
+-0.45004 0.19812 0 4.2108e+06
+-0.4475 0.1673 0 4.2108e+06
+-0.52031 0.21236 0 4.2108e+06
+-0.55182 0.21045 0 4.2108e+06
+-0.5965 0.21131 0 4.2108e+06
+-0.65064 0.2113 0 4.2108e+06
+-0.72216 0.21286 0 4.2108e+06
+-0.7556 0.20987 0 4.2108e+06
+-0.78343 0.31973 0 4.2108e+06
+-0.87572 0.32111 0 4.2108e+06
+-0.90519 0.32263 0 4.2108e+06
+-0.95526 0.34127 0 4.2108e+06
+-0.79774 0.29271 0 4.2108e+06
+-0.85618 0.29497 0 4.2108e+06
+-0.79975 0.24326 0 4.2108e+06
+-0.8521 0.24246 0 4.2108e+06
+-0.91157 0.31224 0 4.2108e+06
+-0.95031 0.29572 0 4.2108e+06
+-0.92223 0.2213 0 4.2108e+06
+-0.94979 0.24354 0 4.2108e+06
+-0.78641 0.21505 0 4.2108e+06
+-0.87094 0.21237 0 4.2108e+06
+-0.90637 0.20934 0 4.2108e+06
+-0.93777 0.21481 0 4.2108e+06
+0.22244 -0.0296 0 4.808e+06
+0.2704 -0.078167 0 4.808e+06
+0.24416 -0.056883 0 4.808e+06
+0.27311 -0.10653 0 4.808e+06
+0.26172 -0.10653 0 4.808e+06
+0.2704 -0.1349 0 4.808e+06
+0.24428 -0.15599 0 4.808e+06
+0.19017 -0.025297 0 4.808e+06
+0.14248 -0.02428 0 4.808e+06
+0.19815 -0.037432 0 4.808e+06
+0.14248 -0.03515 0 4.808e+06
+0.093313 -0.02428 0 4.808e+06
+0.044144 -0.02428 0 4.808e+06
+0.093313 -0.03515 0 4.808e+06
+0.044144 -0.03515 0 4.808e+06
+0.21156 -0.17357 0 4.808e+06
+0.029114 -0.12594 0 4.2108e+06
+0.036583 -0.15619 0 4.2108e+06
+0.22446 -0.20514 0 4.808e+06
+0.2208 -0.2369 0 4.808e+06
+0.2129 -0.208 0 4.808e+06
+0.19316 -0.25672 0 4.808e+06
+0.14497 -0.27484 0 4.808e+06
+0.030167 -0.18748 0 4.2108e+06
+0.1021 -0.27453 0 4.808e+06
+0.1689 -0.2831 0 4.808e+06
+0.13875 -0.28647 0 4.808e+06
+0.086993 -0.29568 0 4.808e+06
+0.044924 -0.3154 0 4.808e+06
+-0.0066125 -0.02428 0 4.808e+06
+-0.057362 -0.02428 0 4.808e+06
+-0.0066125 -0.03515 0 4.808e+06
+-0.057362 -0.03515 0 4.808e+06
+-0.10653 -0.02428 0 4.808e+06
+-0.15266 -0.025282 0 4.808e+06
+-0.10653 -0.03515 0 4.808e+06
+-0.16036 -0.037257 0 4.808e+06
+0.0083286 -0.1259 0 4.2108e+06
+0.0007442 -0.15603 0 4.2108e+06
+-0.1741 -0.17381 0 4.808e+06
+-0.18502 -0.02954 0 4.808e+06
+-0.20707 -0.056403 0 4.808e+06
+-0.23348 -0.07764 0 4.808e+06
+-0.2244 -0.10653 0 4.808e+06
+-0.23604 -0.10652 0 4.808e+06
+-0.20734 -0.15641 0 4.808e+06
+-0.23348 -0.13542 0 4.808e+06
+0.0061083 -0.18729 0 4.2108e+06
+-0.066235 -0.27472 0 4.808e+06
+-0.17577 -0.20789 0 4.808e+06
+-0.10861 -0.27494 0 4.808e+06
+-0.15584 -0.25716 0 4.808e+06
+-0.0075775 -0.31546 0 4.808e+06
+-0.050817 -0.29595 0 4.808e+06
+-0.10306 -0.28653 0 4.808e+06
+-0.1319 -0.2831 0 4.808e+06
+-0.18716 -0.20571 0 4.808e+06
+-0.18369 -0.23729 0 4.808e+06|]
diff --git a/tests/SampleLoad.hs b/tests/SampleLoad.hs
new file mode 100644
--- /dev/null
+++ b/tests/SampleLoad.hs
@@ -0,0 +1,44 @@
+-- |Ensure that a sample PCD file can be loaded.
+module Main where
+import Control.Lens ((^.))
+import qualified Data.Text.IO as T
+import qualified Data.Vector as V
+import System.Directory (getTemporaryDirectory)
+import System.IO (openTempFile, hClose, hSeek, SeekMode(..))
+import Test.Framework (defaultMain)
+import Test.Framework.Providers.HUnit (hUnitTestToTests)
+import Test.HUnit (Test(..), (~?), (~=?))
+
+import SampleData
+import PCD.Data
+import PCD.Header
+
+testLen :: Header -> V.Vector (V.Vector FieldType) -> Test
+testLen header pts = TestLabel "Number of fields" $ 
+                     TestList [ 4 ~=? length (header^.fields)
+                              , 4 ~=? V.length (V.head pts) ]
+
+testPts :: Header -> V.Vector a -> Test
+testPts header pts = TestLabel "Number of points" $
+                     TestList [ 213 ~=? header^.points 
+                              , 213 ~=? V.length pts ]
+
+within :: (Num a, Ord a) => a -> a -> a -> Bool
+within delta x y = abs (y - x) < delta
+
+testData :: V.Vector (V.Vector FieldType) -> Test
+testData pts = TestLabel "Point data" $
+               TestList [ within 0.001 (-0.23729) lastY ~? 
+                            "Last point's Y coordinate" ]
+  where lastY = unsafeUnwrap (V.last pts V.! 1) :: Float
+
+main :: IO ()
+main = do d <- getTemporaryDirectory
+          (_, h) <- openTempFile d "pcdTest"
+          T.hPutStr h sampleData
+          hSeek h AbsoluteSeek 0
+          (header, _) <- readHeader h
+          pts <- loadFlexiblePoints header h
+          defaultMain . hUnitTestToTests $
+            TestList [testLen header pts, testPts header pts, testData pts]
+          hClose h
