Skip to content

bpryan99/synspective-coding-test

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

synspective-coding-test

Repository containing Ben Ryan's official submissions for Synspective's Embedded Software Engineer coding test.

Build guide

Assumptions

  • The user is running a Linux or Unix-based operating system.
    • Alternatively, the user is working from a Linux shell on Windows via WSL
  • Git CLI is installed on the system (usually by default)
  • You are performing this build either on the command line/terminal or in VSCode.

Install dependencies

Ubuntu (or Debian-based)

sudo apt install build-essential cmake libgtest-dev

RHEL/CentOS/Fedora

sudo dnf install code gcc gcc-c++ kernel-devel make cmake gtest

MacOS (requires Homebrew)

If Homebrew is not installed yet

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
username=$(whoami)
echo >> /Users/$username/.zprofile
echo 'eval "$(/opt/homebrew/bin/brew shellenv zsh)"' >> /Users/$username/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv zsh)"

xcode-select --install
brew install cmake googletest

Clone repository

Via your command line/terminal, clone the repository via one of the following commands:

SSH

  • If you have not already, set up your public SSH key with your GitHub account.
  • Run command: git clone git@github.com:bpryan99/synspective-coding-test.git

HTTPS

  • Run command: git clone https://github.com/bpryan99/synspective-coding-test.git

Download ZIP

  • If you prefer, download the project via ZIP archive and extract.

Build the project

CMake is utilized to make the build as system and compiler agnostic as possible.

This project does not produce executables for the source files. This is due to there being no requested main() logic for either implementation question. By omitting main(), I can more easily package the source and header files for each question into a library that the unit tests can use, therefore avoiding double compiling of the source. If the libraries contained source files with main() definitions, they would override gtest_main and result in no tests being discovered.

CLI

From the root of the project directory (where the CMakeLists.txt file resides), set build folder to build and then build/compile.

cd ~/<cloned_dir>
cmake -B build && cmake --build build

VSCode

The build should occur automatically when opening the project. If it does not, click 'Build' along the bottom ribbon menu.

Testing

After successful build, executables for unit tests of questions 2 and 3 will have populated in the build folder as question-*_unittest. The sources for these tests are found in tests/. The testing framework used is GoogleTest (GTest).

Test execution

To run all the tests

cd build && ctest

To run a specific class tests

cd build
ctest -R RingBufferTest
ctest -R LinkedListTest
ctest -R CarTest
ctest -R NodeTest

Testing methodology

My testing methodology for unit tests can be broken down into 3 tenants:

  • Unit tests are testing the public interface for expected behavior
  • Unit tests should produce as close to 100% coverage of code/logic as possible to be effective
  • Unit tests should test edge cases where logic is more likely to be fragile

Testing the public interface

Unit tests can only access the public members of a class, be it vars or functions. Unit tests should test all public members.

Testing for coverage

Unit tests should be sufficient enough to 'touch' each line of code (or at least each line changing logic state) for the given source so as to verify static functionality completely. Even if some members are private, public members invoking private methods should allow for sufficient coverage. This way, if a new engineer goes to update a piece of logic they falsely assume inconsequential, there is already a unit test that will preemptively catch the mistake introduced by the bad assumption. The test implementor must not just test what they consider to be the likely scenarios in which the source might break, but should strive to cover each line so that subsequent changes do not silently introduce bugs.

Testing for edge cases

A source file may have full code coverage by unit tests with certain input conditions, but that may not reveal errors introduced by less common input conditions. For example, if the LinkedList to be merged has only 1 node, this is an edge case which needs to be tested. Hypothetically, a source file with total coverage via unit tests would already have tested this logic since there is code for this special base case, but what if that code were not present? Edge cases where input is most likely to break the functionality should be covered by unit tests for testing completeness.

Testing assumptions

I made a few assumptions when designing unit tests for these two questions:

  1. The source files do not need to generate executables, there just need to be executable tests that can access source/headers as libraries.
  2. The compiler/ctest will handle constructor contract breaks, such as providing a completely incorrect type for a given var/function arg.
  3. Since no system limitations were provided, the resources of the hypothetical system these functions would be run on have sufficient resources that I need not test edge cases where memory size is a concern.

Future improvements

If I were to be asked how I would take either of these implementations further, I would answer with the following:

LinkedList of Cars

  • Further generalize my LinkedList implementation to be more extensible.
    • Updating mergeSortByYear() to be agnostic of the key of the data to sort by would be my first priority. Providing a key type and key name and attempting to sort based on those two args would improve the extensibility of the implementation.
  • Consider updating the implementation as a doubly-linked list if node deletion would be happening frequently. While the implementation is more complex, the time complexity of deletion in a doubly-linked list is O(1) vs singly-linked list's O(n).
  • Add functionality to search the list for a specific Car, insert in a specific location, etc.
  • Potentially sort on insertion of a new node so that the sort function just returns a copy of the already sorted list. This would be most useful if insertion time complexity was not a major concern but retrieval complexity being as low as possible was paramount.

RingBuffer

  • Update implementation to allow specification for an overwriting buffer
    • If the user set a constant value bool overwrite; as true upon buffer instantiation, the buffer would move the head to the tail, increment the tail, and overwrite the value at the head (old tail), thus maintaining First In First Out in overwriting the oldest element in the buffer.

About

Repository containing Ben Ryan's official submissions for Synspective's Embedded Software Engineer coding test.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors