diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3c09fb6264871ee7040c3f4215b69d9a8a951e87..09cc79d4c07d7f2f0b32d5c813b293faa50ba145 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -72,10 +72,10 @@ if (WITH_PYTHON_BINDINGS)
   endif (ENABLE_TESTS)
 
   find_package(PythonInterp REQUIRED)
-  find_package(PythonLibs REQUIRED)
-  find_package(Boost REQUIRED COMPONENTS python)
+  find_package(PythonLibs ${PYTHON_VERSION_STRING} EXACT REQUIRED)
+  find_package(Boost REQUIRED COMPONENTS python${PYTHON_VERSION_MAJOR}${PYTHON_VERSION_MINOR})
 
-  # add_subdirectory(pynesci)
+  add_subdirectory(pynesci)
 endif (WITH_PYTHON_BINDINGS)
 
 install(
diff --git a/pynesci/src/CMakeLists.txt b/pynesci/src/CMakeLists.txt
index 0758ac346883c9a681fbd7e93e0e7f11332ab1f8..0c4480487ad6d25c571058520e6f290536ad186c 100644
--- a/pynesci/src/CMakeLists.txt
+++ b/pynesci/src/CMakeLists.txt
@@ -34,8 +34,9 @@ add_python_module(
   HEADERS ${HEADERS}
   PYTHON_SOURCES ${PYTHON_SOURCES}
   INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}
-  LINK_LIBRARIES Boost::python Boost::disable_autolinking conduit
+  LINK_LIBRARIES Boost::python${PYTHON_VERSION_MAJOR}${PYTHON_VERSION_MINOR} Boost::disable_autolinking conduit nesci::nesci
   OUTPUT_DIRECTORY ${PYNESCI_OUTPUT_DIR}
   )
 
+add_subdirectory(testing)
 add_subdirectory(consumer)
diff --git a/pynesci/src/__init__.py b/pynesci/src/__init__.py
index 7140c8b0fea464fa3e96941705c42f34c66c2edd..4e3e5510ae71eb98cb86a6cd2563917e55c0d9a1 100644
--- a/pynesci/src/__init__.py
+++ b/pynesci/src/__init__.py
@@ -19,5 +19,5 @@
 # limitations under the License.
 # -------------------------------------------------------------------------------
 
-from _pynesci import *
-from consumer import *
+from . _pynesci import *
+from . consumer import *
diff --git a/pynesci/src/consumer/CMakeLists.txt b/pynesci/src/consumer/CMakeLists.txt
index 278750c8aa1b7b651d8c610a47464d8ce260dd6b..de0783f26e71a2d21a2373505b3b6922c8788cb6 100644
--- a/pynesci/src/consumer/CMakeLists.txt
+++ b/pynesci/src/consumer/CMakeLists.txt
@@ -34,6 +34,6 @@ add_python_module(
   HEADERS ${HEADERS}
   PYTHON_SOURCES ${PYTHON_SOURCES}
   INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}
-  LINK_LIBRARIES nesci::consumer Boost::python Boost::disable_autolinking
+  LINK_LIBRARIES Boost::python${PYTHON_VERSION_MAJOR}${PYTHON_VERSION_MINOR} Boost::disable_autolinking conduit nesci::nesci nesci::consumer
   OUTPUT_DIRECTORY ${PYCONSUMER_OUTPUT_DIR}
   )
diff --git a/pynesci/src/consumer/__init__.py b/pynesci/src/consumer/__init__.py
index ae847d496435a2bbaa1949a083472bf05ddf2bd0..350db487773e34c5fc1cb991ba804f55e6f6ac80 100644
--- a/pynesci/src/consumer/__init__.py
+++ b/pynesci/src/consumer/__init__.py
@@ -19,4 +19,4 @@
 # limitations under the License.
 # -------------------------------------------------------------------------------
 
