lab_grader 0.11.0

A crate to help grade labs and assignments
Documentation
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
    <head>
        <!-- Book generated using mdBook -->
        <meta charset="UTF-8">
        <title>Writing the Tests - Lab Grader</title>
        

        <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="theme-color" content="#ffffff" />

        <link rel="shortcut icon" href="../favicon.png">
        <link rel="stylesheet" href="../css/variables.css">
        <link rel="stylesheet" href="../css/general.css">
        <link rel="stylesheet" href="../css/chrome.css">
        <link rel="stylesheet" href="../css/print.css" media="print">

        <!-- Fonts -->
        <link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
        <link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
        <link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">

        <!-- Highlight.js Stylesheets -->
        <link rel="stylesheet" href="../highlight.css">
        <link rel="stylesheet" href="../tomorrow-night.css">
        <link rel="stylesheet" href="../ayu-highlight.css">

        <!-- Custom theme stylesheets -->
        

        
    </head>
    <body>
        <!-- Provide site root to javascript -->
        <script type="text/javascript">
            var path_to_root = "../";
            var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
        </script>

        <!-- Work around some values being stored in localStorage wrapped in quotes -->
        <script type="text/javascript">
            try {
                var theme = localStorage.getItem('mdbook-theme');
                var sidebar = localStorage.getItem('mdbook-sidebar');

                if (theme.startsWith('"') && theme.endsWith('"')) {
                    localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
                }

                if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
                    localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
                }
            } catch (e) { }
        </script>

        <!-- Set the theme before any content is loaded, prevents flash -->
        <script type="text/javascript">
            var theme;
            try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
            if (theme === null || theme === undefined) { theme = default_theme; }
            var html = document.querySelector('html');
            html.classList.remove('no-js')
            html.classList.remove('light')
            html.classList.add(theme);
            html.classList.add('js');
        </script>

        <!-- Hide / unhide sidebar before it is displayed -->
        <script type="text/javascript">
            var html = document.querySelector('html');
            var sidebar = 'hidden';
            if (document.body.clientWidth >= 1080) {
                try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
                sidebar = sidebar || 'visible';
            }
            html.classList.remove('sidebar-visible');
            html.classList.add("sidebar-" + sidebar);
        </script>

        <nav id="sidebar" class="sidebar" aria-label="Table of contents">
            <div class="sidebar-scrollbox">
                <ol class="chapter"><li class="chapter-item expanded affix "><a href="../introduction.html">Introduction</a></li><li class="chapter-item expanded "><a href="../example_grader/home.html"><strong aria-hidden="true">1.</strong> Example Grader</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../example_grader/criteria.html"><strong aria-hidden="true">1.1.</strong> Defining the Criteria</a></li><li class="chapter-item expanded "><a href="../example_grader/submission.html"><strong aria-hidden="true">1.2.</strong> Building a Submission</a></li><li class="chapter-item expanded "><a href="../example_grader/rubric.html"><strong aria-hidden="true">1.3.</strong> Building the Rubric</a></li><li class="chapter-item expanded "><a href="../example_grader/tests.html" class="active"><strong aria-hidden="true">1.4.</strong> Writing the Tests</a></li><li class="chapter-item expanded "><a href="../example_grader/grade.html"><strong aria-hidden="true">1.5.</strong> Grading</a></li><li class="chapter-item expanded "><a href="../example_grader/submit.html"><strong aria-hidden="true">1.6.</strong> Submitting</a></li><li class="chapter-item expanded "><a href="../example_grader/results.html"><strong aria-hidden="true">1.7.</strong> The Results</a></li></ol></li><li class="chapter-item expanded "><a href="../criteria/home.html"><strong aria-hidden="true">2.</strong> Criteria</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../criteria/yaml_spec.html"><strong aria-hidden="true">2.1.</strong> YAML Specification</a></li><li class="chapter-item expanded "><a href="../criteria/criterion.html"><strong aria-hidden="true">2.2.</strong> A Single Criterion</a></li><li class="chapter-item expanded "><a href="../criteria/test.html"><strong aria-hidden="true">2.3.</strong> A Criteria Test</a></li><li class="chapter-item expanded "><a href="../criteria/rubric.html"><strong aria-hidden="true">2.4.</strong> A Rubric</a></li></ol></li><li class="chapter-item expanded "><a href="../submission/home.html"><strong aria-hidden="true">3.</strong> Submission</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../submission/test_data.html"><strong aria-hidden="true">3.1.</strong> TestData</a></li><li class="chapter-item expanded "><a href="../submission/prompt.html"><strong aria-hidden="true">3.2.</strong> The prompt! macro</a></li><li class="chapter-item expanded "><a href="../submission/create.html"><strong aria-hidden="true">3.3.</strong> Creating a Submission</a></li><li class="chapter-item expanded "><a href="../submission/submit.html"><strong aria-hidden="true">3.4.</strong> Submitting</a></li><li class="chapter-item expanded "><a href="../submission/server.html"><strong aria-hidden="true">3.5.</strong> Submission Server</a></li></ol></li><li class="chapter-item expanded "><a href="../helpers/home.html"><strong aria-hidden="true">4.</strong> Helpers</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../helpers/fs.html"><strong aria-hidden="true">4.1.</strong> File System</a></li><li class="chapter-item expanded "><a href="../helpers/web.html"><strong aria-hidden="true">4.2.</strong> Web</a></li><li class="chapter-item expanded "><a href="../helpers/cli.html"><strong aria-hidden="true">4.3.</strong> CLI</a></li></ol></li></ol>
            </div>
            <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
        </nav>

        <div id="page-wrapper" class="page-wrapper">

            <div class="page">
                
                <div id="menu-bar-hover-placeholder"></div>
                <div id="menu-bar" class="menu-bar sticky bordered">
                    <div class="left-buttons">
                        <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
                            <i class="fa fa-bars"></i>
                        </button>
                        <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
                            <i class="fa fa-paint-brush"></i>
                        </button>
                        <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
                            <li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
                        </ul>
                        
                        <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
                            <i class="fa fa-search"></i>
                        </button>
                        
                    </div>

                    <h1 class="menu-title">Lab Grader</h1>

                    <div class="right-buttons">
                        <a href="../print.html" title="Print this book" aria-label="Print this book">
                            <i id="print-button" class="fa fa-print"></i>
                        </a>
                        
                    </div>
                </div>

                
                <div id="search-wrapper" class="hidden">
                    <form id="searchbar-outer" class="searchbar-outer">
                        <input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
                    </form>
                    <div id="searchresults-outer" class="searchresults-outer hidden">
                        <div id="searchresults-header" class="searchresults-header"></div>
                        <ul id="searchresults">
                        </ul>
                    </div>
                </div>
                

                <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
                <script type="text/javascript">
                    document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
                    document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
                    Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
                        link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
                    });
                </script>

                <div id="content" class="content">
                    <main>
                        <h1><a class="header" href="#writing-the-tests" id="writing-the-tests">Writing the Tests</a></h1>
