{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
\n", " How to read this lecture...\n", "
\n", "

Code should execute sequentially if run in a Jupyter notebook

\n", " \n", "
\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Packages, Testing, and Continuous Integration" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Contents\n", "\n", "- [Packages, Testing, and Continuous Integration](#Packages,-Testing,-and-Continuous-Integration) \n", " - [Project Setup](#Project-Setup) \n", " - [Project Structure](#Project-Structure) \n", " - [Project Workflow](#Project-Workflow) \n", " - [Unit Testing](#Unit-Testing) \n", " - [Continuous Integration with Travis](#Continuous-Integration-with-Travis) \n", " - [CodeCoverage](#CodeCoverage) \n", " - [Pull Requests to External Julia Projects](#Pull-Requests-to-External-Julia-Projects) \n", " - [Benchmarking](#Benchmarking) \n", " - [Additional Notes](#Additional-Notes) \n", " - [Exercises](#Exercises) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Co-authored with Arnav Sood\n", "\n", "This lecture is about structuring your project as a Julia module, and testing it with tools from GitHub\n", "\n", "Benefits include\n", "\n", "- Specifying dependencies (and their versions), so that your project works across Julia setups and over time \n", "- Being able to load your project’s functions from outside without copy/pasting \n", "- Writing tests that run locally, *and automatically on the GitHub server* \n", "- Having GitHub test your project across operating systems, Julia versions, etc. \n", "\n", "\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Project Setup" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Account Setup" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Travis CI\n", "\n", "As we’ll see later, Travis is a service that automatically tests your project on the GitHub server\n", "\n", "First, we need to make sure that your GitHub account is set up with Travis CI and CodeCov\n", "\n", "As a reminder, make sure you signed up for the GitHub [Student Developer Pack](https://education.github.com/pack/) or Academic Plan _ if eligible\n", "\n", "Navigate to the [Travis website](https://travis-ci.com/) and click “sign up with GitHub.” Supply your credentials\n", "\n", "If you get stuck, see the [Travis tutorial](https://docs.travis-ci.com/user/tutorial/)\n", "\n", "**NOTE** As of May 2018, Travis is deprecating the `travis-ci.org` website. All users should use `travis-ci.com`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### CodeCov\n", "\n", "CodeCov is a service that tells you how expansive your tests are (i.e., how much of your code is untested)\n", "\n", "To sign up, visit the [CodeCov website](http://codecov.io/), and click “sign up.” You should see something like this\n", "\n", "\n", "\n", " \n", "Next, click “add a repository” and *enable private scope* (this allows CodeCov to service your private projects)\n", "\n", "The result should be\n", "\n", "\n", "\n", " \n", "This is all we need for now" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Julia Setup" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [], "source": [ "using InstantiateFromURL\n", "activate_github(\"QuantEcon/QuantEconLectureAllPackages\", tag = \"v0.9.0\") # activate the QuantEcon environment\n", "\n", "using LinearAlgebra, Statistics, Compat # load common packages" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let’s create a *template* for our project\n", "\n", "This specifies metadata like the license we’ll be using (MIT by default), the location (`~/.julia/dev` by default), etc." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Template:\n", " → User: quanteconuser\n", " → Host: github.com\n", " → License: MIT (Jesse Perla 2018)\n", " → Package directory: ~\\.julia\\dev\n", " → Minimum Julia version: v1.0\n", " → SSH remote: No\n", " → Plugins:\n", " • CodeCov:\n", " → Config file: None\n", " → 3 gitignore entries: \"*.jl.cov\", \"*.jl.*.cov\", \"*.jl.mem\"\n", " • TravisCI:\n", " → Config file: Default\n", " → 0 gitignore entries" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "using PkgTemplates\n", "ourTemplate = Template(;user=\"quanteconuser\", plugins = [TravisCI(), CodeCov()])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let’s create a specific project based off this template" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[32m\u001b[1mGenerating\u001b[22m\u001b[39m project ExamplePackage:\n", " C:\\Users\\jlperla\\.julia\\dev\\ExamplePackage\\Project.toml\n", " C:\\Users\\jlperla\\.julia\\dev\\ExamplePackage\\src/ExamplePackage.jl\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "┌ Info: Initialized git repo at C:\\Users\\jlperla\\.julia\\dev\\ExamplePackage\n", "└ @ PkgTemplates C:\\Users\\jlperla\\.julia\\packages\\PkgTemplates\\DyEmW\\src\\generate.jl:22\n", "┌ Info: Set remote origin to https://github.com/quanteconuser/ExamplePackage.jl\n", "└ @ PkgTemplates C:\\Users\\jlperla\\.julia\\packages\\PkgTemplates\\DyEmW\\src\\generate.jl:41\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `C:\\Users\\jlperla\\.julia\\dev\\ExamplePackage\\Project.toml`\n", " \u001b[90m [8dfed614]\u001b[39m\u001b[92m + Test \u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `C:\\Users\\jlperla\\.julia\\dev\\ExamplePackage\\Manifest.toml`\n", " \u001b[90m [2a0f44e3]\u001b[39m\u001b[92m + Base64 \u001b[39m\n", " \u001b[90m [8ba89e20]\u001b[39m\u001b[92m + Distributed \u001b[39m\n", " \u001b[90m [b77e0a4c]\u001b[39m\u001b[92m + InteractiveUtils \u001b[39m\n", " \u001b[90m [8f399da3]\u001b[39m\u001b[92m + Libdl \u001b[39m\n", " \u001b[90m [37e2e46d]\u001b[39m\u001b[92m + LinearAlgebra \u001b[39m\n", " \u001b[90m [56ddb016]\u001b[39m\u001b[92m + Logging \u001b[39m\n", " \u001b[90m [d6f4376e]\u001b[39m\u001b[92m + Markdown \u001b[39m\n", " \u001b[90m [9a3f8284]\u001b[39m\u001b[92m + Random \u001b[39m\n", " \u001b[90m [9e88b42a]\u001b[39m\u001b[92m + Serialization \u001b[39m\n", " \u001b[90m [6462fe0b]\u001b[39m\u001b[92m + Sockets \u001b[39m\n", " \u001b[90m [8dfed614]\u001b[39m\u001b[92m + Test \u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m registry at `C:\\Users\\jlperla\\.julia\\registries\\General`\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m git-repo `https://github.com/JuliaRegistries/General.git`\n", "\u001b[?25l\u001b[2K\u001b[?25h\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `C:\\Users\\jlperla\\.julia\\dev\\ExamplePackage\\Project.toml`\n", "\u001b[90m [no changes]\u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `C:\\Users\\jlperla\\.julia\\dev\\ExamplePackage\\Manifest.toml`\n", " \u001b[90m [2a0f44e3]\u001b[39m\u001b[91m - Base64 \u001b[39m\n", " \u001b[90m [8ba89e20]\u001b[39m\u001b[91m - Distributed \u001b[39m\n", " \u001b[90m [b77e0a4c]\u001b[39m\u001b[91m - InteractiveUtils \u001b[39m\n", " \u001b[90m [8f399da3]\u001b[39m\u001b[91m - Libdl \u001b[39m\n", " \u001b[90m [37e2e46d]\u001b[39m\u001b[91m - LinearAlgebra \u001b[39m\n", " \u001b[90m [56ddb016]\u001b[39m\u001b[91m - Logging \u001b[39m\n", " \u001b[90m [d6f4376e]\u001b[39m\u001b[91m - Markdown \u001b[39m\n", " \u001b[90m [9a3f8284]\u001b[39m\u001b[91m - Random \u001b[39m\n", " \u001b[90m [9e88b42a]\u001b[39m\u001b[91m - Serialization \u001b[39m\n", " \u001b[90m [6462fe0b]\u001b[39m\u001b[91m - Sockets \u001b[39m\n", " \u001b[90m [8dfed614]\u001b[39m\u001b[91m - Test \u001b[39m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "┌ Info: Committed 9 files/directories: src/, Project.toml, Manifest.toml, test/, REQUIRE, README.md, .gitignore, LICENSE, .travis.yml\n", "└ @ PkgTemplates C:\\Users\\jlperla\\.julia\\packages\\PkgTemplates\\DyEmW\\src\\generate.jl:64\n" ] } ], "source": [ "generate(\"ExamplePackage.jl\", ourTemplate)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we navigate to the package directory (shown in the output), we should see something like\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Adding Project to Git\n", "\n", "The next step is to add this project to Git version control\n", "\n", "First, open the repository screen in your account as discussed previously. We’ll want the following settings\n", "\n", "\n", "\n", " \n", "In particular\n", "\n", "- The repo you create should have the same name as the project we added \n", "- We should leave the boxes unchecked for the `README.md`, `LICENSE`, and `.gitignore`, since these are handled by `PkgTemplates` \n", "\n", "\n", "Then, drag and drop your folder from your `~/.julia/dev` directory to GitHub Desktop\n", "\n", "Click the “publish branch” button to upload your files to GitHub\n", "\n", "If you navigate to your git repo (ours is [here](https:https://github.com/quanteconuser/ExamplePackage.jl/)), you should see something like\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Adding Project to Julia Package Manager\n", "\n", "We also want Julia’s package manager to be aware of the project\n", "\n", "First, open a REPL in the newly created project directory, either by noting the path printed above, or by running" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3-element Array{String,1}:\n", " \"C:\\\\Users\\\\jlperla\\\\.julia\" \n", " \"C:\\\\Users\\\\jlperla\\\\AppData\\\\Local\\\\Julia-1.0.2\\\\local\\\\share\\\\julia\"\n", " \"C:\\\\Users\\\\jlperla\\\\AppData\\\\Local\\\\Julia-1.0.2\\\\share\\\\julia\" " ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "DEPOT_PATH" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And navigating to the first element, then the subdirectory `/dev/ExamplePackage`\n", "\n", "Note the lack of `.jl`!\n", "\n", "You can change the path of a Julia REPL by running" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "cd(joinpath(DEPOT_PATH[1], \"dev\", \"ExamplePackage\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then, run" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "] activate" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To get into the main Julia environment, and" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `C:\\Users\\jlperla\\.julia\\environments\\v1.0\\Project.toml`\n", " \u001b[90m [e4ee45a0]\u001b[39m\u001b[92m + ExamplePackage v0.1.0 [`..\\..\\dev\\ExamplePackage`]\u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `C:\\Users\\jlperla\\.julia\\environments\\v1.0\\Manifest.toml`\n", " \u001b[90m [e4ee45a0]\u001b[39m\u001b[92m + ExamplePackage v0.1.0 [`..\\..\\dev\\ExamplePackage`]\u001b[39m\n" ] } ], "source": [ "] dev ." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We see the change reflected in our default package list" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[32m\u001b[1m Status\u001b[22m\u001b[39m `C:\\Users\\jlperla\\.julia\\environments\\v1.0\\Project.toml`\n", " \u001b[90m [c52e3926]\u001b[39m\u001b[37m Atom v0.7.9\u001b[39m\n", " \u001b[90m [a93c6f00]\u001b[39m\u001b[37m DataFrames v0.14.1\u001b[39m\n", " \u001b[90m [5721bf48]\u001b[39m\u001b[37m DataVoyager v0.3.1\u001b[39m\n", " \u001b[90m [0c46a032]\u001b[39m\u001b[37m DifferentialEquations v5.3.1\u001b[39m\n", " \u001b[90m [31c24e10]\u001b[39m\u001b[37m Distributions v0.16.4\u001b[39m\n", " \u001b[90m [e4ee45a0]\u001b[39m\u001b[37m ExamplePackage v0.1.0 [`..\\..\\dev\\ExamplePackage`]\u001b[39m\n", " \u001b[90m [2fe49d83]\u001b[39m\u001b[37m Expectations v1.0.2\u001b[39m\n", " \u001b[90m [7073ff75]\u001b[39m\u001b[37m IJulia v1.14.0\u001b[39m\n", " \u001b[90m [43edad99]\u001b[39m\u001b[37m InstantiateFromURL v0.1.0\u001b[39m\n", " \u001b[90m [c601a237]\u001b[39m\u001b[37m Interact v0.9.0\u001b[39m\n", " \u001b[90m [54eb57ff]\u001b[39m\u001b[37m InteractiveCodeSearch v0.2.1\u001b[39m\n", " \u001b[90m [a98d9a8b]\u001b[39m\u001b[37m Interpolations v0.10.5\u001b[39m\n", " \u001b[90m [e5e0dc1b]\u001b[39m\u001b[37m Juno v0.5.3\u001b[39m\n", " \u001b[90m [ee78f7c6]\u001b[39m\u001b[37m Makie v0.9.0\u001b[39m\n", " \u001b[90m [429524aa]\u001b[39m\u001b[37m Optim v0.17.1\u001b[39m\n", " \u001b[90m [d96e819e]\u001b[39m\u001b[37m Parameters v0.10.1\u001b[39m\n", " \u001b[90m [d2cacc76]\u001b[39m\u001b[37m PerlaTonettiWaugh v0.1.0 [`C:\\Users\\jlperla\\.julia\\dev\\PerlaTonettiWaugh`]\u001b[39m\n", " \u001b[90m [14b8a8f1]\u001b[39m\u001b[37m PkgTemplates v0.3.0\u001b[39m\n", " \u001b[90m [91a5bcdd]\u001b[39m\u001b[37m Plots v0.21.0\u001b[39m\n", " \u001b[90m [fcd29c91]\u001b[39m\u001b[37m QuantEcon v0.15.0\u001b[39m\n", " \u001b[90m [612083be]\u001b[39m\u001b[37m Queryverse v0.1.0\u001b[39m\n", " \u001b[90m [ce6b1742]\u001b[39m\u001b[37m RDatasets v0.6.1\u001b[39m\n", " \u001b[90m [295af30f]\u001b[39m\u001b[37m Revise v0.7.12\u001b[39m\n", " \u001b[90m [112f6efa]\u001b[39m\u001b[37m VegaLite v0.5.0\u001b[39m\n" ] } ], "source": [ "] st" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Using the Package Manager\n", "\n", "Now, from any Julia terminal in the future, we can run" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```none\n", "using ExamplePackage\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To use its exported functions\n", "\n", "We can also get the path to this by running" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```none\n", "using ExamplePackage\n", "pathof(ExamplePackage) # returns path to src/ExamplePackage.jl\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Project Structure\n", "\n", "Let’s unpack the structure of the generated project\n", "\n", "- The first directory, `.git`, holds the version control information \n", "- The `src` directory contains the project’s source code. Currently, it should contain only one file (`ExamplePackage.jl`), which reads " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```none\n", "module ExamplePackage\n", "\n", "greet() = print(\"Hello World!\")\n", "\n", "end # module\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Likewise, the `test` directory should have only one file (`runtests.jl`), which reads: " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```none\n", "using ExamplePackage\n", "using Test\n", "\n", "@testset \"ExamplePackage.jl\" begin\n", " # Write your own tests here.\n", "end\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In particular, the workflow is to export objects we want to test (`using ExamplePackage`), and test them using Julia’s `Test` module\n", "\n", "The other important text files for now are\n", "\n", "- `Project.toml` and `Manifest.toml`, which contain dependency information \n", "- The `.gitignore` file (which may display as an untitled file), which contains files and paths for `git` to ignore " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Project Workflow" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Dependency Management" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Environments\n", "\n", "As before, the TOML files define an *environment* for our project\n", "\n", "Any package operations we execute will be reflected in our `ExamplePackage.jl` directory’s TOML\n", "\n", "Likewise, the only packages Julia knows about are those in the `ExamplePackage.jl` TOML. For example" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "] activate ExamplePackage" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "using QuantEcon # fails, even though QuantEcon is on the machine" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This allows us to share the project with others, who can exactly reproduce the state used to build and test it\n", "\n", "See the [Pkg3 docs](https://docs.julialang.org/en/v1/stdlib/Pkg/) for more information" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Pkg Operations\n", "\n", "For now, let’s just try adding a dependency" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `C:\\Users\\jlperla\\.julia\\dev\\ExamplePackage\\Project.toml`\n", " \u001b[90m [2fe49d83]\u001b[39m\u001b[92m + Expectations v1.0.2\u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `C:\\Users\\jlperla\\.julia\\dev\\ExamplePackage\\Manifest.toml`\n", " \u001b[90m [7d9fca2a]\u001b[39m\u001b[92m + Arpack v0.3.0\u001b[39m\n", " \u001b[90m [9e28174c]\u001b[39m\u001b[92m + BinDeps v0.8.10\u001b[39m\n", " \u001b[90m [b99e7846]\u001b[39m\u001b[92m + BinaryProvider v0.5.2\u001b[39m\n", " \u001b[90m [34da2185]\u001b[39m\u001b[92m + Compat v1.3.0\u001b[39m\n", " \u001b[90m [864edb3b]\u001b[39m\u001b[92m + DataStructures v0.14.0\u001b[39m\n", " \u001b[90m [31c24e10]\u001b[39m\u001b[92m + Distributions v0.16.4\u001b[39m\n", " \u001b[90m [2fe49d83]\u001b[39m\u001b[92m + Expectations v1.0.2\u001b[39m\n", " \u001b[90m [442a2c76]\u001b[39m\u001b[92m + FastGaussQuadrature v0.3.2\u001b[39m\n", " \u001b[90m [e1d29d7a]\u001b[39m\u001b[92m + Missings v0.3.1\u001b[39m\n", " \u001b[90m [bac558e1]\u001b[39m\u001b[92m + OrderedCollections v1.0.2\u001b[39m\n", " \u001b[90m [90014a1f]\u001b[39m\u001b[92m + PDMats v0.9.5\u001b[39m\n", " \u001b[90m [1fd47b50]\u001b[39m\u001b[92m + QuadGK v2.0.2\u001b[39m\n", " \u001b[90m [79098fc4]\u001b[39m\u001b[92m + Rmath v0.5.0\u001b[39m\n", " \u001b[90m [a2af1166]\u001b[39m\u001b[92m + SortingAlgorithms v0.3.1\u001b[39m\n", " \u001b[90m [276daf66]\u001b[39m\u001b[92m + SpecialFunctions v0.7.2\u001b[39m\n", " \u001b[90m [2913bbd2]\u001b[39m\u001b[92m + StatsBase v0.25.0\u001b[39m\n", " \u001b[90m [4c63d2b9]\u001b[39m\u001b[92m + StatsFuns v0.7.0\u001b[39m\n", " \u001b[90m [30578b45]\u001b[39m\u001b[92m + URIParser v0.4.0\u001b[39m\n", " \u001b[90m [2a0f44e3]\u001b[39m\u001b[92m + Base64 \u001b[39m\n", " \u001b[90m [ade2ca70]\u001b[39m\u001b[92m + Dates \u001b[39m\n", " \u001b[90m [8bb1440f]\u001b[39m\u001b[92m + DelimitedFiles \u001b[39m\n", " \u001b[90m [8ba89e20]\u001b[39m\u001b[92m + Distributed \u001b[39m\n", " \u001b[90m [b77e0a4c]\u001b[39m\u001b[92m + InteractiveUtils \u001b[39m\n", " \u001b[90m [76f85450]\u001b[39m\u001b[92m + LibGit2 \u001b[39m\n", " \u001b[90m [8f399da3]\u001b[39m\u001b[92m + Libdl \u001b[39m\n", " \u001b[90m [37e2e46d]\u001b[39m\u001b[92m + LinearAlgebra \u001b[39m\n", " \u001b[90m [56ddb016]\u001b[39m\u001b[92m + Logging \u001b[39m\n", " \u001b[90m [d6f4376e]\u001b[39m\u001b[92m + Markdown \u001b[39m\n", " \u001b[90m [a63ad114]\u001b[39m\u001b[92m + Mmap \u001b[39m\n", " \u001b[90m [44cfe95a]\u001b[39m\u001b[92m + Pkg \u001b[39m\n", " \u001b[90m [de0858da]\u001b[39m\u001b[92m + Printf \u001b[39m\n", " \u001b[90m [3fa0cd96]\u001b[39m\u001b[92m + REPL \u001b[39m\n", " \u001b[90m [9a3f8284]\u001b[39m\u001b[92m + Random \u001b[39m\n", " \u001b[90m [ea8e919c]\u001b[39m\u001b[92m + SHA \u001b[39m\n", " \u001b[90m [9e88b42a]\u001b[39m\u001b[92m + Serialization \u001b[39m\n", " \u001b[90m [1a1011a3]\u001b[39m\u001b[92m + SharedArrays \u001b[39m\n", " \u001b[90m [6462fe0b]\u001b[39m\u001b[92m + Sockets \u001b[39m\n", " \u001b[90m [2f01184e]\u001b[39m\u001b[92m + SparseArrays \u001b[39m\n", " \u001b[90m [10745b16]\u001b[39m\u001b[92m + Statistics \u001b[39m\n", " \u001b[90m [4607b0f0]\u001b[39m\u001b[92m + SuiteSparse \u001b[39m\n", " \u001b[90m [8dfed614]\u001b[39m\u001b[92m + Test \u001b[39m\n", " \u001b[90m [cf7118a7]\u001b[39m\u001b[92m + UUIDs \u001b[39m\n", " \u001b[90m [4ec0a83e]\u001b[39m\u001b[92m + Unicode \u001b[39m\n" ] } ], "source": [ "] add Expectations" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can track changes in the TOML, as before\n", "\n", "Here’s the Manifest\n", "\n", "\n", "\n", " \n", "We can also run other operations, like `] up`, `] precompile`, etc.\n", "\n", "Recall that, to quit the active environment and return to the base `(v1.0)`, simply run" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "] activate" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Without any arguments" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Writing Code\n", "\n", "The basic idea is to work in `tests/runtests.jl`, while reproducible functions should go in the `src/ExamplePackage.jl`\n", "\n", "For example, let’s say we add `Distributions.jl`" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "] activate ExamplePackage" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `C:\\Users\\jlperla\\.julia\\dev\\ExamplePackage\\Project.toml`\n", " \u001b[90m [31c24e10]\u001b[39m\u001b[92m + Distributions v0.16.4\u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `C:\\Users\\jlperla\\.julia\\dev\\ExamplePackage\\Manifest.toml`\n", "\u001b[90m [no changes]\u001b[39m\n" ] } ], "source": [ "] add Distributions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "and edit the source to read as follows:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Main.ExamplePackage" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "module ExamplePackage\n", "\n", "greet() = print(\"Hello World!\")\n", "\n", "using Expectations, Distributions\n", "\n", "function foo(μ = 1., σ = 2.)\n", " d = Normal(μ, σ)\n", " E = expectation(d)\n", " return E(x -> sin(x))\n", "end\n", "\n", "export foo\n", "\n", "end # module" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let’s try calling this from the top environment (to stand in for a fresh REPL)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "] activate" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "┌ Info: Precompiling ExamplePackage [e4ee45a0-e7ce-11e8-2a29-0577d03cebf1]\n", "└ @ Base loading.jl:1192\n", "WARNING: using ExamplePackage.ExamplePackage in module Main conflicts with an existing identifier.\n" ] }, { "ename": "ErrorException", "evalue": "importing ExamplePackage into Main conflicts with an existing identifier", "output_type": "error", "traceback": [ "importing ExamplePackage into Main conflicts with an existing identifier", "", "Stacktrace:", " [1] top-level scope at In[19]:1" ] } ], "source": [ "using ExamplePackage\n", "ExamplePackage.greet()" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "ename": "UndefVarError", "evalue": "UndefVarError: foo not defined", "output_type": "error", "traceback": [ "UndefVarError: foo not defined", "", "Stacktrace:", " [1] top-level scope at In[20]:1" ] } ], "source": [ "foo() # exported, so don't need to qualify the namespace" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Jupyter Workflow\n", "\n", "We can also call this function from a Jupyter notebook\n", "\n", "Let’s create a new output directory in our project, and run `jupyter lab` from it. Call a new notebook `output.ipynb`\n", "\n", "\n", "\n", " \n", "From here, we can use our package’s functions in the usual way. This lets us produce neat output examples, without re-defining everything\n", "\n", "We can also edit it interactively inside the notebook\n", "\n", "\n", "\n", " \n", "The change will be reflected in the `Project.toml` file:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "ename": "UndefVarError", "evalue": "UndefVarError: deps not defined", "output_type": "error", "traceback": [ "UndefVarError: deps not defined", "", "Stacktrace:", " [1] top-level scope at In[21]:5" ] } ], "source": [ "name = \"ExamplePackage\"\n", "uuid = \"f85830d0-e1f0-11e8-2fad-8762162ab251\"\n", "authors = [\"QuantEcon User \"]\n", "version = \"0.1.0\"\n", "\n", "[deps]\n", "Distributions = \"31c24e10-a181-5473-b8eb-7969acd0382f\"\n", "Expectations = \"2fe49d83-0758-5602-8f54-1f90ad0d522b\"\n", "Parameters = \"d96e819e-fc66-5662-9728-84c9c7592b0a\"\n", "\n", "[extras]\n", "Test = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n", "\n", "[targets]\n", "test = [\"Test\"]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And the Manifest as well\n", "\n", "Be sure to add `output/.ipynb_checkpoints` to your `.gitignore` file, so that’s not checked in\n", "\n", "Make sure you’ve activated the project environment (`] activate ExamplePackage`) before you try to propagate changes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Collaborative Work\n", "\n", "For someone else to get the package, they simply need to run" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[32m\u001b[1m Cloning\u001b[22m\u001b[39m git-repo `https://github.com/quanteconuser/ExamplePackage.jl.git`\n", "\u001b[2K\u001b[?25h\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m git-repo `https://github.com/quanteconuser/ExamplePackage.jl.git`\n", "\u001b[?25l\u001b[2K\u001b[?25h" ] }, { "name": "stderr", "output_type": "stream", "text": [ "┌ Info: Path `C:\\Users\\jlperla\\.julia\\dev\\ExamplePackage` exists and looks like the correct package, using existing path instead of cloning\n", "└ @ Pkg.Types C:\\cygwin\\home\\Administrator\\buildbot\\worker\\package_win64\\build\\usr\\share\\julia\\stdlib\\v1.0\\Pkg\\src\\Types.jl:586\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `C:\\Users\\jlperla\\.julia\\environments\\v1.0\\Project.toml`\n", " \u001b[90m [e4ee45a0]\u001b[39m\u001b[91m - ExamplePackage v0.1.0 [`..\\..\\dev\\ExamplePackage`]\u001b[39m\n", " \u001b[90m [f85830d0]\u001b[39m\u001b[92m + ExamplePackage v0.1.0 [`C:\\Users\\jlperla\\.julia\\dev\\ExamplePackage`]\u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `C:\\Users\\jlperla\\.julia\\environments\\v1.0\\Manifest.toml`\n", " \u001b[90m [e4ee45a0]\u001b[39m\u001b[91m - ExamplePackage v0.1.0 [`..\\..\\dev\\ExamplePackage`]\u001b[39m\n", " \u001b[90m [f85830d0]\u001b[39m\u001b[92m + ExamplePackage v0.1.0 [`C:\\Users\\jlperla\\.julia\\dev\\ExamplePackage`]\u001b[39m\n" ] } ], "source": [ "] dev https://github.com/quanteconuser/ExamplePackage.jl.git" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This will place the repository inside their `~/.julia/dev` folder, and they can drag-and-drop it to GitHub desktop in the usual way\n", "\n", "They can then collaborate as they would on other git repositories\n", "\n", "In particular, they can run" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "] activate ExamplePackage" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m registry at `C:\\Users\\jlperla\\.julia\\registries\\General`\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m git-repo `https://github.com/JuliaRegistries/General.git`\n", "\u001b[?25l\u001b[2K\u001b[?25h" ] } ], "source": [ "] instantiate" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To make sure the right dependencies are installed on their machine" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Unit Testing\n", "\n", "It’s important to make sure that your code is well-tested\n", "\n", "There are a few different kinds of test, each with different purposes\n", "\n", "- *Unit testing* makes sure that individual pieces of a project function as expected \n", "- *Integration testing* makes sure that they work together as expected \n", "- *Regression testing* makes sure that behavior is unchanged over time \n", "\n", "\n", "In this lecture, we’ll focus on unit testing" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The `Test` Module\n", "\n", "Julia provides testing features through a built-in package called `Test`, which we get by `using Test`\n", "\n", "The basic object is the macro `@test`" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[32m\u001b[1mTest Passed\u001b[22m\u001b[39m" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "using Test\n", "@test 1 == 1\n", "@test 1 ≈ 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Tests will pass if the condition is `true`, or fail otherwise\n", "\n", "If a test is failing, we should *flag it and move on*" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[33m\u001b[1mTest Broken\u001b[22m\u001b[39m\n", " Expression: 1 == 2" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "@test_broken 1 == 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This way, we still have access to information about the test, instead of just deleting it or commenting it out\n", "\n", "There are other test macros, that check for things like error handling and type-stability. Advanced users can check the [Julia docs](https://docs.julialang.org/en/v1/stdlib/Test/)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example\n", "\n", "Let’s add some unit tests for the `foo()` function we defined earlier. Our `tests/runtests.jl` file should look like this" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "ename": "ErrorException", "evalue": "importing ExamplePackage into Main conflicts with an existing identifier", "output_type": "error", "traceback": [ "importing ExamplePackage into Main conflicts with an existing identifier", "", "Stacktrace:", " [1] top-level scope at In[27]:1" ] } ], "source": [ "using ExamplePackage\n", "using Test\n", "\n", "@test foo() == 0.11388071406436832\n", "@test foo(1, 1.5) == 0.2731856314283442\n", "@test_broken foo(1, 0) # tells us this is broken" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And run it by running" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[32m\u001b[1m Testing\u001b[22m\u001b[39m ExamplePackage\n", "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "┌ Warning: Could not load Revise.\n", "└ @ Main C:\\Users\\jlperla\\.julia\\config\\startup.jl:6\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\u001b[32m\u001b[1m Testing\u001b[22m\u001b[39m ExamplePackage tests passed \n" ] } ], "source": [ "] test" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "from an activated REPL" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Test Sets\n", "\n", "By default, the `runtests.jl` folder starts off with a `@testset`\n", "\n", "This is useful for organizing different batches of tests, but for now we can simply ignore it\n", "\n", "To learn more about test sets, see [the docs](https://docs.julialang.org/en/v1/stdlib/Test/index.html#Working-with-Test-Sets-1/)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Running Tests Locally\n", "\n", "There are a few different ways to run the tests for your package\n", "\n", "- From a fresh REPL, run `pkg> test ExamplePackage` \n", "- From an activated REPL, simply run `pkg> test` (recall that you can activate with `pkg> activate ExamplePackage`) \n", "- Hit shift-enter in Atom on the actual `runtests.jl` file (as below). Recall that we can get the path of the package by running `using ExamplePackage; pathof(ExamplePackage)` from an unactivated REPL " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Continuous Integration with Travis" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Setup\n", "\n", "By default, Travis should have access to all your repositories and deploy automatically\n", "\n", "This includes private repos if you’re on a student developer pack or an academic plan (Travis detects this automatically)\n", "\n", "To change this, go to “settings” under your GitHub profile\n", "\n", "\n", "\n", " \n", "Click “Applications,” then “Travis CI,” then “Configure,” and choose the repos you want to be tracked" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Build Options\n", "\n", "By default, Travis will compile and test your project (i.e., “build” it) for new commits and PRs for every tracked repo with a `.travis.yml` file\n", "\n", "We can see ours by opening it in Atom" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "ename": "UndefVarError", "evalue": "UndefVarError: language not defined", "output_type": "error", "traceback": [ "UndefVarError: language not defined", "", "Stacktrace:", " [1] top-level scope at In[29]:1" ] } ], "source": [ "# Documentation: http://docs.travis-ci.com/user/languages/julia/\n", "language: julia\n", "os:\n", "- linux\n", "- osx\n", "julia:\n", "- 1.0\n", "- nightly\n", "matrix:\n", "allow_failures:\n", " - julia: nightly\n", "fast_finish: true\n", "notifications:\n", "email: false\n", "after_success:\n", "- julia -e 'using Pkg; Pkg.add(\"Coverage\"); using Coverage; Codecov.submit(process_folder())'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is telling Travis to build the project in Julia, on OSX and Linux, using Julia v1.0 and the latest (“nightly”)\n", "\n", "It also says that if the nightly version doesn’t work, that shouldn’t register as a failure\n", "\n", "**Note** You won’t need OSX unless you’re building something Mac-specific, like iOS or Swift. You can delete those lines to speed up the build. Likewise for nightly Julia" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Working with Builds\n", "\n", "As above, builds are triggered whenever we push changes or open a pull request\n", "\n", "For example, if we push our changes to the server and then click the Travis badge on the README, we should see something like\n", "\n", "\n", "\n", " \n", "This gives us an overview of all the builds running for that commit\n", "\n", "To inspect a build more closely (say, if it fails), we can click on it and expand the log options\n", "\n", "\n", "\n", " \n", "Note that the build times here aren’t informative, because we can’t generally control the hardware to which our job is allocated\n", "\n", "We can also cancel specific jobs, either from their specific pages or by clicking the grey “x” button on the dashboard\n", "\n", "Lastly, we can trigger builds manually (without a new commit or PR) from the Travis overview\n", "\n", "\n", "\n", " \n", "To commit *without* triggering a build, simply add [ci skip] somewhere inside the commit message" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Travis and Pull Requests\n", "\n", "One key feature of Travis is the ability to see at-a-glance whether PRs pass tests before merging them\n", "\n", "This happens automatically when Travis is enabled on a repository\n", "\n", "For an example of this feature, see [this PR](https://github.com/QuantEcon/Games.jl/pull/65/) in the Games.jl repository" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## CodeCoverage\n", "\n", "Beyond the success or failure of our test suite, we also want to know how much of our code the tests cover\n", "\n", "The tool we use to do this is called [CodeCov](http://codecov.io)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Setup\n", "\n", "You’ll find that codecov is automatically enabled for public repos with Travis\n", "\n", "For private ones, you’ll need to first get an access token\n", "\n", "Add private scope in the CodeCov website, just like we did for Travis\n", "\n", "Navigate to the repo settings page (i.e., `https://codecov.io/gh/quanteconuser/ExamplePackage.jl/settings` for our repo) and copy the token\n", "\n", "Next, go to your travis settings and add an environment variable as below\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Interpreting Results\n", "\n", "Click the CodeCov badge to see the build page for your project\n", "\n", "This shows us that our tests cover 50 % of our functions in `src//`\n", "\n", "To get a more granular view, we can click the `src//` and the resultant filename\n", "\n", "\n", "\n", " \n", "This shows us precisely which methods (and parts of methods) are untested" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Pull Requests to External Julia Projects\n", "\n", "As mentioned in [version control](version_control), sometimes we’ll want to work on external repos that are also Julia projects\n", "\n", "The first thing is to `] dev` the git URL (or package name, if the project is a registered Julia package), which will both clone the git repo and sync it with the Julia package manager\n", "\n", "For example, running" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```julia\n", "] dev Expectations\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "will clone the repo `https://github.com/quantecon/Expectations.jl` to `~/.julia/dev`\n", "\n", "Make sure you do this from a fresh REPL\n", "\n", "As a reminder, you can find the location of your `~/.julia` folder (called the “user depot”), by running" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"C:\\\\Users\\\\jlperla\\\\.julia\"" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "DEPOT_PATH[1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `] dev` command will also add the target to the package manager, so that whenever we run `using Expectations`, Julia will load our cloned copy from that location" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```julia\n", "using Expectations\n", "pathof(Expectations) # points to our git clone\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, drag that folder to GitHub Desktop\n", "\n", "The next step is to fork the original (external) package from its website (i.e., `https://github.com/quantecon/Expectations.jl`) to your account (`https://github.com/quanteconuser/Expectations.jl` in our case)\n", "\n", "\n", "\n", " \n", "Lastly, edit the settings in GitHub Desktop (from the “Repository” dropdown) to reflect the new URL\n", "\n", "\n", "\n", " \n", "Here, we’d change the highlighted text to read `quanteconuser`, or whatever our GitHub ID is\n", "\n", "If you make some changes in a text editor and return to GitHub Desktop, you’ll see something like\n", "\n", "\n", "\n", " \n", "Here, for example, we’re revising the README\n", "\n", "Clicking “commit to master” (recall that the checkboxes next to each file indicate whether it’s to be committed) and then pushing (e.g., hitting “push” under the “Repository” dropdown) will add the committed changes to your account\n", "\n", "To confirm this, we can check the history on our account [here](https://github.com/quanteconuser/Expectations.jl/commits/master); for more on working with git repositories, see the [version control](version_control) lecture\n", "\n", "\n", "\n", " \n", "The green check mark indicates that Travis tests passed for this commit\n", "\n", "Clicking “new pull request” from the pull requests tab will show us a snapshot of the changes, and let us create a pull request for project maintainers to review and approve\n", "\n", "\n", "\n", " \n", "For more on PRs, see the relevant section of the [version control](version_control) lecture" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Case with Write Access\n", "\n", "If you have write access to the repo, we can skip the preceding steps about forking and changing the URL\n", "\n", "You can use `] dev` on a package name or the URL of the package." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```julia\n", "] dev Expectations\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "or `] dev https://github.com/quanteconuser/Expectations.jl.git` as an example for an unreleased package by URL\n", "\n", "Which will again clone the repo to `~/.julia/dev`, and use it as a Julia package" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```julia\n", "using Expectations\n", "pathof(Expectations) # points to our git clone\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, drag that folder to GitHub Desktop\n", "\n", "Then, in order to work with a project locally, all we need to do is open it in a text editor (like Atom)\n", "\n", "\n", "\n", " \n", "From here, we can edit this package just like we created it ourselves and use GitHub Desktop to track versions of our package files (say, after `] up`, or editing source code, `] add Package`, etc.)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Removing a Julia Package\n", "\n", "To “un-dev” a Julia package (say, if we want to use our old `Expectations.jl`), you can simply run" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```julia\n", "] free Expectations\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To delete it entirely, simply run" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```julia\n", "] rm Expectations\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Both from a fresh REPL" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Benchmarking\n", "\n", "Another goal of testing is to make sure that code doesn’t slow down significantly from one version to the next\n", "\n", "We can do this using tools provided by the `BenchmarkTools.jl` package\n", "\n", "See the `need for speed` lecture for more details" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Additional Notes\n", "\n", "- The [JuliaCI](https://github.com/JuliaCI/) organization provides more Julia utilities for continuous integration and testing \n", "- This [Salesforce document](https://developer.salesforce.com/page/How_to_Write_Good_Unit_Tests/) has some good lessons about writing and testing code " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercises" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Exercise 1\n", "\n", "Following the [instructions for a new project](project_setup), create a new package on your github account called `NewtonsMethod.jl`\n", "\n", "In this package, you should create a simple package to do Newton’s Method using the code you did in the\n", "[Newton’s method](jbe_ex8a) exercise in [Julia by example](julia_by_example)\n", "\n", "In particular, within your package you should have two functions\n", "* `newtonroot(f, f′; x₀, tol = 1E-7, maxiter = 1000)`\n", "* `newtonroot(f; x₀, tol = 1E-7, maxiter = 1000)`\n", "\n", "Where the second function one uses Automatic Differentiation to call the first.\n", "\n", "The package should include\n", "* implementations of those functions in the `/src` directory\n", "* comprehensive set of tests\n", "* project and manifest files to replicate your development environment\n", "* automated running of the tests with Travis CI in GitHub\n", "\n", "For the tests, you should have at the very minimum\n", "* a way to handle non-convergence (e.g. return back `nothing` as discussed in [error handling](error_handling)\n", "* several `@test` for the root of a known function, given the `f` and analytical `f'` derivatives\n", "* tests of those roots using the automatic differentiation version of the function\n", "* test of finding those roots with a `BigFloat` and not just a `Float64`\n", "* test of non-convergence for a function without a root (e.g. $ f(x) = 2 + x^2 $ )\n", "* test to ensure that the `maxiter` is working (e.g. what happens if you call `maxiter = 5`\n", "* test to ensure that `tol` is working\n", "\n", "And anything else you can think of. You should be able to run `] test` for the project to check that the test-suite is running, and then ensure that it is running automatically on Travis CI\n", "\n", "Push a commit to the repository which breaks one of the tests and see what the Travis CI reports after running the build" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "filename": "testing.rst", "kernelspec": { "display_name": "Julia 1.0.2", "language": "julia", "name": "julia-1.0" }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", "version": "1.0.2" }, "title": "Packages, Testing, and Continuous Integration" }, "nbformat": 4, "nbformat_minor": 2 }