-from _pyconsumer import *
+from . _pyconsumer import *
diff --git a/pynesci/src/consumer/device_data_view.hpp b/pynesci/src/consumer/device_data_view.hpp
index e0b48382b40041e393c8a83988a0241c3eb9cfa6..95872b1be8822199d79c2e3e70318bbe0f62bd3b 100644
--- a/pynesci/src/consumer/device_data_view.hpp
+++ b/pynesci/src/consumer/device_data_view.hpp
@@ -21,6 +21,7 @@
 
 #include <string>
 
+#include "boost/python.hpp"
 #include "nesci/consumer/device_data_view.hpp"
 #include "pyconsumer.hpp"
 #include "pynesci/suppress_warnings.hpp"
@@ -30,25 +31,12 @@ namespace consumer {
 
 SUPPRESS_WARNINGS_BEGIN
 
-boost::python::object HasDeviceName(nesci::consumer::DeviceDataView* device) {
-  return static_cast<boost::python::object>(device->HasName());
-}
-
-boost::python::str GetDeviceName(nesci::consumer::DeviceDataView* device) {
-  return static_cast<boost::python::str>(device->GetName()):
-}
-
-DataType GetDeviceType(nesci::consumer::DeviceDataView* device) {
-  return device->GetType();
-}
-
-template <>
-void expose<nesci::consumer::DeviceDataView>() {
+void ExposeDeviceDataView() {
   class_<nesci::consumer::DeviceDataView>("DeviceDataView",
-    init<const conduit::Node& node>())
-      .def("HasDeviceName", &HasName)
-      .def("GetDeviceName", &GetName)
-      .def("GetDeviceType", &GetType);
+                                          init<const conduit::Node*>())
+      .def("HasName", &nesci::consumer::DeviceDataView::HasName)
+      .def("GetDeviceName", &nesci::consumer::DeviceDataView::GetName)
+      .def("GetDeviceType", &nesci::consumer::DeviceDataView::GetType);
 }
 
 SUPPRESS_WARNINGS_END
diff --git a/pynesci/src/consumer/nest_multimeter_data_view.hpp b/pynesci/src/consumer/nest_multimeter_data_view.hpp
index 0a39dccad03f38787a5d4ce86af346d8dd2e12c6..cfd3b7bff022f3bce7d8ace05f0f350036da3267 100644
--- a/pynesci/src/consumer/nest_multimeter_data_view.hpp
+++ b/pynesci/src/consumer/nest_multimeter_data_view.hpp
@@ -30,21 +30,13 @@ namespace consumer {
 
 SUPPRESS_WARNINGS_BEGIN
 
-boost::python::object IsNestMultimeterValid(
-    nesci::consumer::NestMultimeterDataView* multimeter) {
-  return static_cast<boost::python::object>(multimeter->IsValid());
-}
-
-boost::python::object GetNestMultimeterTimestep(
-    nesci::consumer::NestMultimeterDataView* multimeter) {
-  return static_cast<boost::python::object>(multimeter->GetTimestep());
-}
-
-boost::python::object GetNestMultimeterNeuronIds(
+boost::python::list GetNestMultimeterNeuronIds(
     nesci::consumer::NestMultimeterDataView* multimeter) {
   boost::python::list ret_val;
-  for (const auto& data : nest_multimeter->GetNeuronIds()) {
-    ret_val.append(data);
+  const auto neuron_ids = multimeter->GetNeuronIds();
+  for (conduit::index_t i = 0, count = neuron_ids.number_of_elements();
+       i < count; ++i) {
+    ret_val.append(neuron_ids[i]);
   }
   return ret_val;
 }
@@ -52,7 +44,7 @@ boost::python::object GetNestMultimeterNeuronIds(
 boost::python::list GetNestMultimeterIntegerAttributeNames(
     nesci::consumer::NestMultimeterDataView* multimeter) {
   boost::python::list ret_val;
-  for (const auto& data : nest_multimeter->GetIntegerAttributeNames()) {
+  for (const auto& data : multimeter->GetIntegerAttributeNames()) {
     ret_val.append(data);
   }
   return ret_val;
@@ -61,46 +53,52 @@ boost::python::list GetNestMultimeterIntegerAttributeNames(
 boost::python::list GetNestMultimeterFloatingPointAttributeNames(
     nesci::consumer::NestMultimeterDataView* multimeter) {
   boost::python::list ret_val;
-  for (const auto& data : nest_multimeter->GetFloatingPointAttributeNames()) {
+  for (const auto& data : multimeter->GetFloatingPointAttributeNames()) {
     ret_val.append(data);
   }
   return ret_val;
 }
 
 boost::python::list GetNestMultimeterIntegerAttributeValues(
-    nesci::consumer::NestMultimeterDataView* multimeter, 
-    nesci::consumer::NestMultimeterDataView* multimeter, 
+    nesci::consumer::NestMultimeterDataView* multimeter,
     const std::string& attribute) {
   boost::python::list ret_val;
-  for (const auto& data : nest_multimeter->GetIntegerAttributeValues(attribute)) {
-    ret_val.append(data);
+  const auto int_attributes = multimeter->GetIntegerAttributeValues(attribute);
+  for (conduit::index_t i = 0, count = int_attributes.number_of_elements();
+       i < count; ++i) {
+    ret_val.append(int_attributes[i]);
   }
   return ret_val;
 }
 
 boost::python::list GetNestMultimeterFloatingPointAttributeValues(
-    nesci::consumer::NestMultimeterDataView* multimeter, 
+    nesci::consumer::NestMultimeterDataView* multimeter,
     const std::string& attribute) {
   boost::python::list ret_val;
-  for (const auto& data : nest_multimeter->GetFloatingPointAttributeValues(attribute)) {
-    ret_val.append(data);
+  const auto float_attributes =
+      multimeter->GetFloatingPointAttributeValues(attribute);
+  for (conduit::index_t i = 0, count = float_attributes.number_of_elements();
+       i < count; ++i) {
+    ret_val.append(float_attributes[i]);
   }
   return ret_val;
 }
 
-template <>
-void expose<nesci::consumer::NestMultimeterDataView>() {
-  class_<nesci::consumer::NestMultimeterDataView>("NestMultimeterDataView",
-    init<const conduit::Node& node>())
-      .def("IsNestMultimeterValid", &IsValid)
-      .def("GetNestMultimeterTimestep", &GetTimestep)
-      .def("GetNestMultimeterNeuronIds", &GetNeuronIds)
-      .def("GetNestMultimeterIntegerAttributeNames", &GetIntegerAttributeNames)
-      .def("GetNestMultimeterFloatingPointAttributeNames",
-            &GetFloatingPointAttributeNames)
-      .def("GetNestMultimeterIntegerAttributeValues", &GetIntegerAttributeValues)
-      .def("GetNestMultimeterFloatingPointAttributeValues",
-            &GetFloatingPointAttributeValues);
+void ExposeNestMultimeterDataView() {
+  class_<nesci::consumer::NestMultimeterDataView,
+         bases<nesci::consumer::DeviceDataView>>("NestMultimeterDataView",
+                                                 init<const conduit::Node*>())
+      .def("IsValid", &nesci::consumer::NestMultimeterDataView::IsValid)
+      .def("GetTimestep", &nesci::consumer::NestMultimeterDataView::GetTimestep)
+      .def("GetNeuronIds",
+           &nesci::consumer::NestMultimeterDataView::GetNeuronIds)
+      .def("GetIntegerAttributeNames", &GetNestMultimeterNeuronIds)
+      .def("GetFloatingPointAttributeNames",
+           &GetNestMultimeterFloatingPointAttributeNames)
+      .def("GetIntegerAttributeValues",
+           &GetNestMultimeterIntegerAttributeValues)
+      .def("GetFloatingPointAttributeValues",
+           &GetNestMultimeterFloatingPointAttributeValues);
 }
 
 SUPPRESS_WARNINGS_END
diff --git a/pynesci/src/consumer/pyconsumer.cpp b/pynesci/src/consumer/pyconsumer.cpp
index 1bf4c1dc24e37390774733d58a499544e3768ee3..9d26538697c207729105fb6557642a8211f33074 100644
--- a/pynesci/src/consumer/pyconsumer.cpp
+++ b/pynesci/src/consumer/pyconsumer.cpp
@@ -21,9 +21,9 @@
 
 #include "pyconsumer.hpp"
 #include <string>
-#include "nesci/consumer/device_data_view.hpp"
-#include "nesci/consumer/nest_multimeter_data_view.hpp"
-#include "nesci/consumer/spike_detector_data_view.hpp"
+#include "device_data_view.hpp"
+#include "nest_multimeter_data_view.hpp"
+#include "spike_detector_data_view.hpp"
 
 namespace pynesci {
 namespace consumer {
@@ -38,9 +38,9 @@ SUPPRESS_WARNINGS_BEGIN
 BOOST_PYTHON_MODULE(_pyconsumer) {
   def("Greet", &Greet);
   class_<conduit::Node>("Node");
-  expose<nesci::consumer::DeviceDataView>();
-  expose<nesci::consumer::NestMultimeterDataView>();
-  expose<nesci::consumer::SpikeDetectorDataView>();
+  ExposeDeviceDataView();
+  ExposeNestMultimeterDataView();
+  ExposeSpikeDetectorDataView();
 }
 SUPPRESS_WARNINGS_END
 
diff --git a/pynesci/src/consumer/pyconsumer.hpp b/pynesci/src/consumer/pyconsumer.hpp
index 4624a29ceb9d97b9b372ea8ac3a290e21df79722..a0b52dfd951951daa52ad89053d8e7512ffdfdae 100644
--- a/pynesci/src/consumer/pyconsumer.hpp
+++ b/pynesci/src/consumer/pyconsumer.hpp
@@ -41,14 +41,6 @@ using boost::python::no_init;
 using boost::python::pure_virtual;
 using boost::python::scope;
 using boost::python::wrapper;
-
-namespace pynesci {
-namespace consumer {
-
-template <typename T>
-void expose();
-
-}  // namespace consumer
-}  // namespace pynesci
+using boost::python::enum_;
 
 #endif  // PYNESCI_SRC_CONSUMER_PYCONSUMER_HPP_
diff --git a/pynesci/src/consumer/spike_detector_data_view.hpp b/pynesci/src/consumer/spike_detector_data_view.hpp
index 3d74fac796ea55ba351f26f47555efbab046ddba..f44c863f6946f9fa7c0dfe6db81ac14a07b2f1cb 100644
--- a/pynesci/src/consumer/spike_detector_data_view.hpp
+++ b/pynesci/src/consumer/spike_detector_data_view.hpp
@@ -30,36 +30,35 @@ namespace consumer {
 
 SUPPRESS_WARNINGS_BEGIN
 
-boost::python::object IsSpikeDetectorValid(
-    nesci::consumer::DeviceDataView* spike_detector) {
-  return static_cast<boost::python::object>(spike_detector->IsValid());
-}
-
 boost::python::list GetSpikeDetectorTimesteps(
-    nesci::consumer::DeviceDataView* spike_detector) {
+    nesci::consumer::SpikeDetectorDataView* spike_detector) {
   boost::python::list ret_val;
-  for (const auto& data : spike_detector->GetTimesteps()) {
-    ret_val.append(data);
+  const auto timesteps = spike_detector->GetTimesteps();
+  for (conduit::index_t i = 0, count = timesteps.number_of_elements();
+       i < count; ++i) {
+    ret_val.append(timesteps[i]);
   }
   return ret_val;
 }
 
 boost::python::list GetSpikeDetectorNeuronIds(
-nesci::consumer::DeviceDataView* spike_detector) {
+    nesci::consumer::SpikeDetectorDataView* spike_detector) {
   boost::python::list ret_val;
-  for (const auto& data : spike_detector->GetNeuronIds()) {
-    ret_val.append(data);
+  const auto neuron_ids = spike_detector->GetNeuronIds();
+  for (conduit::index_t i = 0, count = neuron_ids.number_of_elements();
+       i < count; ++i) {
+    ret_val.append(neuron_ids[i]);
   }
   return ret_val;
 }
 
-template <>
-void expose<nesci::consumer::SpikeDetectorDataView>() {
-  class_<nesci::consumer::SpikeDetectorDataView>("SpikeDetectorDataView",
-    init<const conduit::Node& node>())
-      .def("IsSpikeDetectorValid", &IsValid)
-      .def("GetSpikeDetectorTimesteps", &GetTimesteps)
-      .def("GetSpikeDetectorNeuronIds", &GetNeuronIds);
+void ExposeSpikeDetectorDataView() {
+  class_<nesci::consumer::SpikeDetectorDataView,
+         bases<nesci::consumer::DeviceDataView>>("SpikeDetectorDataView",
+                                                 init<const conduit::Node*>())
+      .def("IsValid", &nesci::consumer::SpikeDetectorDataView::IsValid)
+      .def("GetTimesteps", &GetSpikeDetectorTimesteps)
+      .def("GetNeuronIds", &GetSpikeDetectorNeuronIds);
 }
 
 SUPPRESS_WARNINGS_END
diff --git a/pynesci/src/pynesci.cpp b/pynesci/src/pynesci.cpp
index 298cfe6dc8a2a56710931e918a3f3c8b1ac5500a..1cc2e4632926ae76bd19459bda004a0489641231 100644
--- a/pynesci/src/pynesci.cpp
+++ b/pynesci/src/pynesci.cpp
@@ -22,6 +22,19 @@
 #include "pynesci.hpp"
 #include <string>
 #include "conduit/conduit_node.hpp"
+#include "nesci/data_type.hpp"
+
+using boost::noncopyable;
+using boost::python::args;
+using boost::python::bases;
+using boost::python::class_;
+using boost::python::def;
+using boost::python::enum_;
+using boost::python::init;
+using boost::python::no_init;
+using boost::python::pure_virtual;
+using boost::python::scope;
+using boost::python::wrapper;
 
 namespace pynesci {
 
@@ -31,7 +44,14 @@ std::string Greet() { return "G'day!"; }
 
 SUPPRESS_WARNINGS_BEGIN
 // cppcheck-suppress unusedFunction
-BOOST_PYTHON_MODULE(_pynesci) { def("Greet", &Greet); }
+BOOST_PYTHON_MODULE(_pynesci) {
+  def("Greet", &Greet);
+
+  enum_<nesci::DataType>("DataType")
+      .value("Other", nesci::DataType::OTHER)
+      .value("NestMultimeter", nesci::DataType::NEST_MULTIMETER)
+      .value("SpikeDetector", nesci::DataType::SPIKE_DETECTOR);
+}
 SUPPRESS_WARNINGS_END
 
 }  // namespace pynesci
diff --git a/pynesci/src/pynesci.hpp b/pynesci/src/pynesci.hpp
index 6209a1467e866dbdb5d08ef02f81ca7c035cb727..f7765fce5195dc38a6f59c6a099de2616efd70b3 100644
--- a/pynesci/src/pynesci.hpp
+++ b/pynesci/src/pynesci.hpp
@@ -31,22 +31,5 @@ SUPPRESS_WARNINGS_BEGIN
 #include "boost/python.hpp"
 SUPPRESS_WARNINGS_END
 
-using boost::noncopyable;
-using boost::python::args;
-using boost::python::bases;
-using boost::python::class_;
-using boost::python::def;
-using boost::python::init;
-using boost::python::no_init;
-using boost::python::pure_virtual;
-using boost::python::scope;
-using boost::python::wrapper;
-
-namespace pynesci {
-
-template <typename T>
-void expose();
-
-}  // namespace pynesci
 
 #endif  // PYNESCI_SRC_PYNESCI_HPP_
diff --git a/pynesci/src/testing/CMakeLists.txt b/pynesci/src/testing/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..0db67d364376aa6edd3170e545af00c546d148a1
--- /dev/null
+++ b/pynesci/src/testing/CMakeLists.txt
@@ -0,0 +1,39 @@
+# -------------------------------------------------------------------------------
+# nesci -- neuronal simulator conan interface
+#
+# Copyright (c) 2018 RWTH Aachen University, Germany,
+# Virtual Reality & Immersive Visualization Group.
+# -------------------------------------------------------------------------------
+#                                  License
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# -------------------------------------------------------------------------------
+
+file(GLOB SOURCES *.cpp)
+file(GLOB HEADERS *.hpp)
+file(GLOB PYTHON_SOURCES *.py)
+
+set(PYNESCI_TESTING_OUTPUT_DIR
+  ${CMAKE_CURRENT_BINARY_DIR}/../../pynesci/testing
+  CACHE PATH "Output path for pynesci python module"
+  )
+
+add_python_module(
+  NAME _pynesci_testing
+  SOURCES ${SOURCES}
+  HEADERS ${HEADERS}
+  PYTHON_SOURCES ${PYTHON_SOURCES}
+  INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}
+  LINK_LIBRARIES Boost::python${PYTHON_VERSION_MAJOR}${PYTHON_VERSION_MINOR} Boost::disable_autolinking conduit nesci::nesci nesci::testing
+  OUTPUT_DIRECTORY ${PYNESCI_TESTING_OUTPUT_DIR}
+  )
diff --git a/pynesci/src/testing/__init__.py b/pynesci/src/testing/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..21638a9ea3c5aca6dc7ec9fbab106f5100b374c0
--- /dev/null
+++ b/pynesci/src/testing/__init__.py
@@ -0,0 +1,22 @@
+# -------------------------------------------------------------------------------
+# nesci -- neuronal simulator conan interface
+#
+# Copyright (c) 2018 RWTH Aachen University, Germany,
+# Virtual Reality & Immersive Visualization Group.
+# -------------------------------------------------------------------------------
+#                                  License
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# -------------------------------------------------------------------------------
+
+from . _pynesci_testing import *
diff --git a/pynesci/src/testing/pynesci_testing.cpp b/pynesci/src/testing/pynesci_testing.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b64c271c36e43fbd7f0bf4214a29455c8c2cf2d3
--- /dev/null
+++ b/pynesci/src/testing/pynesci_testing.cpp
@@ -0,0 +1,93 @@
+//------------------------------------------------------------------------------
+// nesci -- neuronal simulator conan interface
+//
+// Copyright (c) 2017-2018 RWTH Aachen University, Germany,
+// Virtual Reality & Immersive Visualisation Group.
+//------------------------------------------------------------------------------
+//                                 License
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//------------------------------------------------------------------------------
+
+#include "pynesci/suppress_warnings.hpp"
+SUPPRESS_WARNINGS_BEGIN
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#if __GNUC__ >= 7
+#pragma GCC diagnostic ignored "-Wregister"
+#endif
+#include "boost/python.hpp"
+SUPPRESS_WARNINGS_END
+#include "nesci/testing/data.hpp"
+
+namespace pynesci {
+namespace testing {
+
+using boost::noncopyable;
+using boost::python::args;
+using boost::python::bases;
+using boost::python::class_;
+using boost::python::def;
+using boost::python::enum_;
+using boost::python::init;
+using boost::python::list;
+using boost::python::no_init;
+using boost::python::pure_virtual;
+using boost::python::scope;
+using boost::python::wrapper;
+
+SUPPRESS_WARNINGS_BEGIN
+
+template <typename T>
+list VectorToList(const std::vector<T>& vector) {
+  list python_list;
+  for (const auto& value : vector) {
+    python_list.append(value);
+  }
+  return python_list;
+}
+
+// cppcheck-suppress unusedFunction
+BOOST_PYTHON_MODULE(_pynesci_testing) {
+  class_<conduit::Node>("Node");
+
+  scope().attr("ANY_MULTIMETER_NAME") = nesci::testing::ANY_MULTIMETER_NAME;
+  scope().attr("ANY_DOUBLE_ATTRIBUTES") =
+      VectorToList(nesci::testing::ANY_DOUBLE_ATTRIBUTES);
+  scope().attr("ANY_LONG_ATTRIBUTES") =
+      VectorToList(nesci::testing::ANY_LONG_ATTRIBUTES);
+  scope().attr("ANY_TIME") = nesci::testing::ANY_TIME;
+  scope().attr("OTHER_TIME") = nesci::testing::OTHER_TIME;
+  scope().attr("ANY_ID") = nesci::testing::ANY_ID;
+  scope().attr("OTHER_ID") = nesci::testing::OTHER_ID;
+
+  scope().attr("ANY_DOUBLE_VALUES") =
+      VectorToList(nesci::testing::ANY_DOUBLE_VALUES);
+  scope().attr("OTHER_DOUBLE_VALUES") =
+      VectorToList(nesci::testing::OTHER_DOUBLE_VALUES);
+  scope().attr("ANY_LONG_VALUES") =
+      VectorToList(nesci::testing::ANY_LONG_VALUES);
+  scope().attr("OTHER_LONG_VALUES") =
+      VectorToList(nesci::testing::OTHER_LONG_VALUES);
+  scope().attr("ANY_SPIKE_DETECTOR_NAME") =
+      nesci::testing::ANY_SPIKE_DETECTOR_NAME;
+
+  scope().attr("NestMultimeterDataNode") =
+      nesci::testing::CreateNestMultimeterDataNode();
+
+  scope().attr("SpikeDetectorDataNode") =
+      nesci::testing::CreateSpikeDetectorDataNode();
+}
+SUPPRESS_WARNINGS_END
+
+}  // namespace testing
+}  // namespace pynesci
diff --git a/pynesci/tests/test_pynesci.py b/pynesci/tests/test_pynesci.py
index a45c5e7c9b71815a05c4a5293e0f0954903d7708..5d1dd1ce0d5a9de8eec9e79406e2d14f2580d7bc 100644
--- a/pynesci/tests/test_pynesci.py
+++ b/pynesci/tests/test_pynesci.py
@@ -25,6 +25,11 @@ import pynesci
 def test_pynesci_greet():
     assert pynesci.Greet() == "G'day!"
 
+def test_pynesci_data_type():
+    assert pynesci.DataType.Other == 0
+    assert pynesci.DataType.SpikeDetector == 1
+    assert pynesci.DataType.NestMultimeter == 2
+
 
 def test_pynesci_consumer_greet():
     assert pynesci.consumer.Greet() == "G'day!"
diff --git a/pynesci/tests/test_pynesci_consumer_device_data_view.py b/pynesci/tests/test_pynesci_consumer_device_data_view.py
index d0a65674ca87777809b9a533dd35eb8fec84df2e..adbc9aca896c2fa658456944c053ff079b874858 100644
--- a/pynesci/tests/test_pynesci_consumer_device_data_view.py
+++ b/pynesci/tests/test_pynesci_consumer_device_data_view.py
@@ -20,12 +20,13 @@
 # -------------------------------------------------------------------------------
 
 import pynesci
+import pynesci.testing
 
 
 def test_pynesci_consumer_device_data_view():
-    device = pynesci.consumer.DeviceDataView("SomeDeviceName")
+    device = pynesci.consumer.DeviceDataView(pynesci.testing.SpikeDetectorDataNode)
 
 
 def test_pynesci_consumer_device_data_view_get_device_name():
-    device = pynesci.consumer.DeviceDataView("SomeDeviceName")
-    assert device.GetDeviceName() == "SomeDeviceName"
+    device = pynesci.consumer.DeviceDataView(pynesci.testing.SpikeDetectorDataNode)
+    assert device.GetDeviceName() == pynesci.testing.ANY_SPIKE_DETECTOR_NAME
diff --git a/pynesci/tests/test_pynesci_consumer_nest_multimeter_data_view.py b/pynesci/tests/test_pynesci_consumer_nest_multimeter_data_view.py
index ce8244ce943e208703a1599a3bbbed6111fdbb77..5c0bd4a90039bc7cb83130cd2fa730fd03649b08 100644
--- a/pynesci/tests/test_pynesci_consumer_nest_multimeter_data_view.py
+++ b/pynesci/tests/test_pynesci_consumer_nest_multimeter_data_view.py
@@ -21,39 +21,6 @@
 
 import pynesci
 
-
 def test_pynesci_consumer_nest_multimeter_data_view():
-    multimeter = pynesci.consumer.NestMultimeterDataView("SomeNestMultimeterName")
-
-
-def test_pynesci_consumer_nest_multimeter_data_view_is_valid():
-    multimeter = pynesci.consumer.NestMultimeterDataView("SomeNestMultimeterName")
-    assert multimeter.IsNestMultimeterValid()
-
-
-def test_pynesci_consumer_nest_multimeter_data_view_get_timestep_data():
-    multimeter = pynesci.consumer.NestMultimeterDataView("SomeNestMultimeterName")
-    some_node = pynesci.Node()
-    multimeter.SetNode(some_node)
-    assert len(multimeter.GetTimestepData("0.0", "Attribute1")) == 0
-
-
-def test_pynesci_consumer_nest_multimeter_data_view_get_neuron_ids():
-    multimeter = pynesci.consumer.NestMultimeterDataView("SomeNestMultimeterName")
-    some_node = pynesci.Node()
-    multimeter.SetNode(some_node)
-    multimeter.GetNestMultimeterNeuronIds()
-
-
-def test_pynesci_consumer_nest_multimeter_data_view_get_attributes():
-    multimeter = pynesci.consumer.NestMultimeterDataView("SomeNestMultimeterName")
-    some_node = pynesci.Node()
-    multimeter.SetNode(some_node)
-    assert len(multimeter.GetAttributes("0.0")) == 0
-    
-
-def test_pynesci_consumer_nest_multimeter_data_view_get_neuron_ids():
-    multimeter = pynesci.consumer.NestMultimeterDataView("SomeNestMultimeterName")
-    some_node = pynesci.Node()
-    multimeter.SetNode(some_node)
-    assert len(multimeter.GetNestMultimeterNeuronIds("0.0", "Attribute1")) == 0
+    multimeter = pynesci.consumer.NestMultimeterDataView(pynesci.testing.NestMultimeterDataNode)
+    assert multimeter.IsValid()
diff --git a/pynesci/tests/test_pynesci_consumer_spike_detector_data_view.py b/pynesci/tests/test_pynesci_consumer_spike_detector_data_view.py
index fa36a5fc0514b05ea6cef546a5d641337857e48a..22c1f65501a25160c462a1b28a7a32695ae716b7 100644
--- a/pynesci/tests/test_pynesci_consumer_spike_detector_data_view.py
+++ b/pynesci/tests/test_pynesci_consumer_spike_detector_data_view.py
@@ -23,11 +23,6 @@ import pynesci
 
 
 def test_pynesci_consumer_spike_detector_data_view():
-    spike_detector = pynesci.consumer.SpikeDetectorDataView("SomeSpikeDetectorName")
+    spike_detector = pynesci.consumer.SpikeDetectorDataView(pynesci.testing.SpikeDetectorDataNode)
+    spike_detector.IsValid()
 
-
-def test_pynesci_consumer_spike_detector_data_view_get_neuron_ids():
-    spike_detector = pynesci.consumer.SpikeDetectorDataView("SomeSpikeDetectorName")
-    some_node = pynesci.Node()
-    spike_detector.SetNode(some_node)
-    assert len(spike_detector.GetNeuronIds("0.0")) == 0
diff --git a/testing/CMakeLists.txt b/testing/CMakeLists.txt
index ac0c12fbbe84dcdb939d106301c81d9cd7a9c8d4..f8043b8891355d0c107cae3a09f20d181800e717 100644
--- a/testing/CMakeLists.txt
+++ b/testing/CMakeLists.txt
@@ -30,3 +30,9 @@ add_target(
   SKIP_INSTALL
   ${GLOBAL_TARGET_OPTIONS}
 )
+
+if (NOT MSVC)
+  target_compile_options(testing
+    PRIVATE -fPIC
+  )
+endif (NOT MSVC)