<p>Now comes the most important part of writing the application. We've build our rubric of criteria, but they currently don't have a way to be tested. How do we know if they actually have Git installed or not?</p>
<p>The way we determine this is by writing a &quot;test&quot;, which is just a function, for each of our criteria. Then we'll &quot;attach&quot; the function to the criteria, then we can grade the submission.</p>
<h2><a class="header" href="#a-single-test" id="a-single-test">A Single Test</a></h2>
<p>Each test needs to have the same signature, meaning it has to accept the same parameters and return the same thing. We need this consistency between tests to make grading possible.</p>
<p>Every test must accept a reference to a <code>TestData</code> object, and return a boolean. We created a <code>TestData</code> object with the <code>data!</code> macro when we made a submission. In fact, the exact data we put in the submission will be the data passed into each of our criteria tests. This is why we put the users Github username and repository name in the data; we'll need it inside one of our tests.</p>
<h2><a class="header" href="#helpers" id="helpers">Helpers</a></h2>
<p>Before we write any tests, you should know about the <a href="../helpers/home.html">helper</a> modules. These modules are a collection of functions that do common tasks in criteria tests. They may save you some times. See the documentation linked above for more info on each module.</p>
<h2><a class="header" href="#the-first-test" id="the-first-test">The First Test</a></h2>
<p>Let's write a test for our first criteria, which checks if Git is installed or not. Remember that a test is just a function with a specific signature.</p>
<p>You can write your tests anywhere, but I'll make a <code>tests.rs</code> to keep the tests separate from the rest of our code.</p>
<pre><code class="language-rust noplaypen">// tests.rs
use lab_grader::*;

fn confirm_git_installed(_: &amp;TestData) -&gt; bool {
    cli::Program::Git.version().is_some()
}
</code></pre>
<p>We added a function called <code>confirm_git_installed</code>. It takes in a parameter of type <code>&amp;TestData</code>, but in this case we don't need it so we'll name the parameter <code>_</code>. In the function body, we used the <code>cli</code> helper module to get the version of Git, and returned true if it's a <code>Some</code> value (this is an <code>Option</code> type, it would be <code>None</code> if Git wasn't installed).</p>
<p>And that's it for the first test. There's still one step to go, but we'll do that after the other tests.</p>
<h2><a class="header" href="#the-rest-of-the-tests" id="the-rest-of-the-tests">The Rest of the Tests</a></h2>
<p>I'm going to write some functions to serve as the tests for the remaining criteria. I won't explain what each one is doing in detail, but it should be pretty self explanatory. I'll put the entire <code>tests.rs</code> file here.</p>
<pre><code class="language-rust noplaypen">use std::process::Command;
use lab_grader::*;

