testing.md 7.31 KB
Newer Older
1 2
# Testing

3
## Overview
4

5 6
We maintain three kinds of tests: unit tests, integration tests,
and acceptance tests.
7

8
### Unit Tests
9

10
* Each test case should be concise: setup, execute, check, and teardown.
11
If you find yourself writing tests with many steps, consider refactoring
12
the unit under tests into smaller units, and then testing those individually.
13

Will Daly committed
14 15
* As a rule of thumb, your unit tests should cover every code branch.

16 17
* Mock or patch external dependencies.
We use [voidspace mock](http://www.voidspace.org.uk/python/mock/).
Jay Zoldak committed
18

19
* We unit test Python code (using [unittest](http://docs.python.org/2/library/unittest.html)) and
20
Javascript (using [Jasmine](http://pivotal.github.io/jasmine/))
Jay Zoldak committed
21

22 23 24
### Integration Tests
* Test several units at the same time.
Note that you can still mock or patch dependencies
25 26
that are not under test!  For example, you might test that
`LoncapaProblem`, `NumericalResponse`, and `CorrectMap` in the
27
`capa` package work together, while still mocking out template rendering.
Jay Zoldak committed
28

29
* Use integration tests to ensure that units are hooked up correctly.
30 31
You do not need to test every possible input--that's what unit
tests are for.  Instead, focus on testing the "happy path"
32
to verify that the components work together correctly.
33

34 35
* Many of our tests use the [Django test client](https://docs.djangoproject.com/en/dev/topics/testing/overview/) to simulate
HTTP requests to the server.
36

37 38 39 40 41 42 43 44 45
### UI Acceptance Tests
* Use these to test that major program features are working correctly.

* We use [lettuce](http://lettuce.it/) to write BDD-style tests.  Most of
these tests simulate user interactions through the browser using
[splinter](http://splinter.cobrateam.info/).

Overall, you want to write the tests that **maximize coverage**
while **minimizing maintenance**.
46 47
In practice, this usually means investing heavily
in unit tests, which tend to be the most robust to changes in the code base.
48

49 50 51 52
![Test Pyramid](test_pyramid.png)

The pyramid above shows the relative number of unit tests, integration tests,
and acceptance tests.  Most of our tests are unit tests or integration tests.
53 54 55

## Test Locations

56
* Python unit and integration tests: Located in
57
subpackages called `tests`.
58
For example, the tests for the `capa` package are located in
59 60 61
`common/lib/capa/capa/tests`.

* Javascript unit tests: Located in `spec` folders.  For example,
62
`common/lib/xmodule/xmodule/js/spec` and `{cms,lms}/static/coffee/spec`
63 64
For consistency, you should use the same directory structure for implementation
and test.  For example, the test for `src/views/module.coffee`
65 66
should be written in `spec/views/module_spec.coffee`.

67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
* UI acceptance tests:
    - Set up and helper methods: `common/djangoapps/terrain`
    - Tests: located in `features` subpackage within a Django app.
    For example: `lms/djangoapps/courseware/features`


## Factories

Many tests delegate set-up to a "factory" class.  For example,
there are factories for creating courses, problems, and users.
This encapsulates set-up logic from tests.

Factories are often implemented using [FactoryBoy](https://readthedocs.org/projects/factoryboy/)

In general, factories should be located close to the code they use.
For example, the factory for creating problem XML definitions
 is located in `common/lib/capa/capa/tests/response_xml_factory.py`
because the `capa` package handles problem XML.


# Running Tests

Before running tests, ensure that you have all the dependencies.  You can install dependencies using:

91
    rake install_prereqs
92 93 94 95 96 97 98 99 100 101 102 103


## Running Python Unit tests

We use [nose](https://nose.readthedocs.org/en/latest/) through
the [django-nose plugin](https://pypi.python.org/pypi/django-nose)
to run the test suite.

You can run tests using `rake` commands.  For example,

    rake test

104
runs all the tests.  It also runs `collectstatic`, which prepares the static files used by the site (for example, compiling Coffeescript to Javascript).
105 106 107 108 109 110 111 112 113 114 115 116 117

You can also run the tests without `collectstatic`, which tends to be faster:

    rake fasttest_lms

or

    rake fasttest_cms

xmodule can be tested independently, with this:

    rake test_common/lib/xmodule

118 119 120 121 122
other module level tests include

* `rake test_common/lib/capa`
* `rake test_common/lib/calc`

123 124
To run a single django test class:

125
    rake test_lms[courseware.tests.tests:testViewAuth]
126 127 128

To run a single django test:

129
    rake test_lms[courseware.tests.tests:TestViewAuth.test_dark_launch]
130 131

To run a single nose test file:
132

133
    nosetests common/lib/xmodule/xmodule/tests/test_stringify.py
Jay Zoldak committed
134

135
To run a single nose test:
Jay Zoldak committed
136

137
    nosetests common/lib/xmodule/xmodule/tests/test_stringify.py:test_stringify
Jay Zoldak committed
138 139


140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
Very handy: if you uncomment the `pdb=1` line in `setup.cfg`, it will drop you into pdb on error.  This lets you go up and down the stack and see what the values of the variables are.  Check out [the pdb documentation](http://docs.python.org/library/pdb.html)

### Running Javascript Unit Tests

These commands start a development server with jasmine testing enabled, and launch your default browser
pointing to those tests

    rake browse_jasmine_{lms,cms}

To run the tests headless, you must install [phantomjs](http://phantomjs.org/download.html), then run:

    rake phantomjs_jasmine_{lms,cms}

If the `phantomjs` binary is not on the path, set the `PHANTOMJS_PATH` environment variable to point to it

    PHANTOMJS_PATH=/path/to/phantomjs rake phantomjs_jasmine_{lms,cms}

157
Once you have run the `rake` command, your browser should open to
158
to `http://localhost/_jasmine/`, which displays the test results.
159

160 161
**Troubleshooting**: If you get an error message while running the `rake` task,
try running `bundle install` to install the required ruby gems.
162 163 164 165 166 167

### Running Acceptance Tests

We use [Lettuce](http://lettuce.it/) for acceptance testing.
Most of our tests use [Splinter](http://splinter.cobrateam.info/)
to simulate UI browser interactions.  Splinter, in turn,
168
uses [Selenium](http://docs.seleniumhq.org/) to control the Chrome browser.
169

170
**Prerequisite**: You must have [ChromeDriver](https://code.google.com/p/selenium/wiki/ChromeDriver)
171 172 173
installed to run the tests in Chrome.  The tests are confirmed to run
with Chrome (not Chromium) version 26.0.0.1410.63 with ChromeDriver
version r195636.
174

175
To run all the acceptance tests:
176

177 178
    rake test_acceptance_lms
    rake test_acceptance_cms
179

180
To test only a specific feature:
181

182
    rake test_acceptance_lms[lms/djangoapps/courseware/features/problems.feature]
183

184 185 186 187 188 189
To start the debugger on failure, add the `--pdb` option:

    rake test_acceptance_lms["lms/djangoapps/courseware/features/problems.feature --pdb"]

To run tests faster by not collecting static files, you can use
`rake fasttest_acceptance_lms` and `rake fasttest_acceptance_cms`.
190

191
**Note**: The acceptance tests can *not* currently run in parallel.
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212

## Viewing Test Coverage

We currently collect test coverage information for Python unit/integration tests.

To view test coverage:

1. Run the test suite:

        rake test

2. Generate reports:

        rake coverage:html

3. HTML reports are located in the `reports` folder.


## Testing using queue servers

When testing problems that use a queue server on AWS (e.g. sandbox-xqueue.edx.org), you'll need to run your server on your public IP, like so.
Jay Zoldak committed
213

214
`django-admin.py runserver --settings=lms.envs.dev --pythonpath=. 0.0.0.0:8000`
Jay Zoldak committed
215

216
When you connect to the LMS, you need to use the public ip.  Use `ifconfig` to figure out the number, and connect e.g. to `http://18.3.4.5:8000/`