3

I am a newbie dev and I am trying to create a unit test for the code below. Mind you the code isnt perfect as I have written it in the context of what I have read/learnt so far. Its a program meant to display the prime factors of a number entered by a user. In my unit test file, i created an object of class PFGen but for some reason i am not able to use the object to call method PrimeFactors (which returns an array). Will appreciate all help.

using System;
using static System.Console;
using System.Diagnostics;
using System.IO;

namespace PrimeFactorsGen
{
    public class PFGen
    {
        public static Array PrimeFactors(int number)
        {
            int[] pf = new int[10];
            int position = 0;

            for(int div = 2; div <= number; div++)
            {
                while(number % div == 0)
                {
                    pf[position] = div;
                    number = number / div;
                    position = position + 1;
                }
            }

            return pf;
        }

        public static void RunPrimeFactor()
        {
            Write("Please enter a value to calculate prime factors: ");
            if (int.TryParse(ReadLine(), out int number)){
                if(number >= 2)
                {
                    WriteLine($"The prime factors of {number} are: ");
                    foreach(var entry in PrimeFactors(number))
                    {
                        Write($"{entry}, ");
                    }
                }
                else
                {
                    WriteLine("Enter a number greater than 2 next time!");
                }
            }else
            {
                WriteLine("Enter a valid number");
            }
        }

        static void Main(string[] args)
        {
            Trace.Listeners.Add(new TextWriterTraceListener(File.CreateText("log.txt")));

            Trace.AutoFlush = true;
            Trace.WriteLine("Trace is listening...");

            RunPrimeFactor();
        }
    }
}

Thanks for the help so far. I have been able to access PrimeFactors by calling it directly with PFGen. The unit test code is below. It passed.

using System;
using Xunit;
using PrimeFactorsGen;

namespace FactorialUnitTest
{
    public class UnitTest1
    {
        [Fact]
        public void Test1()
        {
            //arrange
            int number = 42;
            int[] expected = { 2, 3, 7, 0, 0, 0, 0, 0, 0, 0 };
            //act
            var actual = PFGen.PrimeFactors(number);
            //assert
            Assert.Equal(actual,expected);
        }
    }
}
5
  • Your methods are static so creating an instance is not needed. PFGen.PrimeFactors(42); should work, except that the return type of Array is a bit unexpected, due to "magic" it will work. Commented Jun 17, 2018 at 18:23
  • Would you show the unit test code as well, please? Commented Jun 17, 2018 at 18:31
  • Some quick comments in the meantime: * Since PrimeFactors is declared static, you wouldn't create an instance of PFGen to call it--you would call it as var output = PFGen.PrimeFactors(input); or similar. * It would be more correct for PrimeFactors to return int[] rather than Array, because Array isn't intrinsically type-safe. Commented Jun 17, 2018 at 18:35
  • Thanks all, please see above modifications and test file. I realize the code despite working, is not so optimal but at the moment i dont know any other technique to solve the prime factor problem without putting its values into an array. I would love suggestions on how to do better. Thanks. Commented Jun 17, 2018 at 19:07
  • 2
    Just a helpful hint, not related to your question: XUnit's Assert.Equal wants its parameters in expected, actual order (the reverse of what you have). Your assertion will still work, but you'll get better failure messages if you order the parameters correctly. Commented Jun 17, 2018 at 20:22

2 Answers 2

1

This is a great use case for XUnit's Theory attribute. Decorating a test method with the Theory attribute means that you can run it multiple times with different inputs and expected outputs.

First, I've taken the liberty of changing the PrimeFactors method's return value from an array to a List<int> (see Microsoft documentation of this class). This has two advantages over an array:

  • An array's length is fixed when it is declared, whereas a List<T> grows and shrinks as items are added or removed - this eliminates the misleading zeroes in the unused elements of the array (zero isn't a prime factor of anything).
  • A List<T> is type safe - we know that every element in the list is of type T, and any attempt to put something that's not of type T into the list will result in a compile error.
public static List<int> PrimeFactors(int number)
{
    //int[] pf = new int[10];
    var pf = new List<int>();
    //int position = 0;

    for (int div = 2; div <= number; div++)
    {
        while (number % div == 0)
        {
            //pf[position] = div;
            pf.Add(div);
            number = number / div;
            //position = position + 1;
        }
    }

    return pf;
}

Anyway, back to the actual question...

There are a few different ways of passing test data to your Theory test, but the simplest is probably to add InlineData attributes to your test method. The InlineData attribute accepts the same parameters as your test method, and passes those parameters into your test method.

This is the unit test I've written:

[Theory]
[InlineData(2, new int[] { 2 })]
[InlineData(3, new int[] { 3 })]
[InlineData(4, new int[] { 2, 2 })]
[InlineData(5, new int[] { 5 })]
[InlineData(6, new int[] { 2, 3 })]
[InlineData(7, new int[] { 7 })]
[InlineData(8, new int[] { 2, 2, 2 })]
[InlineData(9, new int[] { 3, 3 })]
[InlineData(10, new int[] { 2, 5 })]
[InlineData(11, new int[] { 11 })]
[InlineData(12, new int[] { 2, 2, 3 })]
[InlineData(13, new int[] { 13 })]
[InlineData(14, new int[] { 2, 7 })]
[InlineData(15, new int[] { 3, 5 })]
[InlineData(16, new int[] { 2, 2, 2, 2 })]
[InlineData(17, new int[] { 17 })]
[InlineData(18, new int[] { 2, 3, 3 })]
[InlineData(19, new int[] { 19 })]
[InlineData(20, new int[] { 2, 2, 5 })]
[InlineData(21, new int[] { 3, 7 })]
[InlineData(22, new int[] { 2, 11 })]
[InlineData(23, new int[] { 23 })]
[InlineData(24, new int[] { 2, 2, 2, 3 })]
[InlineData(25, new int[] { 5, 5 })]
[InlineData(42, new int[] { 2, 3, 7 })]
public void PrimeFactorsTest(int input, int[] primeFactors)
{
    // Arrange
    var expected = new List<int>(primeFactors);

    // Act
    var actual = PFGen.PrimeFactors(input);

    // Assert
    Assert.Equal(expected, actual);
}

The test method accepts two parameters

  • an integer called input - this is the number we want to find the prime factors of.
  • an array called primeFactors - this is an array of the prime factors that we expect the PrimeFactors method to return for the given value of input.

Each InlineData attribute passes the same two parameters into the test method, for example

[InlineData(42, new int[] { 2, 3, 7 })]

This will pass 42 to the test method's input parameter, and an array of 2, 3 and 7 to the test method's primeFactors parameter, to indicate that when we pass 42 to the PrimeFactors method, we expect the returned prime factors to be 2, 3 and 7.

If you want to add more inputs and expected outputs to the test, just add more InlineData attributes.

Sign up to request clarification or add additional context in comments.

Comments

0

Don't use the Array type. EIther use the type-safe int[] or the variable length List<int>. You can check a collection with CollectionAssert.AreEqual()

For example in the test method

List<int> actual = PrimeFactors(42);
int[] expected = new int[] { 2, 3, 7 };
CollectionAssert.AreEqual(expected, actual);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.