0
\$\begingroup\$

This is a follow-up question for Android app class serialization. Some problems have been mentioned in forsvarir's answer. Then, I am following JUnit 5 User Guide to redesign the test cases for User class. Because the part of Save and Load methods are the operations including read and write on storage. It seems that they're not a part of unit testing.

The experimental implementation

  • Project name: UnitTestsForUserClass

  • Unit Tests for User class:

    package com.example.unittestsforuserclass;
    
    import static org.junit.jupiter.api.Assertions.assertEquals;
    
    import org.junit.jupiter.api.Assertions;
    
    import java.security.NoSuchAlgorithmException;
    
    class UserTest {
    
        @org.junit.jupiter.api.Test
        void getFullName() {
            Assertions.assertThrows(NoSuchAlgorithmException.class, () -> {
                User User1 = new User(
                        "Mike",
                        "M12345678",
                        "1990/10/13",
                        "(555) 555-1234",
                        "[email protected]",
                        "password");
                assertEquals("Mike", User1.GetFullName());
            });
        }
    
        @org.junit.jupiter.api.Test
        void getPersonalID() {
            Assertions.assertThrows(NoSuchAlgorithmException.class, () -> {
                User User1 = new User(
                        "Mike",
                        "M12345678",
                        "1990/10/13",
                        "(555) 555-1234",
                        "[email protected]",
                        "password");
                assertEquals("M12345678", User1.GetPersonalID());
            });
        }
    
        @org.junit.jupiter.api.Test
        void getDateOfBirth() {
            Assertions.assertThrows(NoSuchAlgorithmException.class, () -> {
                User User1 = new User(
                        "Mike",
                        "M12345678",
                        "1990/10/13",
                        "(555) 555-1234",
                        "[email protected]",
                        "password");
                assertEquals("1990/10/13", User1.GetDateOfBirth());
            });
        }
    
        @org.junit.jupiter.api.Test
        void getCellPhoneNumber() {
            Assertions.assertThrows(NoSuchAlgorithmException.class, () -> {
                User User1 = new User(
                        "Mike",
                        "M12345678",
                        "1990/10/13",
                        "(555) 555-1234",
                        "[email protected]",
                        "password");
                assertEquals("(555) 555-1234", User1.GetCellPhoneNumber());
            });
        }
    
        @org.junit.jupiter.api.Test
        void getEmailInfo() {
            Assertions.assertThrows(NoSuchAlgorithmException.class, () -> {
                User User1 = new User(
                        "Mike",
                        "M12345678",
                        "1990/10/13",
                        "(555) 555-1234",
                        "[email protected]",
                        "password");
                assertEquals("[email protected]", User1.GetEmailInfo());
            });
        }
    
        @org.junit.jupiter.api.Test
        void checkPassword() {
            Assertions.assertThrows(NoSuchAlgorithmException.class, () -> {
                User User1 = new User(
                        "Mike",
                        "M12345678",
                        "1990/10/13",
                        "(555) 555-1234",
                        "[email protected]",
                        "password");
                assertEquals(User1.CheckPassword("password"), true);
                assertEquals(User1.CheckPassword("password1"), false);
            });
        }
    }
    
  • User class implementation:

    package com.example.unittestsforuserclass;
    
    import android.content.Context;
    import android.util.Log;
    
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    
    
    public class User  implements java.io.Serializable{
        private String fullName;
        private String personalID;
        private String dateOfBirth;
        private String cellPhoneNumber;
        private String emailInfo;
        private String password;
    
    
        public User(String fullNameInput,
                    String personalIDInput,
                    String dateOfBirthInput,
                    String cellPhoneNumberInput,
                    String emailInfoInput,
                    String passwordInput) throws NoSuchAlgorithmException             //  User object constructor
        {
            this.fullName = fullNameInput;
            this.personalID = personalIDInput;
            this.dateOfBirth = dateOfBirthInput;
            this.cellPhoneNumber = cellPhoneNumberInput;
            this.emailInfo = emailInfoInput;
            this.password = HashingMethod(passwordInput);
        }
    
        public String GetFullName()
        {
            return this.fullName;
        }
        public String GetPersonalID()
        {
            return this.personalID;
        }
    
        public String GetDateOfBirth()
        {
            return this.dateOfBirth;
        }
    
        public String GetCellPhoneNumber()
        {
            return this.cellPhoneNumber;
        }
    
        public String GetEmailInfo()
        {
            return this.emailInfo;
        }
    
        public String GetHash() throws NoSuchAlgorithmException
        {
            return HashingMethod(this.fullName + this.personalID);
        }
    
        public String GetHashedPassword() throws NoSuchAlgorithmException
        {
            return this.password;
        }
    
        public boolean CheckPassword(String password)
        {
            boolean result = false;
            try {
                result = this.password.equals(HashingMethod(password));
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
            return result;
        }
    
        //  Reference: https://stackoverflow.com/a/4118917/6667035
        //  fileName cannot contain any path separator
        public boolean Save(Context context, String fileName)
        {
            try {
                FileOutputStream fos = context.openFileOutput(fileName, Context.MODE_PRIVATE);
                ObjectOutputStream os = new ObjectOutputStream(fos);
                os.writeObject(this);
                os.close();
                fos.close();
                return true;
            } catch (IOException i) {
                i.printStackTrace();
                return false;
            }
        }
    
        public void Load(Context context, String fileName){
            try {
                FileInputStream fis = context.openFileInput(fileName);
                ObjectInputStream is = new ObjectInputStream(fis);
                User simpleClass = (User) is.readObject();
                is.close();
                fis.close();
                this.fullName = simpleClass.fullName;
                this.personalID = simpleClass.personalID;
                this.dateOfBirth = simpleClass.dateOfBirth;
                this.cellPhoneNumber = simpleClass.cellPhoneNumber;
                this.emailInfo = simpleClass.emailInfo;
                this.password = simpleClass.password;
            } catch (Exception e){
                e.printStackTrace();
            }
        }
        //*****************************************************************************
    
        //  Reference: https://stackoverflow.com/a/2624385/6667035
        private String HashingMethod(String InputString) throws NoSuchAlgorithmException
        {
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
            String stringToHash = InputString;
            messageDigest.update(stringToHash.getBytes());
            String stringHash = new String(messageDigest.digest());
            return stringHash;
        }
    }
    

All suggestions are welcome.

The summary information:

  • Which question it is a follow-up to?

    Android APP class serialization in JAVA

  • What changes has been made in the code since last question?

    I am following JUnit 5 User Guide to redesign the test cases for User class.

  • Why a new review is being asked for?

    If there is any possible improvement, please let me know.

\$\endgroup\$
4
  • \$\begingroup\$ I've voted to close this question at the moment, because your unit tests really don't look like they work. All of them seem to be expecting a NoSuchAlgorithmException to be thrown. This may be a misinterpretation of what I said on your other question. You should allow any exceptions thrown by the test to escape the test case unless you are actually testing for an expected exception to be thrown. So, for example void getFullName() throws NoSuchAlgorithmException then perform the rest of the test with no exception handling. \$\endgroup\$
    – forsvarir
    Commented May 17, 2021 at 22:21
  • \$\begingroup\$ @forsvarir Sorry for the misunderstanding of your answer. However, it seems that I can't modify the code here after receiving answers. \$\endgroup\$
    – JimmyHu
    Commented May 17, 2021 at 23:51
  • 2
    \$\begingroup\$ If your question is off-topic/closed you can edit the code in the question. If your question is closed please feel free to update the code in the question. But in lieu of your post being closed posting a new question is probably best. \$\endgroup\$
    – Peilonrayz
    Commented May 21, 2021 at 17:14
  • 2
    \$\begingroup\$ I have rolled back your latest edit. Your actions go against what I said you can do in my previous comment. Your question is not closed, so you may not edit. \$\endgroup\$
    – Peilonrayz
    Commented May 27, 2021 at 7:55

1 Answer 1

3
\$\begingroup\$

Unittests are (executable) documentation

The name of a test method should read like a sentence from the requirements.

Beginners should stick with the pattern suggested by Roi Osherove methodName_precondition_expectedResult()

Keep AAA - Structure of test methods visible.

Test Methods consist of three parts:

  • Arrange
  • Act
  • Assert

Others call it "given, when then" but I like this high quality rating analogy from the stock market...
Always keep this three parts because this gives you the chance to introduce local variables with descriptive names, that help future readers unfamiliar with your code (which is yourself 5 month later) to guess what's going on here.

And no, the setup method is no (complete) replacement for the arange part since the arrangement is always specific to the test method. Two test methods with the exact same arrangement obviously test the same behavior and are redundant (a nice word for "useless" ;o)).

Any unittest method covers exactly one single expectation on the codes behavior.

That effectively results in one single assertion in a test method.

Yes, it is OK to have multiple assertion in a test method, but in that case you must have a good reason for that (given in the test methods name).

General coding

Adhere to the Java Naming Conventions.

  • Names of methods and variables start with a lowercase letter, names of classes with an uppercase letter.

  • Method names should start with a verb so that they read like an instruction. Names of variables should start with a noun.

    Exception to that are variables holding or methods returning a boolean which both should start with is, can, has or alike.

Do not do work in Constructors.

Checks that may lead to exceptions (other then NPEs) should be done before the data are passed to the constructor.

Do not hide dependencies.

In your method HashingMethod() you use an instance of MessageDigest This is a hidden dependency. You better pass that instance into your class as constructor parameter.

Do not mix DTOs with Business logic classes.

Any class should be either one, not both. DTOs with business logic will poisen other layers of the application with that. Imagine you want to transfer objects of your User class via network from a client to your server application. Most likely you don't want the other end to know about the hashing...

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.