Writing and testing a crate – logic gate simulator

Armed with all of this knowledge, let's start things off with our logic gate simulation crate. We'll create a new project by running cargo new logic_gates --lib. Starting with primitive gates implemented as functions such as and, xor, and so on, we will write unit tests for these gates. Following that, we'll write integration tests by implementing a half adder that uses our primitive gates. During this process, we'll also get to write documentation for our crate.

First off, we'll start with some unit tests. Here's the initial crate code in its entirety:

//! This is a logic gates simulation crate built to demonstrate writing unit tests and integration tests

// logic_gates/src/lib.rs

pub fn and(a: u8, b: u8) -> u8 {
unimplemented!()
}

pub fn xor(a: u8, b: u8) -> u8 {
unimplemented!()
}

#[cfg(test)]
mod tests {
use crate::{xor, and};
#[test]
fn test_and() {
assert_eq!(1, and(1, 1));
assert_eq!(0, and(0, 1));
assert_eq!(0, and(1, 0));
assert_eq!(0, and(0, 0));
}

#[test]
fn test_xor() {
assert_eq!(1, xor(1, 0));
assert_eq!(0, xor(0, 0));
assert_eq!(0, xor(1, 1));
assert_eq!(1, xor(0, 1));
}
}

We have started with two logic gates, and and xor, which have been implemented as functions. We also have tests cases against those that fail when run because they haven't been implemented yet. Note that to represent bit 0 and 1, we are using a u8 as Rust does not have a native type to represent bits. Now, let's fill in their implementation, along with some documentation:

/// Implements a boolean `and` gate taking as input two bits and returns a bit as output
pub fn and(a: u8, b: u8) -> u8 {
match (a, b) {
(1, 1) => 1,
_ => 0
}
}

/// Implements a boolean `xor` gate taking as input two bits and returning a bit as output
pub fn xor(a: u8, b: u8) -> u8 {
match (a, b) {
(1, 0) | (0, 1) => 1,
_ => 0
}
}

In the preceding code, we just expressed the truth tables of the and and xor gates using match expressions. We can see how concise match expressions can be in expressing our logic. Now, we can run the tests by running cargo test:

All green! We are now ready to write integration tests by implementing a half adder using these gates. A half adder fits in perfectly as an integration test example as it tests the individual components of our crate while they're being used together. Under the tests/ directory, we'll create a file called half_adder.rs that includes the following code:

// logic_gates/tests/half_adder.rs

use logic_gates::{and, xor};

pub type Sum = u8;
pub type Carry = u8;

pub fn half_adder_input_output() -> Vec<((u8, u8), (Sum, Carry))> {
vec![
((0, 0), (0, 0)),
((0, 1), (1, 0)),
((1, 0), (1, 0)),
((1, 1), (0, 1)),
]
}

/// This function implements a half adder using primitive gates
fn half_adder(a: u8, b: u8) -> (Sum, Carry) {
(xor(a, b), and(a, b))
}

#[test]
fn one_bit_adder() {
for (inn, out) in half_adder_input_output() {
let (a, b) = inn;
println("Testing: {}, {} -> {}", a, b, out);
assert_eq!(half_adder(a, b), out);
}
}

In the preceding code, we import our primitive gate functions xor and and. Following that, we have something like pub type Sum = u8, which is known as a type alias. They are helpful in situations where you either have a type that is cumbersome to write every time or when you have types with complex signatures. It gives another name to our original type and is purely for readability and disambiguation; it has no implications in the way Rust analyzes those types. We then use the  Sum and Carry in our half_adder_input_output function, which implements the truth table for the half adder. This is a convenient helper function to test our half_adder function that follows it. This function takes in two one-bit inputs and calculates the Sum and Carry from them before returning them as a tuple of (Sum, Carry). Further ahead, we have our one_bit_adder integration test function, in which we iterate over our half adder input output pairs and assert against the output of the  half_adder. By running cargo test, we get the following output:

Great ! Let's also generate documentation for our crate by running cargo doc --open. The --open flag opens the page for us to view in a browser. To customize our documentation, we'll also add an icon to our crate docs page. To do this, we need to add the following attribute at the top of lib.rs:

#![doc(html_logo_url = "https://d30y9cdsu7xlg0.cloudfront.net/png/411962-200.png")]

After generation, the documentation page looks like this:

This is great! We have come a long way in our testing journey. Next, let's look at the aspect  automating out test suites.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset