-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Section/testing #19
base: master
Are you sure you want to change the base?
Section/testing #19
Conversation
- Added the testing frameworks section src/SUMMARY.md - Created src/testing/testing-frameworks.md and included an introductory paragraph.
- I would like to add a description of all tests that one can consider (death test etc). - I intend to build this up into Test Suites and Test Fixtures, - There should also be a discussion on test runners. This could then also tie into CI.
- Class creates a student with known age and name. - TEST_student_init() checks that a student is appropriately created with a known name and age.
- name is altered before being assigned to the student. - Should provide a simple example of why unit testing can be helpful.
- Initial commit for the what are tests for section. - This section highlights why we might want to use tests. - Included a python example using the quadratic formula. Important to also include examples in other languages. TODO: Update this section with at least an R function. Will also do c++.
- Initial commit of Unit Tests section. - This section gives initial insight into what a unit test is. - Defines unit tests as cheap to run. - Provides insight into how often unit tests should be run (all the time).
- Removed testing-frameworks.md. - This file was split up into multiple sections instead.
- Initial commit of the test driven development section. - This section provides an example of test driven development. - Includes link to an interesting article. TODO: Reorder, I think I should emphasise that in test driven development, the tests are defined first.
- Initial commit of the Test cases section. - This function introduces test cases in python. - This does not use unittests in python yet. - Provides broad functionality of what unit tests should do. - Commented out c++ example. Realised tis was too complicated for an initial include and left for later.
- Initial commit of test runners section. - Brief description of test runner. - Links to continuous integration. - Highlights that sometimes test runners only trigger a subset of tests depending upon flags.
-originally included a line saying the phrase test cases is deprecated in google tests.
- Initial commit of the testing frameworks section. - Just highlights that frameworks to test your code already exist. - This lists unittest and googletest for python and c++ respectively.
- Initial commit of testing suites section. - Highlights that it is important to group similar tests into the one suite. - Emphasises that good testing frameworks allow you to call specific suites as opposed to all tests.
- Initial include of this section. - This commit will assign a git blame to this file. - I want to dump the git blame into a different file when built so that we can observe (interactively) the process of git blame.
- Initial commit of testing stochastic models - Highlighted two approaches to test non-deterministic functions. - First approach is statistical approach (large number of repeats) - Second approach is reproducible sequence of random numbers.
- Initial commit of integration tests. - Starts to outline what a unit test is.
- The contents page will now link to the appropriate sections in the testing framework section.
…ameworks - Fixed merge conflict within src/README.md, rearranged section headings.
- Provide tip about unit tests for the newly found bug. - Removed the git blame file as it was causing infinite recursion. Simple fix, we just cannot nest the git blame of this file. - This could prove helpful for demonstrating some bugs later.
- Initial commit of regression tests section. - Large amount of work is still to be done. This is a place holder
- The link to continuous integration is now within text as opposed to by itself at the bottom.
- Initial commit of the test fixtures section. - Highlights why test fixtures should be used. - Uses a Square class with different member functions to emphasise different tests and their names - TODO: needs to include an example of how to use a fixture. My familiarity is only with googletest.
Hi Eamon, thanks very much for putting this together! I've made one pass through this section, and had a few initial (superficial) thoughts:
I also have a few thoughts about the first chapter (What are tests for?):
|
Hmm ... can we come up with a relatively simple example of an integration test that demonstrates why unit tests are insufficient? The only thing that comes to mind at the moment is having 2 or 3 low-level (and fully independent) functions, and a top-level function that uses these low-level functions, but the output of one low-level function isn't in an appropriate form to be an input for another low-level function. That sounds confusing, but I mean something like: #!/usr/bin/env python3
"""
A stochastic implementation of the SIS compartmental model.
"""
import numpy as np
def event_prob(mean_rate, size, dt):
"""
Return the probability of an individual leaving a compartment.
:param mean_rate: The mean rate at which people leave the compartment.
:param size: The number of people in the compartment.
:param dt: The time step size.
"""
indiv_rate = mean_rate / size
return -np.expm1(-dt * indiv_rate)
def sample_events(prob, size, rng):
"""
Return a random sample of individuals who leave a compartment.
:param prob: The probability of an individual leaving the compartment.
:param size: The number of people in the compartment.
:param rng: The pseudo-random number generator.
"""
return rng.binomial(size, prob)
def sis_model(beta=1.0, gamma=0.5, N=20, dt=0.1, steps=100, seed=12345):
"""
Run a stochastic SIS simulation, starting with one infectious individual,
and return the model state history as a list of tuples ``(S(t), I(t))``.
:param beta: The daily force of infection from an individual.
:param gamma: The recovery rate (inverse of infectious period).
:param N: The population size.
:param dt: The time step size.
:param steps: The number of time steps.
:param seed: The seed for the pseudo-random number generator.
"""
current_state = (N - 1, 1)
state_history = [current_state]
rng = np.random.default_rng(seed=seed)
for time_step in range(steps):
(S, I) = current_state
mean_s_to_i = beta * I * S / N
mean_i_to_s = gamma * I
prob_s_to_i = event_prob(mean_s_to_i, S, dt)
prob_i_to_s = event_prob(mean_i_to_s, I, dt)
s_to_i = sample_events(prob_s_to_i, S, rng)
i_to_s = sample_events(prob_i_to_s, I, rng)
current_state = (S - s_to_i + i_to_s, I - i_to_s + s_to_i)
state_history.append(current_state)
return state_history
if __name__ == "__main__":
print(sis_model(steps=20))
print(sis_model(steps=50)) If you run this for 20 steps, everything looks fine and there's one infection event and one recovery event. But if you run this for 50 steps you encounter a divide by zero error, because What do you think? We should be able to come up with an Edit: ooh, we could use this example to also talk about testing stochastic function, and show example test cases that (a) run many times and check the mean is roughly as expected; and (b) provide a single specific input and check we get the expected stochastic output. 2nd edit: if we did use something like this for an example of unit tests, integration tests, and testing stochastic functions, we should probably make the "Testing stochastic models" chapter come before unit tests and integration tests, because it's a higher-level concept and then we can show unit tests that take both approaches. |
Here's an R equivalent (and I've made some minor changes to the Python version so that they're more similar): #!/usr/bin/env -S Rscript --vanilla
#' Return the probability of an individual leaving a compartment
#'
#' @param mean_rate The mean rate at which people leave the compartment.
#' @param size The number of people in the compartment.
#' @param dt The time step size.
event_prob <- function(mean_rate, size, dt) {
indiv_rate <- mean_rate / size
-expm1(-dt * indiv_rate)
}
#' Return a random sample of individuals who leave a compartment
#'
#' @param prob The probability of an individual leaving the compartment.
#' @param size The number of people in the compartment.
#' @param rng The pseudo-random number generator.
sample_events <- function(prob, size) {
rbinom(1, size, prob)
}
#' Run a stochastic SIS simulation
#'
#' This starts with one infectious individual, and returns the model state
#' history as a list of vectors `(S(t), I(t))`.
#'
#' @param beta The daily force of infection from an individual.
#' @param gamma The recovery rate (inverse of infectious period).
#' @param N The population size.
#' @param dt The time step size.
#' @param steps The number of time steps.
#' @param seed The seed for the pseudo-random number generator.
sis_model <- function(beta = 1.0, gamma = 0.5, N = 20, dt = 0.1, steps = 100, seed = 12345) {
I <- 1
S <- N - I
state_history <- list(c(S, I))
set.seed(seed)
for (time_step in seq(steps)) {
mean_s_to_i <- beta * I * S / N
mean_i_to_s <- gamma * I
prob_s_to_i <- event_prob(mean_s_to_i, S, dt)
prob_i_to_s <- event_prob(mean_i_to_s, I, dt)
s_to_i <- sample_events(prob_s_to_i, S)
i_to_s <- sample_events(prob_i_to_s, I)
S <- S - s_to_i + i_to_s
I <- I - i_to_s + s_to_i
state_history[[length(state_history) + 1]] <- c(S, I)
}
state_history
}
print(sis_model(steps = 5))
print(sis_model(steps = 6)) |
Definitely my mistake here. Forgot to commit that change on my end.
I second this, it will ensure that everything works as expected. I was worried about the code snippets, especially with python as it is very new to me.
Sounds wonderful.
I will work on this section. I agree with the comments, it is a bit of a jump for people that are not familiar with testing. Definitely, will work on the phrasing throughout to ensure that we do not give too much confidence about unit tests. This can definitely be emphasised in other examples too. We should include failures of our tests to show that they can only be as good as the person that writes them. |
Interesting, I actually have not checked. The frameworks I use are not case sensitive so I just used TEST to really emphasise that they are "special." |
I love the idea of using the SIS model. We should definitely use it throughout as I think it will be suitable for part of our target audience. For the 2nd edit comment, I think we will have to careful here to ensure it is not confusing. Should we have a piece on testing code in general before introducing the concepts of unit tests etc? |
I agree, the order in which we introduce topics and concepts is tricky but very important. On reflection, testing stochastic code should probably come later, it's more of an advanced topic, and it relies on general concepts that we should cover earlier, such as unit tests and integration tests. |
@EamonConway if you rebase this on master, it should create a live online preview :) |
On a related note this article has some great tips about testing and reproducibility (and I recently contributed tip S8). |
Noting here that the structure for this section (prior to the MkDocs migration) was:
|
Just checking in to say that rebasing, updating, and fleshing out this PR will be on my radar after the SPECTRUM 2024 meeting @EamonConway |
Initial pull request for the Testing section of Git is my Lab book.
I have included at least an initial outline on each section. I am very interested in getting feed back on the work that has been done and suggestions on where to go. Currently, its very python focused (I did not want to scare people with c++ and googletest even though it is what I am familiar with).
I think we should include R versions of everything that we investigate as it will be the most common language used.