move unit tests into 'testing' directories
diff --git a/openstack/compute/v2/extensions/secgroups/testing/doc.go b/openstack/compute/v2/extensions/secgroups/testing/doc.go
new file mode 100644
index 0000000..7603f83
--- /dev/null
+++ b/openstack/compute/v2/extensions/secgroups/testing/doc.go
@@ -0,0 +1 @@
+package testing
diff --git a/openstack/compute/v2/extensions/secgroups/testing/fixtures.go b/openstack/compute/v2/extensions/secgroups/testing/fixtures.go
new file mode 100644
index 0000000..8a83ca8
--- /dev/null
+++ b/openstack/compute/v2/extensions/secgroups/testing/fixtures.go
@@ -0,0 +1,303 @@
+package testing
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	th "github.com/gophercloud/gophercloud/testhelper"
+	fake "github.com/gophercloud/gophercloud/testhelper/client"
+)
+
+const rootPath = "/os-security-groups"
+
+const listGroupsJSON = `
+{
+  "security_groups": [
+    {
+      "description": "default",
+      "id": "{groupID}",
+      "name": "default",
+      "rules": [],
+      "tenant_id": "openstack"
+    }
+  ]
+}
+`
+
+func mockListGroupsResponse(t *testing.T) {
+	th.Mux.HandleFunc(rootPath, func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+
+		fmt.Fprintf(w, listGroupsJSON)
+	})
+}
+
+func mockListGroupsByServerResponse(t *testing.T, serverID string) {
+	url := fmt.Sprintf("/servers/%s%s", serverID, rootPath)
+	th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+
+		fmt.Fprintf(w, listGroupsJSON)
+	})
+}
+
+func mockCreateGroupResponse(t *testing.T) {
+	th.Mux.HandleFunc(rootPath, func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		th.TestJSONRequest(t, r, `
+{
+  "security_group": {
+    "name": "test",
+    "description": "something"
+  }
+}
+	`)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+
+		fmt.Fprintf(w, `
+{
+  "security_group": {
+    "description": "something",
+    "id": "{groupID}",
+    "name": "test",
+    "rules": [],
+    "tenant_id": "openstack"
+  }
+}
+`)
+	})
+}
+
+func mockUpdateGroupResponse(t *testing.T, groupID string) {
+	url := fmt.Sprintf("%s/%s", rootPath, groupID)
+	th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "PUT")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		th.TestJSONRequest(t, r, `
+{
+  "security_group": {
+    "name": "new_name",
+		"description": "new_desc"
+  }
+}
+	`)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+
+		fmt.Fprintf(w, `
+{
+  "security_group": {
+    "description": "something",
+    "id": "{groupID}",
+    "name": "new_name",
+    "rules": [],
+    "tenant_id": "openstack"
+  }
+}
+`)
+	})
+}
+
+func mockGetGroupsResponse(t *testing.T, groupID string) {
+	url := fmt.Sprintf("%s/%s", rootPath, groupID)
+	th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+
+		fmt.Fprintf(w, `
+{
+  "security_group": {
+    "description": "default",
+    "id": "{groupID}",
+    "name": "default",
+    "rules": [
+      {
+        "from_port": 80,
+        "group": {
+          "tenant_id": "openstack",
+          "name": "default"
+        },
+        "ip_protocol": "TCP",
+        "to_port": 85,
+        "parent_group_id": "{groupID}",
+        "ip_range": {
+						"cidr": "0.0.0.0"
+				},
+        "id": "{ruleID}"
+      }
+    ],
+    "tenant_id": "openstack"
+  }
+}
+			`)
+	})
+}
+
+func mockGetNumericIDGroupResponse(t *testing.T, groupID int) {
+	url := fmt.Sprintf("%s/%d", rootPath, groupID)
+	th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+
+		fmt.Fprintf(w, `
+{
+	"security_group": {
+		"id": "12345"
+	}
+}
+			`)
+	})
+}
+
+func mockDeleteGroupResponse(t *testing.T, groupID string) {
+	url := fmt.Sprintf("%s/%s", rootPath, groupID)
+	th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "DELETE")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusAccepted)
+	})
+}
+
+func mockAddRuleResponse(t *testing.T) {
+	th.Mux.HandleFunc("/os-security-group-rules", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		th.TestJSONRequest(t, r, `
+{
+  "security_group_rule": {
+    "from_port": 22,
+    "ip_protocol": "TCP",
+    "to_port": 22,
+    "parent_group_id": "{groupID}",
+    "cidr": "0.0.0.0/0"
+  }
+}	`)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+
+		fmt.Fprintf(w, `
+{
+  "security_group_rule": {
+    "from_port": 22,
+    "group": {},
+    "ip_protocol": "TCP",
+    "to_port": 22,
+    "parent_group_id": "{groupID}",
+    "ip_range": {
+      "cidr": "0.0.0.0/0"
+    },
+    "id": "{ruleID}"
+  }
+}`)
+	})
+}
+
+func mockAddRuleResponseICMPZero(t *testing.T) {
+	th.Mux.HandleFunc("/os-security-group-rules", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		th.TestJSONRequest(t, r, `
+{
+  "security_group_rule": {
+    "from_port": 0,
+    "ip_protocol": "ICMP",
+    "to_port": 0,
+    "parent_group_id": "{groupID}",
+    "cidr": "0.0.0.0/0"
+  }
+}	`)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+
+		fmt.Fprintf(w, `
+{
+  "security_group_rule": {
+    "from_port": 0,
+    "group": {},
+    "ip_protocol": "ICMP",
+    "to_port": 0,
+    "parent_group_id": "{groupID}",
+    "ip_range": {
+      "cidr": "0.0.0.0/0"
+    },
+    "id": "{ruleID}"
+  }
+}`)
+	})
+}
+
+func mockDeleteRuleResponse(t *testing.T, ruleID string) {
+	url := fmt.Sprintf("/os-security-group-rules/%s", ruleID)
+	th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "DELETE")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusAccepted)
+	})
+}
+
+func mockAddServerToGroupResponse(t *testing.T, serverID string) {
+	url := fmt.Sprintf("/servers/%s/action", serverID)
+	th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		th.TestJSONRequest(t, r, `
+{
+  "addSecurityGroup": {
+    "name": "test"
+  }
+}
+	`)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusAccepted)
+		fmt.Fprintf(w, `{}`)
+	})
+}
+
+func mockRemoveServerFromGroupResponse(t *testing.T, serverID string) {
+	url := fmt.Sprintf("/servers/%s/action", serverID)
+	th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		th.TestJSONRequest(t, r, `
+{
+  "removeSecurityGroup": {
+    "name": "test"
+  }
+}
+	`)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusAccepted)
+		fmt.Fprintf(w, `{}`)
+	})
+}
diff --git a/openstack/compute/v2/extensions/secgroups/testing/requests_test.go b/openstack/compute/v2/extensions/secgroups/testing/requests_test.go
new file mode 100644
index 0000000..b7ffa20
--- /dev/null
+++ b/openstack/compute/v2/extensions/secgroups/testing/requests_test.go
@@ -0,0 +1,279 @@
+package testing
+
+import (
+	"testing"
+
+	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups"
+	"github.com/gophercloud/gophercloud/pagination"
+	th "github.com/gophercloud/gophercloud/testhelper"
+	"github.com/gophercloud/gophercloud/testhelper/client"
+)
+
+const (
+	serverID = "{serverID}"
+	groupID  = "{groupID}"
+	ruleID   = "{ruleID}"
+)
+
+func TestList(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	mockListGroupsResponse(t)
+
+	count := 0
+
+	err := secgroups.List(client.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		actual, err := secgroups.ExtractSecurityGroups(page)
+		if err != nil {
+			t.Errorf("Failed to extract users: %v", err)
+			return false, err
+		}
+
+		expected := []secgroups.SecurityGroup{
+			{
+				ID:          groupID,
+				Description: "default",
+				Name:        "default",
+				Rules:       []secgroups.Rule{},
+				TenantID:    "openstack",
+			},
+		}
+
+		th.CheckDeepEquals(t, expected, actual)
+
+		return true, nil
+	})
+
+	th.AssertNoErr(t, err)
+	th.AssertEquals(t, 1, count)
+}
+
+func TestListByServer(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	mockListGroupsByServerResponse(t, serverID)
+
+	count := 0
+
+	err := secgroups.ListByServer(client.ServiceClient(), serverID).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		actual, err := secgroups.ExtractSecurityGroups(page)
+		if err != nil {
+			t.Errorf("Failed to extract users: %v", err)
+			return false, err
+		}
+
+		expected := []secgroups.SecurityGroup{
+			{
+				ID:          groupID,
+				Description: "default",
+				Name:        "default",
+				Rules:       []secgroups.Rule{},
+				TenantID:    "openstack",
+			},
+		}
+
+		th.CheckDeepEquals(t, expected, actual)
+
+		return true, nil
+	})
+
+	th.AssertNoErr(t, err)
+	th.AssertEquals(t, 1, count)
+}
+
+func TestCreate(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	mockCreateGroupResponse(t)
+
+	opts := secgroups.CreateOpts{
+		Name:        "test",
+		Description: "something",
+	}
+
+	group, err := secgroups.Create(client.ServiceClient(), opts).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := &secgroups.SecurityGroup{
+		ID:          groupID,
+		Name:        "test",
+		Description: "something",
+		TenantID:    "openstack",
+		Rules:       []secgroups.Rule{},
+	}
+	th.AssertDeepEquals(t, expected, group)
+}
+
+func TestUpdate(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	mockUpdateGroupResponse(t, groupID)
+
+	opts := secgroups.UpdateOpts{
+		Name:        "new_name",
+		Description: "new_desc",
+	}
+
+	group, err := secgroups.Update(client.ServiceClient(), groupID, opts).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := &secgroups.SecurityGroup{
+		ID:          groupID,
+		Name:        "new_name",
+		Description: "something",
+		TenantID:    "openstack",
+		Rules:       []secgroups.Rule{},
+	}
+	th.AssertDeepEquals(t, expected, group)
+}
+
+func TestGet(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	mockGetGroupsResponse(t, groupID)
+
+	group, err := secgroups.Get(client.ServiceClient(), groupID).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := &secgroups.SecurityGroup{
+		ID:          groupID,
+		Description: "default",
+		Name:        "default",
+		TenantID:    "openstack",
+		Rules: []secgroups.Rule{
+			{
+				FromPort:      80,
+				ToPort:        85,
+				IPProtocol:    "TCP",
+				IPRange:       secgroups.IPRange{CIDR: "0.0.0.0"},
+				Group:         secgroups.Group{TenantID: "openstack", Name: "default"},
+				ParentGroupID: groupID,
+				ID:            ruleID,
+			},
+		},
+	}
+
+	th.AssertDeepEquals(t, expected, group)
+}
+
+func TestGetNumericID(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	numericGroupID := 12345
+
+	mockGetNumericIDGroupResponse(t, numericGroupID)
+
+	group, err := secgroups.Get(client.ServiceClient(), "12345").Extract()
+	th.AssertNoErr(t, err)
+
+	expected := &secgroups.SecurityGroup{ID: "12345"}
+	th.AssertDeepEquals(t, expected, group)
+}
+
+func TestDelete(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	mockDeleteGroupResponse(t, groupID)
+
+	err := secgroups.Delete(client.ServiceClient(), groupID).ExtractErr()
+	th.AssertNoErr(t, err)
+}
+
+func TestAddRule(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	mockAddRuleResponse(t)
+
+	opts := secgroups.CreateRuleOpts{
+		ParentGroupID: groupID,
+		FromPort:      22,
+		ToPort:        22,
+		IPProtocol:    "TCP",
+		CIDR:          "0.0.0.0/0",
+	}
+
+	rule, err := secgroups.CreateRule(client.ServiceClient(), opts).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := &secgroups.Rule{
+		FromPort:      22,
+		ToPort:        22,
+		Group:         secgroups.Group{},
+		IPProtocol:    "TCP",
+		ParentGroupID: groupID,
+		IPRange:       secgroups.IPRange{CIDR: "0.0.0.0/0"},
+		ID:            ruleID,
+	}
+
+	th.AssertDeepEquals(t, expected, rule)
+}
+
+func TestAddRuleICMPZero(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	mockAddRuleResponseICMPZero(t)
+
+	opts := secgroups.CreateRuleOpts{
+		ParentGroupID: groupID,
+		FromPort:      0,
+		ToPort:        0,
+		IPProtocol:    "ICMP",
+		CIDR:          "0.0.0.0/0",
+	}
+
+	rule, err := secgroups.CreateRule(client.ServiceClient(), opts).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := &secgroups.Rule{
+		FromPort:      0,
+		ToPort:        0,
+		Group:         secgroups.Group{},
+		IPProtocol:    "ICMP",
+		ParentGroupID: groupID,
+		IPRange:       secgroups.IPRange{CIDR: "0.0.0.0/0"},
+		ID:            ruleID,
+	}
+
+	th.AssertDeepEquals(t, expected, rule)
+}
+
+func TestDeleteRule(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	mockDeleteRuleResponse(t, ruleID)
+
+	err := secgroups.DeleteRule(client.ServiceClient(), ruleID).ExtractErr()
+	th.AssertNoErr(t, err)
+}
+
+func TestAddServer(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	mockAddServerToGroupResponse(t, serverID)
+
+	err := secgroups.AddServer(client.ServiceClient(), serverID, "test").ExtractErr()
+	th.AssertNoErr(t, err)
+}
+
+func TestRemoveServer(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	mockRemoveServerFromGroupResponse(t, serverID)
+
+	err := secgroups.RemoveServer(client.ServiceClient(), serverID, "test").ExtractErr()
+	th.AssertNoErr(t, err)
+}