// Naming the data parameter &quot;_&quot; because we don't need it in this case
pub fn confirm_git_installed(_: &amp;TestData) -&gt; bool {
    cli::Program::Git.version().is_some()
}

pub fn confirm_git_init(_: &amp;TestData) -&gt; bool {
    // This is a filesystem helper that this crate provides
    // also works on directories
    // This is *not* std::fs
    fs::file_exists(&quot;.git/&quot;)
}

pub fn confirm_enough_commits(_: &amp;TestData) -&gt; bool {
    // Run the git command to list commit count
    let out = Command::new(&quot;sh&quot;)
        .arg(&quot;-c&quot;)
        .arg(&quot;git rev-list --all --count&quot;)
        .output()
        .expect(&quot;Couldn't run subcommand&quot;);

    // If the command returns something
    if let Ok(string) = String::from_utf8(out.stdout) {
        // And if we could parse a number from it
        if let Ok(num) = string.trim().parse::&lt;u64&gt;() {
            return num &gt; 2;
        }
    }

    false
}

// We do need the data this time
pub fn confirm_repo_pushed(data: &amp;TestData) -&gt; bool {
    // Format the url to check
    let url = format!(&quot;https://github.com/{}/{}/&quot;, data[&quot;gh_name&quot;], data[&quot;repo&quot;]);
    // Another helper function
    web::site_responds(&amp;url)
}
</code></pre>
<p>In the <code>confirm_repo_pushed</code>, we're actually using the data attached to the submission. We can do that with bracket syntax (<code>data[&quot;key&quot;]</code>) or through the <a href="https://doc.rust-lang.org/beta/std/collections/struct.HashMap.html#method.get"><code>get</code></a> method. Using <code>get</code> is recommended over bracket syntax.</p>
<h2><a class="header" href="#attaching-the-tests" id="attaching-the-tests">Attaching the Tests</a></h2>
<p>We need to import the tests we wrote in <code>main.rs</code>. Add this import to the top of <code>main.rs</code></p>
<pre><code class="language-rust noplaypen">// Declare the tests mod (tests.rs)
mod tests;
// Import all the test functions
use tests::*;
</code></pre>
<p>Now that we have our tests, we need to attach them to the appropriate criteria. We can do that with the <code>attach!</code> macro.</p>
<pre><code class="language-rust noplaypen">fn main() {
    // ...
    attach! {
        rubric,
        &quot;git-installed&quot; =&gt; confirm_git_installed,
        &quot;git-init&quot; =&gt; confirm_git_init,
        &quot;commits-present&quot; =&gt; confirm_enough_commits,
        &quot;repo-pushed&quot; =&gt; confirm_repo_pushed
    };
}
</code></pre>
<p>This attached each of our functions to the criteria with the given stub. This is why we needed to specify a stub in yaml.</p>
<blockquote>
<p>Note: If you don't provide the stub in yaml, it will be created by lowercasing the name and replacing whitespace with a dash. ie. <code>My First Criterion =&gt; my-first-criterion</code>.</p>
</blockquote>
<p>Now that the tests are attached, we're <a href="grade.html">ready to grade</a>.</p>

                    </main>

                    <nav class="nav-wrapper" aria-label="Page navigation">
                        <!-- Mobile navigation buttons -->
                        
                            <a rel="prev" href="../example_grader/rubric.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
                                <i class="fa fa-angle-left"></i>
                            </a>
                        

                        
                            <a rel="next" href="../example_grader/grade.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
                                <i class="fa fa-angle-right"></i>
                            </a>
                        

                        <div style="clear: both"></div>
                    </nav>
                </div>
            </div>

            <nav class="nav-wide-wrapper" aria-label="Page navigation">
                
                    <a rel="prev" href="../example_grader/rubric.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
                        <i class="fa fa-angle-left"></i>
                    </a>
                

                
                    <a rel="next" href="../example_grader/grade.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
                        <i class="fa fa-angle-right"></i>
                    </a>
                
            </nav>

        </div>

        

        

        
        
        
        <script type="text/javascript">
            window.playpen_copyable = true;
        </script>
        

        

        
        <script src="../elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="../mark.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="../searcher.js" type="text/javascript" charset="utf-8"></script>
        

        <script src="../clipboard.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="../highlight.js" type="text/javascript" charset="utf-8"></script>
        <script src="../book.js" type="text/javascript" charset="utf-8"></script>

        <!-- Custom JS scripts -->
        

        

    </body>
</html>